accessibility-widgets 1.0.6 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +370 -365
  2. package/package.json +1 -1
  3. package/widget.js +1244 -436
package/widget.js CHANGED
@@ -1,10 +1,396 @@
1
- /*
1
+ /*
2
2
  ===========================================
3
3
  ACCESSIBILITY WIDGET
4
- A comprehensive web accessibility tool
5
4
  ===========================================
6
5
  */
7
6
 
7
+ // ===========================================
8
+ // TRANSLATIONS
9
+ // ===========================================
10
+
11
+ const TRANSLATIONS = {
12
+ en: {
13
+ accessibilityMenu: 'Accessibility Menu',
14
+ closeAccessibilityMenu: 'Close Accessibility Menu',
15
+ accessibilityTools: 'Accessibility Tools',
16
+ resetAllSettings: 'Reset All Settings',
17
+ screenReader: 'Screen Reader',
18
+ voiceCommand: 'Voice Command',
19
+ textSpacing: 'Text Spacing',
20
+ pauseAnimations: 'Pause Animations',
21
+ hideImages: 'Hide Images',
22
+ dyslexiaFriendly: 'Dyslexia Friendly',
23
+ biggerCursor: 'Bigger Cursor',
24
+ lineHeight: 'Line Height',
25
+ fontSelection: 'Font Selection',
26
+ colorFilter: 'Color Filter',
27
+ textAlign: 'Text Align',
28
+ textSize: 'Text Size',
29
+ highContrast: 'High Contrast',
30
+ defaultFont: 'Default Font',
31
+ noFilter: 'No Filter',
32
+ default: 'Default',
33
+ screenReaderOn: 'Screen reader on',
34
+ screenReaderOff: 'Screen reader off',
35
+ voiceControlActivated: 'Voice control activated',
36
+ notSupportedBrowser: 'is not supported in this browser',
37
+ close: 'Close',
38
+ reset: 'Reset',
39
+ saturation: 'Saturation',
40
+ selectLanguage: 'Select Language'
41
+ },
42
+ de: {
43
+ accessibilityMenu: 'Barrierefreiheitsmenü',
44
+ closeAccessibilityMenu: 'Barrierefreiheitsmenü schließen',
45
+ accessibilityTools: 'Barrierefreiheitswerkzeuge',
46
+ resetAllSettings: 'Alle Einstellungen zurücksetzen',
47
+ screenReader: 'Screenreader',
48
+ voiceCommand: 'Sprachbefehl',
49
+ textSpacing: 'Textabstand',
50
+ pauseAnimations: 'Animationen pausieren',
51
+ hideImages: 'Bilder ausblenden',
52
+ dyslexiaFriendly: 'Legasthenie-freundlich',
53
+ biggerCursor: 'Größerer Cursor',
54
+ lineHeight: 'Zeilenhöhe',
55
+ fontSelection: 'Schriftauswahl',
56
+ colorFilter: 'Farbfilter',
57
+ textAlign: 'Textausrichtung',
58
+ textSize: 'Textgröße',
59
+ highContrast: 'Hoher Kontrast',
60
+ defaultFont: 'Standardschrift',
61
+ noFilter: 'Kein Filter',
62
+ default: 'Standard',
63
+ screenReaderOn: 'Screenreader ein',
64
+ screenReaderOff: 'Screenreader aus',
65
+ voiceControlActivated: 'Sprachsteuerung aktiviert',
66
+ notSupportedBrowser: 'wird in diesem Browser nicht unterstützt',
67
+ close: 'Schließen',
68
+ reset: 'Zurücksetzen',
69
+ saturation: 'Sättigung',
70
+ selectLanguage: 'Sprache wählen'
71
+ },
72
+ es: {
73
+ accessibilityMenu: 'Menú de Accesibilidad',
74
+ closeAccessibilityMenu: 'Cerrar Menú de Accesibilidad',
75
+ accessibilityTools: 'Herramientas de Accesibilidad',
76
+ resetAllSettings: 'Restablecer Todas las Configuraciones',
77
+ screenReader: 'Lector de Pantalla',
78
+ voiceCommand: 'Comando de Voz',
79
+ textSpacing: 'Espaciado de Texto',
80
+ pauseAnimations: 'Pausar Animaciones',
81
+ hideImages: 'Ocultar Imágenes',
82
+ dyslexiaFriendly: 'Amigable para Dislexia',
83
+ biggerCursor: 'Cursor Más Grande',
84
+ lineHeight: 'Altura de Línea',
85
+ fontSelection: 'Selección de Fuente',
86
+ colorFilter: 'Filtro de Color',
87
+ textAlign: 'Alineación de Texto',
88
+ textSize: 'Tamaño de Texto',
89
+ highContrast: 'Alto Contraste',
90
+ defaultFont: 'Fuente Predeterminada',
91
+ noFilter: 'Sin Filtro',
92
+ default: 'Predeterminado',
93
+ screenReaderOn: 'Lector de pantalla activado',
94
+ screenReaderOff: 'Lector de pantalla desactivado',
95
+ voiceControlActivated: 'Control de voz activado',
96
+ notSupportedBrowser: 'no es compatible con este navegador',
97
+ close: 'Cerrar',
98
+ reset: 'Restablecer',
99
+ saturation: 'Saturación',
100
+ selectLanguage: 'Seleccionar Idioma'
101
+ },
102
+ it: {
103
+ accessibilityMenu: 'Menu Accessibilità',
104
+ closeAccessibilityMenu: 'Chiudi Menu Accessibilità',
105
+ accessibilityTools: 'Strumenti di Accessibilità',
106
+ resetAllSettings: 'Ripristina Tutte le Impostazioni',
107
+ screenReader: 'Lettore Schermo',
108
+ voiceCommand: 'Comando Vocale',
109
+ textSpacing: 'Spaziatura Testo',
110
+ pauseAnimations: 'Pausa Animazioni',
111
+ hideImages: 'Nascondi Immagini',
112
+ dyslexiaFriendly: 'Adatto alla Dislessia',
113
+ biggerCursor: 'Cursore Più Grande',
114
+ lineHeight: 'Altezza Linea',
115
+ fontSelection: 'Selezione Font',
116
+ colorFilter: 'Filtro Colore',
117
+ textAlign: 'Allineamento Testo',
118
+ textSize: 'Dimensione Testo',
119
+ highContrast: 'Alto Contrasto',
120
+ defaultFont: 'Font Predefinito',
121
+ noFilter: 'Nessun Filtro',
122
+ default: 'Predefinito',
123
+ screenReaderOn: 'Lettore schermo attivo',
124
+ screenReaderOff: 'Lettore schermo disattivo',
125
+ voiceControlActivated: 'Controllo vocale attivato',
126
+ notSupportedBrowser: 'non è supportato in questo browser',
127
+ close: 'Chiudi',
128
+ reset: 'Ripristina',
129
+ saturation: 'Saturazione',
130
+ selectLanguage: 'Seleziona Lingua'
131
+ },
132
+ fr: {
133
+ accessibilityMenu: 'Menu Accessibilité',
134
+ closeAccessibilityMenu: 'Fermer le Menu Accessibilité',
135
+ accessibilityTools: 'Outils d\'Accessibilité',
136
+ resetAllSettings: 'Réinitialiser Tous les Paramètres',
137
+ screenReader: 'Lecteur d\'Écran',
138
+ voiceCommand: 'Commande Vocale',
139
+ textSpacing: 'Espacement du Texte',
140
+ pauseAnimations: 'Mettre en Pause les Animations',
141
+ hideImages: 'Masquer les Images',
142
+ dyslexiaFriendly: 'Convivial pour la Dyslexie',
143
+ biggerCursor: 'Curseur Plus Grand',
144
+ lineHeight: 'Hauteur de Ligne',
145
+ fontSelection: 'Sélection de Police',
146
+ colorFilter: 'Filtre de Couleur',
147
+ textAlign: 'Alignement du Texte',
148
+ textSize: 'Taille du Texte',
149
+ highContrast: 'Contraste Élevé',
150
+ defaultFont: 'Police par Défaut',
151
+ noFilter: 'Aucun Filtre',
152
+ default: 'Par Défaut',
153
+ screenReaderOn: 'Lecteur d\'écran activé',
154
+ screenReaderOff: 'Lecteur d\'écran désactivé',
155
+ voiceControlActivated: 'Contrôle vocal activé',
156
+ notSupportedBrowser: 'n\'est pas pris en charge dans ce navigateur',
157
+ close: 'Fermer',
158
+ reset: 'Réinitialiser',
159
+ saturation: 'Saturation',
160
+ selectLanguage: 'Sélectionner la Langue'
161
+ },
162
+ ru: {
163
+ accessibilityMenu: 'Меню Доступности',
164
+ closeAccessibilityMenu: 'Закрыть Меню Доступности',
165
+ accessibilityTools: 'Инструменты Доступности',
166
+ resetAllSettings: 'Сбросить Все Настройки',
167
+ screenReader: 'Программа Чтения с Экрана',
168
+ voiceCommand: 'Голосовая Команда',
169
+ textSpacing: 'Межбуквенный Интервал',
170
+ pauseAnimations: 'Приостановить Анимацию',
171
+ hideImages: 'Скрыть Изображения',
172
+ dyslexiaFriendly: 'Для Дислексии',
173
+ biggerCursor: 'Увеличенный Курсор',
174
+ lineHeight: 'Высота Строки',
175
+ fontSelection: 'Выбор Шрифта',
176
+ colorFilter: 'Цветовой Фильтр',
177
+ textAlign: 'Выравнивание Текста',
178
+ textSize: 'Размер Текста',
179
+ highContrast: 'Высокая Контрастность',
180
+ defaultFont: 'Шрифт по Умолчанию',
181
+ noFilter: 'Без Фильтра',
182
+ default: 'По Умолчанию',
183
+ screenReaderOn: 'Программа чтения включена',
184
+ screenReaderOff: 'Программа чтения выключена',
185
+ voiceControlActivated: 'Голосовое управление активировано',
186
+ notSupportedBrowser: 'не поддерживается в этом браузере',
187
+ close: 'Закрыть',
188
+ reset: 'Сбросить',
189
+ saturation: 'Насыщенность',
190
+ selectLanguage: 'Выберите Язык'
191
+ },
192
+ tr: {
193
+ accessibilityMenu: 'Erişilebilirlik Menüsü',
194
+ closeAccessibilityMenu: 'Erişilebilirlik Menüsünü Kapat',
195
+ accessibilityTools: 'Erişilebilirlik Araçları',
196
+ resetAllSettings: 'Tüm Ayarları Sıfırla',
197
+ screenReader: 'Ekran Okuyucu',
198
+ voiceCommand: 'Sesli Komut',
199
+ textSpacing: 'Metin Aralığı',
200
+ pauseAnimations: 'Animasyonları Duraklat',
201
+ hideImages: 'Resimleri Gizle',
202
+ dyslexiaFriendly: 'Disleksi Dostu',
203
+ biggerCursor: 'Daha Büyük İmleç',
204
+ lineHeight: 'Satır Yüksekliği',
205
+ fontSelection: 'Yazı Tipi Seçimi',
206
+ colorFilter: 'Renk Filtresi',
207
+ textAlign: 'Metin Hizalama',
208
+ textSize: 'Metin Boyutu',
209
+ highContrast: 'Yüksek Kontrast',
210
+ defaultFont: 'Varsayılan Yazı Tipi',
211
+ noFilter: 'Filtre Yok',
212
+ default: 'Varsayılan',
213
+ screenReaderOn: 'Ekran okuyucu açık',
214
+ screenReaderOff: 'Ekran okuyucu kapalı',
215
+ voiceControlActivated: 'Sesli kontrol etkinleştirildi',
216
+ notSupportedBrowser: 'bu tarayıcıda desteklenmiyor',
217
+ close: 'Kapat',
218
+ reset: 'Sıfırla',
219
+ saturation: 'Doygunluk',
220
+ selectLanguage: 'Dil Seçin'
221
+ },
222
+ ar: {
223
+ accessibilityMenu: 'قائمة إمكانية الوصول',
224
+ closeAccessibilityMenu: 'إغلاق قائمة إمكانية الوصول',
225
+ accessibilityTools: 'أدوات إمكانية الوصول',
226
+ resetAllSettings: 'إعادة تعيين جميع الإعدادات',
227
+ screenReader: 'قارئ الشاشة',
228
+ voiceCommand: 'الأمر الصوتي',
229
+ textSpacing: 'تباعد النص',
230
+ pauseAnimations: 'إيقاف الرسوم المتحركة مؤقتًا',
231
+ hideImages: 'إخفاء الصور',
232
+ dyslexiaFriendly: 'صديق لعسر القراءة',
233
+ biggerCursor: 'مؤشر أكبر',
234
+ lineHeight: 'ارتفاع الخط',
235
+ fontSelection: 'اختيار الخط',
236
+ colorFilter: 'مرشح الألوان',
237
+ textAlign: 'محاذاة النص',
238
+ textSize: 'حجم النص',
239
+ highContrast: 'تباين عالي',
240
+ defaultFont: 'الخط الافتراضي',
241
+ noFilter: 'بدون مرشح',
242
+ default: 'افتراضي',
243
+ screenReaderOn: 'قارئ الشاشة مفعّل',
244
+ screenReaderOff: 'قارئ الشاشة معطل',
245
+ voiceControlActivated: 'تم تفعيل التحكم الصوتي',
246
+ notSupportedBrowser: 'غير مدعوم في هذا المتصفح',
247
+ close: 'إغلاق',
248
+ reset: 'إعادة تعيين',
249
+ saturation: 'التشبع',
250
+ selectLanguage: 'اختر اللغة'
251
+ },
252
+ hi: {
253
+ accessibilityMenu: 'पहुँच मेनू',
254
+ closeAccessibilityMenu: 'पहुँच मेनू बंद करें',
255
+ accessibilityTools: 'पहुँच उपकरण',
256
+ resetAllSettings: 'सभी सेटिंग्स रीसेट करें',
257
+ screenReader: 'स्क्रीन रीडर',
258
+ voiceCommand: 'वॉयस कमांड',
259
+ textSpacing: 'टेक्स्ट स्पेसिंग',
260
+ pauseAnimations: 'एनिमेशन रोकें',
261
+ hideImages: 'चित्र छिपाएँ',
262
+ dyslexiaFriendly: 'डिस्लेक्सिया के अनुकूल',
263
+ biggerCursor: 'बड़ा कर्सर',
264
+ lineHeight: 'लाइन की ऊँचाई',
265
+ fontSelection: 'फ़ॉन्ट चयन',
266
+ colorFilter: 'रंग फ़िल्टर',
267
+ textAlign: 'टेक्स्ट संरेखण',
268
+ textSize: 'टेक्स्ट का आकार',
269
+ highContrast: 'उच्च कंट्रास्ट',
270
+ defaultFont: 'डिफ़ॉल्ट फ़ॉन्ट',
271
+ noFilter: 'कोई फ़िल्टर नहीं',
272
+ default: 'डिफ़ॉल्ट',
273
+ screenReaderOn: 'स्क्रीन रीडर चालू',
274
+ screenReaderOff: 'स्क्रीन रीडर बंद',
275
+ voiceControlActivated: 'वॉयस नियंत्रण सक्रिय',
276
+ notSupportedBrowser: 'इस ब्राउज़र में समर्थित नहीं है',
277
+ close: 'बंद करें',
278
+ reset: 'रीसेट करें',
279
+ saturation: 'संतृप्ति',
280
+ selectLanguage: 'भाषा चुनें'
281
+ },
282
+ 'zh-cn': {
283
+ accessibilityMenu: '辅助功能菜单',
284
+ closeAccessibilityMenu: '关闭辅助功能菜单',
285
+ accessibilityTools: '辅助功能工具',
286
+ resetAllSettings: '重置所有设置',
287
+ screenReader: '屏幕阅读器',
288
+ voiceCommand: '语音命令',
289
+ textSpacing: '文本间距',
290
+ pauseAnimations: '暂停动画',
291
+ hideImages: '隐藏图片',
292
+ dyslexiaFriendly: '阅读障碍友好',
293
+ biggerCursor: '更大的光标',
294
+ lineHeight: '行高',
295
+ fontSelection: '字体选择',
296
+ colorFilter: '颜色滤镜',
297
+ textAlign: '文本对齐',
298
+ textSize: '文本大小',
299
+ highContrast: '高对比度',
300
+ defaultFont: '默认字体',
301
+ noFilter: '无滤镜',
302
+ default: '默认',
303
+ screenReaderOn: '屏幕阅读器已开启',
304
+ screenReaderOff: '屏幕阅读器已关闭',
305
+ voiceControlActivated: '语音控制已激活',
306
+ notSupportedBrowser: '此浏览器不支持',
307
+ close: '关闭',
308
+ reset: '重置',
309
+ saturation: '饱和度',
310
+ selectLanguage: '选择语言'
311
+ },
312
+ jp: {
313
+ accessibilityMenu: 'アクセシビリティメニュー',
314
+ closeAccessibilityMenu: 'アクセシビリティメニューを閉じる',
315
+ accessibilityTools: 'アクセシビリティツール',
316
+ resetAllSettings: 'すべての設定をリセット',
317
+ screenReader: 'スクリーンリーダー',
318
+ voiceCommand: '音声コマンド',
319
+ textSpacing: 'テキスト間隔',
320
+ pauseAnimations: 'アニメーション一時停止',
321
+ hideImages: '画像を非表示',
322
+ dyslexiaFriendly: 'ディスレクシア対応',
323
+ biggerCursor: '大きいカーソル',
324
+ lineHeight: '行の高さ',
325
+ fontSelection: 'フォント選択',
326
+ colorFilter: 'カラーフィルター',
327
+ textAlign: 'テキスト配置',
328
+ textSize: 'テキストサイズ',
329
+ highContrast: 'ハイコントラスト',
330
+ defaultFont: 'デフォルトフォント',
331
+ noFilter: 'フィルターなし',
332
+ default: 'デフォルト',
333
+ screenReaderOn: 'スクリーンリーダーがオン',
334
+ screenReaderOff: 'スクリーンリーダーがオフ',
335
+ voiceControlActivated: '音声制御が有効',
336
+ notSupportedBrowser: 'このブラウザではサポートされていません',
337
+ close: '閉じる',
338
+ reset: 'リセット',
339
+ saturation: '彩度',
340
+ selectLanguage: '言語を選択'
341
+ }
342
+ };
343
+
344
+ // Language detection and management
345
+ let currentLanguage = 'en';
346
+
347
+ function detectBrowserLanguage() {
348
+ const browserLang = (navigator.language || navigator.userLanguage).toLowerCase();
349
+
350
+ // Direct match
351
+ if (TRANSLATIONS[browserLang]) {
352
+ return browserLang;
353
+ }
354
+
355
+ // Try language code only (e.g., 'en' from 'en-US')
356
+ const langCode = browserLang.split('-')[0];
357
+ if (TRANSLATIONS[langCode]) {
358
+ return langCode;
359
+ }
360
+
361
+ // Special case for Chinese
362
+ if (browserLang.includes('zh')) {
363
+ if (browserLang.includes('cn') || browserLang.includes('hans')) {
364
+ return 'zh-cn';
365
+ }
366
+ }
367
+
368
+ // Default to English
369
+ return 'en';
370
+ }
371
+
372
+ function setLanguage(lang) {
373
+ if (TRANSLATIONS[lang]) {
374
+ currentLanguage = lang;
375
+ localStorage.setItem('accessibilityWidgetLanguage', lang);
376
+ return true;
377
+ }
378
+ return false;
379
+ }
380
+
381
+ function getTranslation(key) {
382
+ return TRANSLATIONS[currentLanguage][key] || TRANSLATIONS['en'][key] || key;
383
+ }
384
+
385
+ // Initialize language from localStorage or detect from browser
386
+ const savedLanguage = localStorage.getItem('accessibilityWidgetLanguage');
387
+ if (savedLanguage && TRANSLATIONS[savedLanguage]) {
388
+ currentLanguage = savedLanguage;
389
+ } else {
390
+ currentLanguage = detectBrowserLanguage();
391
+ localStorage.setItem('accessibilityWidgetLanguage', currentLanguage);
392
+ }
393
+
8
394
  // ===========================================
9
395
  // CONFIGURATION VARIABLES
10
396
  // ===========================================
@@ -14,21 +400,20 @@ const DEFAULT_WIDGET_CONFIG = {
14
400
  // Core Features
15
401
  enableHighContrast: true,
16
402
  enableBiggerText: true,
17
- enableTextSpacing: true,
18
- enablePauseAnimations: true,
403
+ enableTextSpacing: true, // Now has 3 levels
404
+ enablePauseAnimations: true, // Enhanced to include reduced motion features
19
405
  enableHideImages: true,
20
406
  enableDyslexiaFont: true,
21
407
  enableBiggerCursor: true,
22
- enableLineHeight: true,
408
+ enableLineHeight: true, // Now has 3 levels (2em, 3em, 4em)
23
409
  enableTextAlign: true,
24
-
410
+
25
411
  // Advanced Features
26
412
  enableScreenReader: true,
27
413
  enableVoiceControl: true,
28
- enableReducedMotion: true,
29
414
  enableFontSelection: true,
30
415
  enableColorFilter: true,
31
-
416
+
32
417
  // Widget Styling
33
418
  widgetWidth: '440px',
34
419
  widgetPosition: {
@@ -37,21 +422,16 @@ const DEFAULT_WIDGET_CONFIG = {
37
422
  left: '20px',
38
423
  bottom: '20px'
39
424
  },
40
-
425
+
41
426
  // Colors
42
427
  colors: {
43
- primary: '#000000',
44
- primaryHover: '#00bfff',
45
- secondary: '#f9f9f9',
46
- text: '#333',
47
- textLight: '#fff',
48
- border: '#e6e6e6',
49
- borderHover: '#d4d4d4',
50
- shadow: 'rgba(0, 0, 0, 0.2)',
51
- focus: '#ff6b35',
52
- focusGlow: 'rgba(255, 107, 53, 0.3)'
428
+ primary: '#1663d7', // Header bg, main button bg, active border, close hover bg
429
+ secondary: '#ffffff', // Main button icon color
430
+ optionBg: '#ffffff', // Option button background
431
+ optionText: '#333333', // Option button text color
432
+ optionIcon: '#000000' // Option button icon color
53
433
  },
54
-
434
+
55
435
  // Button styling
56
436
  button: {
57
437
  size: '55px',
@@ -59,34 +439,34 @@ const DEFAULT_WIDGET_CONFIG = {
59
439
  iconSize: '40px',
60
440
  shadow: '0 4px 8px rgba(0, 0, 0, 0.2)'
61
441
  },
62
-
442
+
63
443
  // Menu styling
64
444
  menu: {
65
- headerHeight: '55px',
445
+ headerHeight: '70px',
66
446
  padding: '0 10px 10px 10px',
67
447
  optionPadding: '20px 10px',
68
448
  optionMargin: '10px',
69
449
  borderRadius: '8px',
70
450
  fontSize: '16px',
71
- titleFontSize: '22px',
451
+ titleFontSize: '16px',
72
452
  closeButtonSize: '44px'
73
453
  },
74
-
454
+
75
455
  // Typography
76
456
  typography: {
77
457
  fontFamily: 'Arial, sans-serif',
78
- fontSize: '16px',
458
+ fontSize: '17px',
79
459
  titleFontSize: '22px',
80
- titleFontWeight: '500',
460
+ titleFontWeight: '700',
81
461
  lineHeight: '1'
82
462
  },
83
-
463
+
84
464
  // Animation
85
465
  animation: {
86
466
  transition: '0.2s',
87
467
  hoverScale: '1.05'
88
468
  },
89
-
469
+
90
470
  // Language/Text Configuration
91
471
  lang: {
92
472
  accessibilityMenu: 'Accessibility Menu',
@@ -101,7 +481,6 @@ const DEFAULT_WIDGET_CONFIG = {
101
481
  dyslexiaFriendly: 'Dyslexia Friendly',
102
482
  biggerCursor: 'Bigger Cursor',
103
483
  lineHeight: 'Line Height',
104
- reducedMotion: 'Reduced Motion',
105
484
  fontSelection: 'Font Selection',
106
485
  colorFilter: 'Color Filter',
107
486
  textAlign: 'Text Align',
@@ -114,7 +493,8 @@ const DEFAULT_WIDGET_CONFIG = {
114
493
  screenReaderOff: 'Screen reader off',
115
494
  voiceControlActivated: 'Voice control activated',
116
495
  notSupportedBrowser: 'is not supported in this browser',
117
- close: 'Close'
496
+ close: 'Close',
497
+ reset: 'Reset'
118
498
  },
119
499
 
120
500
  // Voice Command Configuration - Developers can customize commands for different languages
@@ -136,7 +516,7 @@ const DEFAULT_WIDGET_CONFIG = {
136
516
 
137
517
  // Grid Layout Configuration
138
518
  gridLayout: {
139
- columns: '1fr 1fr', // Default 2-column layout (can be changed to '1fr 1fr 1fr' for 3 columns, etc.)
519
+ columns: '1fr 1fr', // Default 2-column layout
140
520
  gap: '10px' // Gap between grid items
141
521
  }
142
522
  };
@@ -144,9 +524,9 @@ const DEFAULT_WIDGET_CONFIG = {
144
524
  // Function to deep merge user configuration with defaults
145
525
  function mergeConfigs(defaultConfig, userConfig) {
146
526
  const result = { ...defaultConfig };
147
-
527
+
148
528
  if (!userConfig) return result;
149
-
529
+
150
530
  for (const key in userConfig) {
151
531
  if (userConfig.hasOwnProperty(key)) {
152
532
  if (typeof userConfig[key] === 'object' && userConfig[key] !== null && !Array.isArray(userConfig[key])) {
@@ -156,26 +536,38 @@ function mergeConfigs(defaultConfig, userConfig) {
156
536
  }
157
537
  }
158
538
  }
159
-
539
+
160
540
  return result;
161
541
  }
162
542
 
163
543
  // Merge user configuration with defaults
164
- // Users can define window.ACCESSIBILITY_WIDGET_CONFIG before loading this script
165
544
  const WIDGET_CONFIG = mergeConfigs(DEFAULT_WIDGET_CONFIG, window.ACCESSIBILITY_WIDGET_CONFIG || {});
166
545
 
167
546
  // ===========================================
168
547
  // STYLES & VISUAL ASSETS
169
548
  // ===========================================
170
549
 
171
- // Generate styles using configuration variables
172
- const styles = `
550
+ // Widget styles (will go inside Shadow DOM - NOT affected by page styles or accessibility features)
551
+ const widgetStyles = `
552
+ :host {
553
+ all: initial;
554
+ font-family: ${WIDGET_CONFIG.typography.fontFamily};
555
+ }
556
+
557
+ * {
558
+ box-sizing: border-box;
559
+ }
560
+
173
561
  #snn-accessibility-fixed-button {
174
562
  position: fixed !important;
175
563
  ${WIDGET_CONFIG.widgetPosition.side}: ${WIDGET_CONFIG.widgetPosition[WIDGET_CONFIG.widgetPosition.side]} !important;
176
564
  bottom: ${WIDGET_CONFIG.widgetPosition.bottom} !important;
177
565
  z-index: 9999;
566
+ background:${WIDGET_CONFIG.colors.primary};
567
+ padding:5px;
568
+ border-radius:100%;
178
569
  }
570
+
179
571
  #snn-accessibility-button {
180
572
  background: ${WIDGET_CONFIG.colors.primary};
181
573
  border: none;
@@ -188,78 +580,130 @@ const styles = `
188
580
  display: flex;
189
581
  justify-content: center;
190
582
  align-items: center;
583
+ border:solid 4px white;
191
584
  }
585
+
192
586
  #snn-accessibility-button:hover {
193
587
  transform: scale(${WIDGET_CONFIG.animation.hoverScale});
194
588
  }
589
+
195
590
  #snn-accessibility-button:focus {
196
- outline: 2px solid ${WIDGET_CONFIG.colors.textLight};
591
+ outline: 2px solid ${WIDGET_CONFIG.colors.secondary};
197
592
  outline-offset: 2px;
198
593
  }
594
+
199
595
  #snn-accessibility-button svg {
200
596
  width: ${WIDGET_CONFIG.button.iconSize};
201
597
  height: ${WIDGET_CONFIG.button.iconSize};
202
- fill: ${WIDGET_CONFIG.colors.textLight};
598
+ fill: ${WIDGET_CONFIG.colors.secondary};
203
599
  pointer-events: none;
204
600
  }
601
+
205
602
  #snn-accessibility-menu {
206
603
  position: fixed;
207
604
  top: 0;
208
605
  ${WIDGET_CONFIG.widgetPosition.side}: 0;
209
- width: ${WIDGET_CONFIG.widgetWidth};
606
+ max-width: ${WIDGET_CONFIG.widgetWidth};
607
+ width:100%;
210
608
  height: 100vh;
211
609
  overflow-y: auto;
212
- background-color: ${WIDGET_CONFIG.colors.secondary};
610
+ background-color: #e2e2e2;
213
611
  padding: 0;
214
612
  display: none;
215
613
  font-family: ${WIDGET_CONFIG.typography.fontFamily};
216
614
  z-index: 999999;
217
615
  scrollbar-width: thin;
616
+ line-height:1 !important;
218
617
  }
618
+
219
619
  .snn-accessibility-option {
220
620
  font-size: ${WIDGET_CONFIG.menu.fontSize};
221
621
  display: flex;
622
+ flex-direction: column;
222
623
  align-items: center;
223
- padding: ${WIDGET_CONFIG.menu.optionPadding};
624
+ justify-content: space-around;
625
+ padding: 5px;
224
626
  width: 100%;
225
- background-color: ${WIDGET_CONFIG.colors.border};
226
- color: ${WIDGET_CONFIG.colors.text};
227
- border: none;
627
+ background-color: ${WIDGET_CONFIG.colors.optionBg};
628
+ color: ${WIDGET_CONFIG.colors.optionText};
629
+ border: 3px solid ${WIDGET_CONFIG.colors.optionBg};
228
630
  cursor: pointer;
229
631
  border-radius: ${WIDGET_CONFIG.menu.borderRadius};
230
- transition: background-color ${WIDGET_CONFIG.animation.transition};
632
+ transition: background-color ${WIDGET_CONFIG.animation.transition}, border-color ${WIDGET_CONFIG.animation.transition};
231
633
  line-height: ${WIDGET_CONFIG.typography.lineHeight} !important;
634
+ gap: 5px;
635
+ min-height: 105px;
232
636
  }
637
+
233
638
  .snn-accessibility-option:hover {
234
- background-color: ${WIDGET_CONFIG.colors.borderHover};
639
+ border-color: ${WIDGET_CONFIG.colors.primary};
235
640
  }
641
+
236
642
  .snn-accessibility-option.active {
237
- background-color: ${WIDGET_CONFIG.colors.primary};
238
- color: ${WIDGET_CONFIG.colors.textLight};
643
+ border-color: ${WIDGET_CONFIG.colors.primary};
644
+ }
645
+
646
+ .snn-accessibility-option:disabled {
647
+ opacity: 0.5;
648
+ cursor: not-allowed;
239
649
  }
650
+
240
651
  .snn-icon {
241
- margin-right: 12px;
242
652
  width: ${WIDGET_CONFIG.button.iconSize};
243
653
  height: ${WIDGET_CONFIG.button.iconSize};
654
+ fill: ${WIDGET_CONFIG.colors.optionIcon};
655
+ flex-shrink: 0;
244
656
  }
657
+
245
658
  .snn-icon svg {
246
659
  width: 100%;
247
660
  height: 100%;
248
661
  fill: currentColor;
249
662
  }
250
- .snn-close {
663
+
664
+ .snn-button-text {
665
+ text-align: center;
666
+ line-height: 1.2;
667
+ font-size:16px;
668
+ font-weight: 600;
669
+ }
670
+
671
+ .snn-option-steps {
672
+ display: flex;
673
+ gap: 5px;
674
+ align-items: center;
675
+ justify-content: center;
676
+ margin-top: 5px;
677
+ }
678
+
679
+ .snn-option-step {
680
+ width: 30px;
681
+ height: 6px;
682
+ border-radius: 3px;
683
+ background-color: #d0d0d0;
684
+ transition: background-color ${WIDGET_CONFIG.animation.transition};
685
+ }
686
+
687
+ .snn-option-step.active {
688
+ background-color: ${WIDGET_CONFIG.colors.primary};
689
+ }
690
+
691
+ .snn-close, .snn-reset-button {
251
692
  background: none;
252
693
  border: none;
253
694
  font-size: ${WIDGET_CONFIG.menu.closeButtonSize};
254
- color: ${WIDGET_CONFIG.colors.textLight};
695
+ color: ${WIDGET_CONFIG.colors.secondary};
255
696
  cursor: pointer;
256
- margin-left: auto;
257
697
  line-height: ${WIDGET_CONFIG.typography.lineHeight};
258
698
  border-radius: ${WIDGET_CONFIG.button.borderRadius};
259
699
  width: ${WIDGET_CONFIG.menu.closeButtonSize};
260
700
  height: ${WIDGET_CONFIG.menu.closeButtonSize};
261
701
  position: relative;
702
+ display: flex;
703
+ align-items: center;
704
+ justify-content: center;
262
705
  }
706
+
263
707
  .snn-close::before {
264
708
  content: '×';
265
709
  position: absolute;
@@ -269,50 +713,94 @@ const styles = `
269
713
  font-size: ${WIDGET_CONFIG.menu.closeButtonSize};
270
714
  line-height: 1;
271
715
  }
272
- .snn-close:focus {
273
- outline: solid 2px ${WIDGET_CONFIG.colors.textLight};
716
+
717
+ .snn-reset-button svg {
718
+ width: 22px;
719
+ height: 22px;
720
+ fill: ${WIDGET_CONFIG.colors.secondary};
721
+ }
722
+
723
+ .snn-close:focus, .snn-reset-button:focus {
724
+ outline: solid 2px ${WIDGET_CONFIG.colors.secondary};
725
+ }
726
+
727
+ .snn-close:hover, .snn-reset-button:hover {
728
+ color: ${WIDGET_CONFIG.colors.secondary};
729
+ background: rgba(255, 255, 255, 0.2);
730
+ }
731
+
732
+ /* Tooltip styles */
733
+ .snn-tooltip {
734
+ position: absolute;
735
+ bottom: -35px;
736
+ left: 50%;
737
+ transform: translateX(-50%);
738
+ background-color: rgba(0, 0, 0, 0.8);
739
+ color: white;
740
+ padding: 6px 10px;
741
+ border-radius: 4px;
742
+ font-size: 12px;
743
+ white-space: nowrap;
744
+ pointer-events: none;
745
+ opacity: 0;
746
+ transition: opacity 0.2s;
747
+ z-index: 1000;
748
+ }
749
+
750
+ .snn-tooltip::before {
751
+ content: '';
752
+ position: absolute;
753
+ top: -4px;
754
+ left: 50%;
755
+ transform: translateX(-50%);
756
+ width: 0;
757
+ height: 0;
758
+ border-left: 5px solid transparent;
759
+ border-right: 5px solid transparent;
760
+ border-bottom: 5px solid rgba(0, 0, 0, 0.8);
274
761
  }
275
- .snn-close:hover {
276
- color: ${WIDGET_CONFIG.colors.text};
762
+
763
+ .snn-close:hover .snn-tooltip,
764
+ .snn-close:focus .snn-tooltip,
765
+ .snn-reset-button:hover .snn-tooltip,
766
+ .snn-reset-button:focus .snn-tooltip {
767
+ opacity: 1;
277
768
  }
769
+
278
770
  .snn-header {
279
771
  display: flex;
280
772
  align-items: center;
281
- margin-bottom: 20px;
282
773
  padding: 10px;
283
- background: #000000;
774
+ background: ${WIDGET_CONFIG.colors.primary};
284
775
  height: ${WIDGET_CONFIG.menu.headerHeight};
285
776
  position: sticky;
286
777
  top: 0;
287
778
  z-index: 10;
288
779
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
780
+ gap: 8px;
289
781
  }
290
782
 
291
783
  .snn-content {
292
- padding: 0 10px 10px 10px;
784
+ padding: 20px 20px 0px 20px;
293
785
  }
294
786
 
295
- .snn-reset-button {
296
- font-size: ${WIDGET_CONFIG.menu.fontSize};
297
- display: flex;
298
- align-items: center;
299
- justify-content: center;
300
- margin-bottom: 10px;
301
- padding: ${WIDGET_CONFIG.menu.optionPadding};
787
+ .snn-language-selector {
302
788
  width: 100%;
303
- background-color: #343434;
304
- color: ${WIDGET_CONFIG.colors.textLight};
789
+ background: white;
790
+ color: black;
305
791
  border: none;
792
+ padding: 14px;
793
+ font-size: 16px;
794
+ font-family: ${WIDGET_CONFIG.typography.fontFamily};
795
+ border-radius: 5px;
796
+ margin-bottom: 20px;
306
797
  cursor: pointer;
307
- border-radius: ${WIDGET_CONFIG.menu.borderRadius};
308
- transition: background-color ${WIDGET_CONFIG.animation.transition};
309
- line-height: ${WIDGET_CONFIG.typography.lineHeight} !important;
310
- font-weight: 500;
311
- gap: 8px;
798
+ outline: none;
312
799
  }
313
800
 
314
- .snn-reset-button:hover {
315
- background-color: #cc3333;
801
+ .snn-language-selector:focus {
802
+ outline: 2px solid ${WIDGET_CONFIG.colors.primary};
803
+ outline-offset: 2px;
316
804
  }
317
805
 
318
806
  .snn-options-grid {
@@ -322,53 +810,53 @@ const styles = `
322
810
  margin-bottom: 20px;
323
811
  }
324
812
 
325
-
326
813
  .snn-title {
327
814
  margin: 0;
328
815
  font-size: ${WIDGET_CONFIG.menu.titleFontSize};
329
- color: ${WIDGET_CONFIG.colors.textLight};
816
+ color: ${WIDGET_CONFIG.colors.secondary};
330
817
  line-height: ${WIDGET_CONFIG.typography.lineHeight} !important;
331
818
  margin-left: 5px;
332
819
  font-weight: ${WIDGET_CONFIG.typography.titleFontWeight};
820
+ flex: 1;
821
+ letter-spacing: 1px !important;
822
+ word-spacing: 2px !important;
823
+ text-align: left;
333
824
  }
334
- /* Accessibility feature styles */
825
+ `;
826
+
827
+ // Page accessibility styles (will go in main document - these affect the page, NOT the widget)
828
+ const pageStyles = `
829
+ /* High Contrast Modes */
335
830
  .snn-high-contrast-medium {
336
- filter: contrast(1.3) !important;
831
+ filter: none !important;
337
832
  }
338
- .snn-high-contrast-medium *{
833
+ .snn-high-contrast-medium *:not(#snn-accessibility-widget-container):not(#snn-accessibility-widget-container *) {
339
834
  filter: contrast(1.3) !important;
340
835
  }
341
- .snn-high-contrast-medium #snn-accessibility-menu{
342
- filter: contrast(0.8) !important;
343
- }
344
836
 
345
837
  .snn-high-contrast-high {
346
838
  background-color: #000 !important;
347
839
  color: #fff !important;
348
- filter: contrast(1.5) !important;
840
+ filter: none !important;
349
841
  }
350
- .snn-high-contrast-high *{
842
+ .snn-high-contrast-high *:not(#snn-accessibility-widget-container):not(#snn-accessibility-widget-container *) {
351
843
  background-color: #000 !important;
352
844
  color: #fff !important;
353
845
  filter: contrast(1.5) !important;
354
846
  }
355
- .snn-high-contrast-high #snn-accessibility-menu{
356
- filter: contrast(0.7) !important;
357
- }
358
847
 
359
848
  .snn-high-contrast-ultra {
360
849
  background-color: #000 !important;
361
850
  color: #ffff00 !important;
362
- filter: contrast(2.0) !important;
851
+ filter: none !important;
363
852
  }
364
- .snn-high-contrast-ultra *{
853
+ .snn-high-contrast-ultra *:not(#snn-accessibility-widget-container):not(#snn-accessibility-widget-container *) {
365
854
  background-color: #000 !important;
366
855
  color: #ffff00 !important;
367
856
  filter: contrast(2.0) !important;
368
857
  }
369
- .snn-high-contrast-ultra #snn-accessibility-menu{
370
- filter: contrast(0.6) !important;
371
- }
858
+
859
+ /* Text Size */
372
860
  .snn-bigger-text-medium * {
373
861
  font-size: 20px !important;
374
862
  }
@@ -378,32 +866,66 @@ const styles = `
378
866
  .snn-bigger-text-xlarge * {
379
867
  font-size: 28px !important;
380
868
  }
381
- .snn-text-spacing *:not(#snn-accessibility-menu *, #snn-accessibility-fixed-button *, #snn-accessibility-button *, .snn-accessibility-option *) {
382
- letter-spacing: 0.2em !important;
383
- word-spacing: 0.3em !important;
869
+
870
+ /* Text Spacing - 3 Options */
871
+ .snn-text-spacing-light * {
872
+ letter-spacing: 0.1em !important;
873
+ word-spacing: 0.5em !important;
874
+ }
875
+ .snn-text-spacing-medium * {
876
+ letter-spacing: 0.15em !important;
877
+ word-spacing: 1em !important;
878
+ }
879
+ .snn-text-spacing-heavy * {
880
+ letter-spacing: 0.25em !important;
881
+ word-spacing: 2em !important;
384
882
  }
883
+
884
+ /* Pause Animations (Enhanced to include Reduced Motion features) */
385
885
  .snn-pause-animations * {
386
886
  animation: none !important;
387
887
  transition: none !important;
388
888
  }
889
+ .snn-pause-animations *::before {
890
+ animation: none !important;
891
+ transition: none !important;
892
+ }
893
+ .snn-pause-animations *::after {
894
+ animation: none !important;
895
+ transition: none !important;
896
+ }
897
+
898
+ /* Dyslexia Font */
389
899
  .snn-dyslexia-font {
390
- font-family: 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', Brush Script MT, fantasy !important;
900
+ font-family: 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important;
391
901
  }
392
902
  .snn-dyslexia-font * {
393
- font-family: 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', Brush Script MT, fantasy !important;
903
+ font-family: 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important;
904
+ }
905
+
906
+ /* Line Height - 3 Options */
907
+ .snn-line-height-2em * {
908
+ line-height: 2 !important;
394
909
  }
395
- .snn-line-height *:not(#snn-accessibility-menu *, #snn-accessibility-fixed-button *, #snn-accessibility-button *, .snn-accessibility-option *) {
396
- line-height: 2.5 !important;
910
+ .snn-line-height-3em * {
911
+ line-height: 3 !important;
397
912
  }
398
- .snn-text-align-left *:not(#snn-accessibility-menu *, #snn-accessibility-fixed-button *, #snn-accessibility-button *, .snn-accessibility-option *) {
913
+ .snn-line-height-4em * {
914
+ line-height: 4 !important;
915
+ }
916
+
917
+ /* Text Alignment */
918
+ .snn-text-align-left * {
399
919
  text-align: left !important;
400
920
  }
401
- .snn-text-align-center *:not(#snn-accessibility-menu *, #snn-accessibility-fixed-button *, #snn-accessibility-button *, .snn-accessibility-option *) {
921
+ .snn-text-align-center * {
402
922
  text-align: center !important;
403
923
  }
404
- .snn-text-align-right *:not(#snn-accessibility-menu *, #snn-accessibility-fixed-button *, #snn-accessibility-button *, .snn-accessibility-option *) {
924
+ .snn-text-align-right * {
405
925
  text-align: right !important;
406
926
  }
927
+
928
+ /* Bigger Cursor */
407
929
  .snn-bigger-cursor {
408
930
  cursor: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNzIiIHZpZXdCb3g9IjAgMCA0OCA3MiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNCAyVjcwTDIwIDU0SDM2TDQgMloiIGZpbGw9IiMwMDAiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSI0Ii8+PC9zdmc+'), auto !important;
409
931
  }
@@ -433,27 +955,56 @@ const styles = `
433
955
 
434
956
  /* Color Filters */
435
957
  .snn-filter-protanopia {
958
+ filter: none !important;
959
+ }
960
+ .snn-filter-protanopia body > *:not(#snn-accessibility-widget-container) {
436
961
  filter: url('#protanopia-filter') !important;
437
962
  }
438
963
  .snn-filter-deuteranopia {
964
+ filter: none !important;
965
+ }
966
+ .snn-filter-deuteranopia body > *:not(#snn-accessibility-widget-container) {
439
967
  filter: url('#deuteranopia-filter') !important;
440
968
  }
441
969
  .snn-filter-tritanopia {
970
+ filter: none !important;
971
+ }
972
+ .snn-filter-tritanopia body > *:not(#snn-accessibility-widget-container) {
442
973
  filter: url('#tritanopia-filter') !important;
443
974
  }
444
975
  .snn-filter-grayscale {
976
+ filter: none !important;
977
+ }
978
+ .snn-filter-grayscale body > *:not(#snn-accessibility-widget-container) {
445
979
  filter: grayscale(100%) !important;
446
980
  }
447
981
 
448
- /* Reduced Motion */
449
- .snn-reduced-motion * {
450
- animation: none !important;
451
- transition: none !important;
982
+ /* Saturation Filters */
983
+ .snn-saturation-low {
984
+ filter: none !important;
452
985
  }
453
- .snn-reduced-motion *::before,
454
- .snn-reduced-motion *::after {
455
- animation: none !important;
456
- transition: none !important;
986
+ .snn-saturation-low body > *:not(#snn-accessibility-widget-container) {
987
+ filter: saturate(0.5) !important;
988
+ }
989
+ .snn-saturation-high {
990
+ filter: none !important;
991
+ }
992
+ .snn-saturation-high body > *:not(#snn-accessibility-widget-container) {
993
+ filter: saturate(10) !important;
994
+ }
995
+ .snn-saturation-none {
996
+ filter: none !important;
997
+ }
998
+ .snn-saturation-none body > *:not(#snn-accessibility-widget-container) {
999
+ filter: grayscale(100%) saturate(0) !important;
1000
+ }
1001
+
1002
+ /* Protect widget container from page styles */
1003
+ #snn-accessibility-widget-container,
1004
+ #snn-accessibility-widget-container * {
1005
+ filter: none !important;
1006
+ background-color: initial !important;
1007
+ color: initial !important;
457
1008
  }
458
1009
  `;
459
1010
 
@@ -461,40 +1012,44 @@ const styles = `
461
1012
  // SVG ICONS
462
1013
  // ===========================================
463
1014
 
464
- // SVG icons
465
1015
  const icons = {
466
- buttonsvg: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="" height="" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" viewBox="0 0 2713 2713" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.fil1 {fill:${WIDGET_CONFIG.colors.primary}} .fil0 {fill:white}]]></style></defs><g id="Layer_x0020_1"><metadata id="CorelCorpID_0Corel-Layer"/><g id="_275060008"><circle class="fil0" cx="1356" cy="1356" r="1356"/><path class="fil1" d="M1305 315c-143,32 -237,147 -205,319 25,141 143,240 312,213 131,-21 237,-160 206,-324 -23,-125 -156,-243 -313,-208zm-150 1699l0 -340c1,-75 5,-367 1,-417 -9,-113 -93,-177 -174,-250 -19,-17 -33,-31 -53,-50 -19,-18 -35,-30 -54,-49 -19,-18 -34,-29 -53,-50 -38,-40 -162,-118 -98,-188 60,-65 124,34 188,86l111 99c11,10 17,13 27,25 9,12 16,18 28,28 35,30 72,64 125,85 122,50 214,44 334,-14 71,-34 103,-68 150,-113 9,-9 17,-15 27,-24 20,-18 39,-34 56,-51l108 -103c19,-18 29,-36 65,-39 33,-3 58,10 67,36 11,30 3,63 -13,83l-273 254c-40,31 -76,64 -109,98 -38,41 -54,80 -55,153 -3,243 -1,489 0,733 0,3 0,5 0,8 0,0 0,0 0,0 0,184 149,333 333,333 61,0 118,-17 167,-45 24,-18 48,-36 67,-51 39,-32 140,-145 171,-186 11,-16 19,-26 30,-42 104,-151 178,-317 209,-505 39,-242 -12,-506 -119,-712 -36,-69 -69,-123 -108,-178 -12,-15 -20,-24 -32,-39 -28,-36 -67,-84 -99,-115 -69,-66 -76,-68 -158,-129 -53,-39 -113,-70 -182,-103 -140,-67 -297,-100 -472,-102 -180,-2 -322,37 -472,97 -55,22 -93,42 -143,72 -55,33 -73,43 -127,87 -47,38 -70,60 -111,104 -6,6 -12,10 -18,17 -7,7 -9,13 -16,20 -8,9 -10,8 -17,18 -80,101 -91,116 -158,235 -64,113 -121,286 -136,435 -18,190 1,329 58,498 46,134 132,283 204,367 13,15 21,26 32,40 34,43 103,105 146,139 7,6 14,11 22,17 54,38 120,61 192,61 183,0 332,-149 332,-333l0 0z"/></g></g></svg>`,
467
- highContrast: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><circle cx="32" cy="32" r="30" fill="#000"/><path d="M32 2a30 30 0 000 60V2z" fill="#fff"/></svg>`,
468
- biggerText: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M32 8L8 56h12l6-12h24l6 12h12L32 8zm-6 36L32 20l6 24H26z"/></svg>`,
469
- textSpacing: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M10 16h44v4H10zm0 12h44v4H10zm0 12h44v4H10zm0 12h44v4H10z"/></svg>`,
470
- pauseAnimations: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect x="16" y="12" width="10" height="40"/><rect x="38" y="12" width="10" height="40"/></svg>`,
1016
+ buttonsvg: `<svg xmlns="http://www.w3.org/2000/svg" style="fill:white;" viewBox="0 0 24 24" width="30px" height="30px"><path d="M0 0h24v24H0V0z" fill="none"></path><path d="M20.5 6c-2.61.7-5.67 1-8.5 1s-5.89-.3-8.5-1L3 8c1.86.5 4 .83 6 1v13h2v-6h2v6h2V9c2-.17 4.14-.5 6-1l-.5-2zM12 6c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"></path></svg>`,
1017
+ highContrast: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" version="1.2" viewBox="0 0 35 35"><path fill="currentColor" fill-rule="evenodd" d="M1.89998 15.6285c0-7.58203 6.14649-13.72852 13.72852-13.72852 7.5821 0 13.7286 6.14649 13.7286 13.72852 0 .6081-.0395 1.2069-.1161 1.794.5933.2913 1.1478.6497 1.6534 1.0654.1725-.9268.2627-1.8825.2627-2.8594 0-8.57615-6.9524-15.5285244-15.5286-15.5285244C7.05235.0999756.0999756 7.05235.0999756 15.6285c0 8.5762 6.9523744 15.5286 15.5285244 15.5286 1.2241 0 2.415-.1416 3.5574-.4093-.4388-.4866-.8222-1.0242-1.1402-1.6028-.7847.1394-1.5924.2121-2.4172.2121-7.58203 0-13.72852-6.1465-13.72852-13.7286Z" clip-rule="evenodd"/><path fill="currentColor" fill-rule="evenodd" d="M2.35 15.6286C2.35 8.29502 8.29502 2.35 15.6286 2.35c7.3335 0 13.2785 5.94502 13.2785 13.2786 0 .5408-.0323 1.0741-.0951 1.5979.444.1881.8687.4128 1.2703.6703.1151-.7392.1748-1.4967.1748-2.2682C30.2571 7.54943 23.7077 1 15.6286 1 7.54943 1 15.6286 1 7.54943 1 15.6286c0 8.0791 6.54943 14.6285 14.6286 14.6285 1.0033 0 1.9831-.101 2.9297-.2934-.276-.3898-.52-.8038-.7282-1.2382-.716.1195-1.4515.1816-2.2015.1816-7.33358 0-13.2786-5.945-13.2786-13.2785Z" clip-rule="evenodd"/><path fill="currentColor" fill-rule="evenodd" d="M15.6286 1C7.54943 1 1 7.54943 1 15.6286c0 8.0791 6.54943 14.6285 14.6286 14.6285" clip-rule="evenodd"/><path stroke="currentColor" stroke-width="1.8" d="M15.6286 1C7.54943 1 1 7.54943 1 15.6286c0 8.0791 6.54943 14.6285 14.6286 14.6285"/><path fill="currentColor" fill-rule="evenodd" d="M22.8729 25.114c0-1.3811 1.0901-2.5007 2.4359-2.5007 1.3459 0 2.436 1.1196 2.436 2.5007 0 1.38-1.0901 2.4997-2.436 2.4997-1.3458 0-2.4359-1.1197-2.4359-2.4997Zm7.2258-2.0373c-.0899-.2248-.071-.4785.0512-.6875l.912-1.5598c.0898-.1532.0668-.3504-.0574-.4779l-1.0556-1.0832c-.1232-.1264-.3153-.1511-.4657-.0589l-1.5225.9374c-.201.1237-.4495.1427-.667.051-.2181-.092-.3797-.2819-.4358-.5118l-.4329-1.7763c-.0428-.1735-.1953-.2957-.3696-.2957h-1.4931c-.1744 0-.3268.1222-.3696.2957l-.433 1.7763c-.056.2299-.2177.4198-.4357.5118-.2176.0917-.466.0727-.6671-.051l-1.5225-.9374c-.1503-.0922-.3424-.0675-.4656.0589l-1.0556 1.0832c-.1243.1275-.1473.3247-.0575.4779l.9121 1.5598c.1222.209.1411.4627.0511.6875-.0895.2239-.2806.3916-.5142.4514l-1.7165.4395c-.1692.0439-.2882.2003-.2882.3803v1.5311c0 .18.119.3364.2882.3804l1.7165.4394c.2336.0599.4247.2276.5142.4515.09.2247.0711.4785-.0511.6874l-.9121 1.5599c-.0898.1532-.0668.3503.0575.4778l1.0556 1.0833c.1232.1264.3153.151.4656.0589l1.5225-.9374c.2011-.1238.4495-.1428.6671-.051.218.092.3797.2818.4357.5118l.433 1.7762c.0428.1736.1952.2968.3696.2968h1.4931c.1743 0 .3268-.1232.3696-.2968l.4329-1.7762c.0561-.23.2177-.4198.4358-.5118.2175-.0918.466-.0728.667.051l1.5225.9374c.1504.0921.3425.0675.4657-.0589l1.0556-1.0833c.1242-.1275.1472-.3246.0574-.4778l-.912-1.5599c-.1222-.2089-.1411-.4627-.0512-.6874.0896-.2239.2806-.3916.5142-.4515l1.7166-.4394c.1691-.044.2881-.2004.2881-.3804v-1.5311c0-.18-.119-.3364-.2881-.3803l-1.7166-.4395c-.2336-.0598-.4246-.2275-.5142-.4514Z" clip-rule="evenodd"/></svg>`,
1018
+ biggerText: `<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 36 23"><g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path stroke-linejoin="round" d="M26.58 21.3225806V1m-7.92 4.06451613V1H34.5v4.06451613"/><path d="M22.62 21.3225806h7.92"/><path stroke-linejoin="round" d="M6.78 18.6129032V5.06451613M1.5 7.77419355V5.06451613h10.56v2.70967742"/><path d="M4.14 18.6129032h5.28"/></g></svg>`,
1019
+ textSpacing: `<svg xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 15 15" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.55293 0.999969C4.75295 0.999969 4.93372 1.11917 5.0125 1.30301L8.01106 8.29982C8.11984 8.55363 8.00226 8.84757 7.74844 8.95635C7.49463 9.06512 7.20069 8.94754 7.09191 8.69373L6.11613 6.41685H2.98973L2.01395 8.69373C1.90517 8.94754 1.61123 9.06512 1.35742 8.95635C1.1036 8.84757 0.986023 8.55363 1.0948 8.29982L4.09336 1.30301C4.17214 1.11917 4.35291 0.999969 4.55293 0.999969ZM4.55293 2.76929L5.75186 5.56685H3.354L4.55293 2.76929ZM11.0562 9.00214C11.2617 9.00214 11.4463 8.87633 11.5215 8.68502L14.2733 1.68299C14.3743 1.42598 14.2478 1.13575 13.9908 1.03475C13.7338 0.933747 13.4436 1.06021 13.3426 1.31722L11.0562 7.13514L8.76973 1.31722C8.66873 1.06021 8.3785 0.933747 8.1215 1.03475C7.86449 1.13575 7.73802 1.42598 7.83902 1.68299L10.5908 8.68502C10.666 8.87633 10.8506 9.00214 11.0562 9.00214ZM14.9537 12.4999C14.9537 12.606 14.9115 12.7077 14.8365 12.7828L12.8365 14.7828C12.6803 14.939 12.4271 14.939 12.2708 14.7828C12.1146 14.6265 12.1146 14.3733 12.2708 14.2171L13.588 12.8999H1.51937L2.83653 14.2171C2.99274 14.3733 2.99274 14.6265 2.83653 14.7828C2.68032 14.939 2.42705 14.939 2.27084 14.7828L0.270843 12.7828C0.195828 12.7077 0.153687 12.606 0.153687 12.4999C0.153687 12.3938 0.195828 12.2921 0.270843 12.2171L2.27084 10.2171C2.42705 10.0609 2.68032 10.0609 2.83653 10.2171C2.99274 10.3733 2.99274 10.6265 2.83653 10.7828L1.51937 12.0999L13.588 12.0999L12.2708 10.7828C12.1146 10.6265 12.1146 10.3733 12.2708 10.2171C12.4271 10.0609 12.6803 10.0609 12.8365 10.2171L14.8365 12.2171C14.9115 12.2921 14.9537 12.3938 14.9537 12.4999Z" fill="#000000"/></svg>`,
1020
+ pauseAnimations: `<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 37 36"><g fill="none" fill-rule="evenodd"><path fill="currentColor" d="M15.8087111 23.6666667h-1.2702778c-.4429444 0-.8018333-.3598334-.8018333-.8027778v-9.7277778c0-.4429444.3588889-.8027778.8018333-.8027778h1.2702778c.4429445 0 .8027778.3598334.8027778.8027778v9.7277778c0 .4429444-.3598333.8027778-.8027778.8027778m6.6525722 0h-1.2702777c-.442 0-.8018334-.3598334-.8018334-.8027778v-9.7277778c0-.4429444.3598334-.8027778.8018334-.8027778h1.2702777c.4438889 0 .8027778.3598334.8027778.8027778v9.7277778c0 .4429444-.3588889.8027778-.8027778.8027778"/><path stroke="currentColor" stroke-linecap="round" stroke-width="1.88888889" d="M18.5 4.77777778V1m0 34v-3.7777778M31.7222222 18H35.5m-34 0h3.77777778m3.87278889-9.34943333L6.47873333 5.97967778M30.5204167 30.0204167l-2.6708889-2.6708889m-.0000945-18.69896113 2.6708889-2.67088889M6.47911111 30.0204167l2.67183333-2.6708889M23.5542889 5.78219444l1.4440555-3.49066666M12.0013722 33.7087556l1.4440556-3.4906667m17.2723778-7.1638 3.4906666 1.4440555M2.79124444 11.5013722l3.49066667 1.4440556m7.15274999-7.15860558L11.9877722 2.2971m13.0246445 31.4061778-1.4468889-3.4897222m7.14765-17.2788945L34.2029 11.4877722M2.79672222 24.5124167l3.48972222-1.4468889"/></g></svg>`,
471
1021
  hideImages: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M32 12C16 12 4 32 4 32s12 20 28 20 28-20 28-20S48 12 32 12zm0 32a12 12 0 1112-12 12 12 0 01-12 12z"/><circle cx="32" cy="32" r="8"/></svg>`,
472
- dyslexiaFont: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M12 8v48h12a16 16 0 000-32h-8v-8h16V8H12zm12 24a8 8 0 010 16h-4V32h4zM40 8v48h12V8H40z"/></svg>`,
473
- biggerCursor: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M12 4v56l16-16h24L12 4z"/></svg>`,
474
- lineHeight: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M16 16h32v4H16zm0 12h32v4H16zm0 12h32v4H16zm0 12h32v4H16zM8 8l8 8-8 8V8zm0 32l8 8-8 8V40z"/></svg>`,
1022
+ dyslexiaFont: `<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 31 22"><path fill="currentColor" fill-rule="evenodd" d="M.5 22V1.0083333h7.2421899c6.8051611 0 11.6124768 4.3388889 11.6124768 10.4805556C19.3546667 17.6305556 14.547351 22 7.7421899 22H.5Zm2.4348742-4.31h4.8073157c5.3692097 0 9.1463863-2.8616703 9.1463863-7.27 0-4.3807776-3.7771766-7.2422222-9.1463863-7.2422222H2.9348742V17.69ZM26.2735913 4.0333333l.0114609 2.1694445h4.0126191V8.25h-4.001719L26.77 22h-3.535416L23.78 8.25h-2.4238344V6.2027778h2.55923l.0751088-2.1694445C24.0706908 1.6805556 25.6007488 0 27.697782 0 28.6896221 0 29.677687.3666667 30.5 1.0083333l-.9627285 1.6805556c-.3479788-.3666667-.9515992-.6416667-1.627768-.6416667-.8819593 0-1.6420082.825-1.6359122 1.9861111Z"/></svg>`,
1023
+ biggerCursor: `<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 27 27"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15.9983464 11.5517813 9.5269972 9.52699721-4.4465655 4.44656549-9.5269972-9.52699717-4.05145413 9.06403815L1 1.0000004l24.0623846 6.5003268z"/></svg>`,
1024
+ lineHeight: `<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 47 25"><g fill="none" fill-rule="evenodd"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M3.99999962 2.71042226V22.7104223"/><path fill="currentColor" d="m.16814235 20.5270412 3.44487862 4.2104072c.17486379.2137224.48987514.2452235.70359754.0703597a.4999988.4999988 0 0 0 .07035976-.0703597l3.44487862-4.2104072c.17486378-.2137225.14336265-.5287338-.07035976-.7035976-.08933106-.073089-.20119771-.1130213-.31661889-.1130213H.555121c-.27614238 0-.5.2238576-.5.5 0 .1154211.0399323.2272878.11302135.3166189Zm0-161332381L3.61302097.18339592c.17486379-.21372241.48987514-.24522355.70359754-.07035976a.49999975.49999975 0 0 1 .07035976.07035976l3.44487862 4.2104072c.17486378.2137224.14336265.52873375-.07035976.70359754-.08933106.07308905-.20119771.11302135-.31661889.11302135H.555121c-.27614237 0-.5-.22385762-.5-.5 0-.11542118.0399323-.22728783.11302135-.3166189Z"/><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.4999996 1.71042226h30m-30 7h30m-30 7.00000004h30m-30 7h24"/></g></svg>`,
475
1025
  textAlign: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M10 16h44v4H10zm0 12h44v4H10zm0 12h44v4H10zm0 12h44v4H10z"/></svg>`,
476
1026
  screenReader: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M16 24 L24 24 L32 16 L32 48 L24 40 L16 40 Z" fill="#333" stroke="#555" stroke-width="2"/><path d="M36 20 C42 24, 42 40, 36 44" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"/><path d="M36 12 C48 24, 48 40, 36 52" fill="none" stroke="#555" stroke-width="2" stroke-linecap="round"/><rect x="28" y="48" width="8" height="8" fill="#ccc"/></svg>`,
477
- resetAll: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/><path d="M8 12l4-4 4 4-1.41 1.41L12 10.83l-2.59 2.58z" transform="rotate(45 12 12)"/></svg>`,
1027
+ resetAll: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 17" width="100%" height="100%"><g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-width="1.84"><path d="M16.20106 8c0 .9667-.189683 1.8872-.5324673 2.7251-.3427843.8372-.8386698 1.5911-1.4517524 2.2246-.6130825.6335-1.3426846 1.1459-2.152902 1.5001-.8108948.3542-1.70172746.5502-2.6372711.5502-.93554365 0-1.8263763-.196-2.63727112-.5502-.81021738-.3542-1.53981948-.8666-2.15290203-1.5001M2.6522744 8c0-.9667.189683-1.8872.53246728-2.7251.34278427-.8372.83866982-1.5911 1.45175237-2.2246.61308255-.6335 1.34268465-1.1459 2.15290203-1.5001C7.6002909 1.196 8.49112355 1 9.4266672 1c.93554364 0 1.8263763.196 2.6372711.5502.8102174.3542 1.5398195.8666 2.152902 1.5001"></path><path stroke-linejoin="round" d="m4.92576062 6.96092-2.48958935 1.484L1 5.87242m13.0125924 2.93832 2.3886509-1.652L18 9.62694"></path></g></svg>`,
478
1028
  voiceControl: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M32 44a12 12 0 0012-12V20a12 12 0 10-24 0v12a12 12 0 0012 12z" fill="#333"/><path d="M20 32h24v4H20z" fill="#555"/><path d="M32 48v8" stroke="#555" stroke-width="4" stroke-linecap="round"/></svg>`,
479
1029
  fontSelection: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><text x="32" y="40" font-family="serif" font-size="24" text-anchor="middle" fill="#333">Aa</text><path d="M8 48h48v2H8z"/></svg>`,
480
1030
  colorFilter: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><circle cx="32" cy="32" r="24" fill="none" stroke="#333" stroke-width="2"/><path d="M32 8a24 24 0 000 48V8z" fill="#f00" opacity="0.3"/><path d="M32 8a24 24 0 000 48" fill="none" stroke="#333" stroke-width="2" stroke-dasharray="4,2"/></svg>`,
1031
+ saturation: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><circle cx="20" cy="32" r="12" fill="#ff0000" opacity="0.7"/><circle cx="32" cy="32" r="12" fill="#00ff00" opacity="0.7"/><circle cx="44" cy="32" r="12" fill="#0000ff" opacity="0.7"/></svg>`,
481
1032
  reducedMotion: `<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect x="16" y="24" width="8" height="16" fill="#333"/><rect x="28" y="24" width="8" height="16" fill="#333"/><rect x="40" y="24" width="8" height="16" fill="#333"/></svg>`,
482
1033
  };
483
1034
 
484
1035
  // ===========================================
485
- // CORE UTILITY FUNCTIONS
1036
+ // SHADOW DOM SETUP
486
1037
  // ===========================================
487
1038
 
488
- // Inject styles and SVG filters into the document
489
- function injectStyles() {
1039
+ let shadowRoot = null;
1040
+
1041
+ // Inject styles into the page (NOT the widget)
1042
+ function injectPageStyles() {
490
1043
  const styleSheet = document.createElement('style');
491
- styleSheet.innerText = styles;
1044
+ styleSheet.innerText = pageStyles;
1045
+ styleSheet.id = 'snn-accessibility-page-styles';
492
1046
  document.head.appendChild(styleSheet);
493
-
494
- // Add SVG color blindness filters
1047
+
1048
+ // Add SVG color blindness filters to main document
495
1049
  const svgFilters = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
496
1050
  svgFilters.style.position = 'absolute';
497
1051
  svgFilters.style.width = '0';
1052
+ svgFilters.setAttribute('class', 'snn-accessibility-filters');
498
1053
  svgFilters.style.height = '0';
499
1054
  svgFilters.innerHTML = `
500
1055
  <defs>
@@ -512,8 +1067,25 @@ function injectStyles() {
512
1067
  document.body.appendChild(svgFilters);
513
1068
  }
514
1069
 
1070
+ // Create shadow DOM container
1071
+ function createShadowContainer() {
1072
+ const container = document.createElement('div');
1073
+ container.id = 'snn-accessibility-widget-container';
1074
+ document.body.appendChild(container);
1075
+
1076
+ // Create shadow root
1077
+ shadowRoot = container.attachShadow({ mode: 'open' });
1078
+
1079
+ // Add widget styles to shadow DOM
1080
+ const styleElement = document.createElement('style');
1081
+ styleElement.textContent = widgetStyles;
1082
+ shadowRoot.appendChild(styleElement);
1083
+
1084
+ return shadowRoot;
1085
+ }
1086
+
515
1087
  // ===========================================
516
- // PERFORMANCE OPTIMIZATION
1088
+ // CORE UTILITY FUNCTIONS
517
1089
  // ===========================================
518
1090
 
519
1091
  // Cache for DOM elements to improve performance
@@ -522,7 +1094,7 @@ const domCache = {
522
1094
  documentElement: document.documentElement,
523
1095
  images: null,
524
1096
  lastImageUpdate: 0,
525
- getImages: function() {
1097
+ getImages: function () {
526
1098
  const now = Date.now();
527
1099
  if (!this.images || now - this.lastImageUpdate > 5000) {
528
1100
  this.images = document.querySelectorAll('img');
@@ -537,13 +1109,10 @@ function applySettings() {
537
1109
  const settings = [
538
1110
  { key: 'biggerCursor', className: 'snn-bigger-cursor' },
539
1111
  { key: 'biggerText', className: 'snn-bigger-text' },
540
- { key: 'highContrast', className: 'snn-high-contrast', target: domCache.documentElement },
1112
+ { key: 'highContrast', className: 'snn-high-contrast' },
541
1113
  { key: 'dyslexiaFont', className: 'snn-dyslexia-font' },
542
- { key: 'lineHeight', className: 'snn-line-height' },
543
1114
  { key: 'textAlign', className: 'snn-text-align' },
544
1115
  { key: 'pauseAnimations', className: 'snn-pause-animations' },
545
- { key: 'textSpacing', className: 'snn-text-spacing' },
546
- { key: 'reducedMotion', className: 'snn-reduced-motion' },
547
1116
  ];
548
1117
 
549
1118
  // Batch DOM operations for better performance
@@ -601,6 +1170,14 @@ function applySettings() {
601
1170
  domCache.documentElement.classList.add(`snn-filter-${selectedFilter}`);
602
1171
  }
603
1172
 
1173
+ // Handle saturation filters
1174
+ const saturationClasses = ['snn-saturation-low', 'snn-saturation-high', 'snn-saturation-none'];
1175
+ domCache.documentElement.classList.remove(...saturationClasses);
1176
+ const selectedSaturation = localStorage.getItem('saturation');
1177
+ if (selectedSaturation) {
1178
+ domCache.documentElement.classList.add(`snn-saturation-${selectedSaturation}`);
1179
+ }
1180
+
604
1181
  // Handle text alignment
605
1182
  const alignClasses = ['snn-text-align-left', 'snn-text-align-center', 'snn-text-align-right'];
606
1183
  domCache.body.classList.remove(...alignClasses);
@@ -619,10 +1196,26 @@ function applySettings() {
619
1196
 
620
1197
  // Handle high contrast
621
1198
  const contrastClasses = ['snn-high-contrast-medium', 'snn-high-contrast-high', 'snn-high-contrast-ultra'];
622
- domCache.documentElement.classList.remove(...contrastClasses);
1199
+ domCache.body.classList.remove(...contrastClasses);
623
1200
  const selectedContrast = localStorage.getItem('highContrast');
624
1201
  if (selectedContrast) {
625
- domCache.documentElement.classList.add(`snn-high-contrast-${selectedContrast}`);
1202
+ domCache.body.classList.add(`snn-high-contrast-${selectedContrast}`);
1203
+ }
1204
+
1205
+ // Handle Text Spacing (3 Levels)
1206
+ const spacingClasses = ['snn-text-spacing-light', 'snn-text-spacing-medium', 'snn-text-spacing-heavy'];
1207
+ domCache.body.classList.remove(...spacingClasses);
1208
+ const selectedSpacing = localStorage.getItem('textSpacing');
1209
+ if (selectedSpacing) {
1210
+ domCache.body.classList.add(`snn-text-spacing-${selectedSpacing}`);
1211
+ }
1212
+
1213
+ // Handle Line Height (3 Levels)
1214
+ const lineHeightClasses = ['snn-line-height-2em', 'snn-line-height-3em', 'snn-line-height-4em'];
1215
+ domCache.body.classList.remove(...lineHeightClasses);
1216
+ const selectedLineHeight = localStorage.getItem('lineHeight');
1217
+ if (selectedLineHeight) {
1218
+ domCache.body.classList.add(`snn-line-height-${selectedLineHeight}`);
626
1219
  }
627
1220
 
628
1221
  // Handle images with cached query
@@ -653,12 +1246,12 @@ function createAccessibilityButton() {
653
1246
  const button = document.createElement('button');
654
1247
  button.id = 'snn-accessibility-button';
655
1248
  button.innerHTML = icons.buttonsvg;
656
- button.setAttribute('aria-label', WIDGET_CONFIG.lang.accessibilityMenu);
1249
+ button.setAttribute('aria-label', getTranslation('accessibilityMenu'));
657
1250
 
658
1251
  button.addEventListener('click', function () {
659
1252
  toggleMenu();
660
1253
  });
661
-
1254
+
662
1255
  button.addEventListener('keydown', function (e) {
663
1256
  if (e.key === 'Enter' || e.key === ' ') {
664
1257
  e.preventDefault();
@@ -667,7 +1260,7 @@ function createAccessibilityButton() {
667
1260
  });
668
1261
 
669
1262
  buttonContainer.appendChild(button);
670
- document.body.appendChild(buttonContainer);
1263
+ shadowRoot.appendChild(buttonContainer);
671
1264
  }
672
1265
 
673
1266
  // Reset all accessibility settings
@@ -684,9 +1277,9 @@ function resetAccessibilitySettings() {
684
1277
  'textSpacing',
685
1278
  'highContrast',
686
1279
  'voiceControl',
687
- 'reducedMotion',
688
1280
  'fontSelection',
689
1281
  'colorFilter',
1282
+ 'saturation',
690
1283
  ];
691
1284
  keys.forEach((key) => localStorage.removeItem(key));
692
1285
 
@@ -696,25 +1289,45 @@ function resetAccessibilitySettings() {
696
1289
  'snn-bigger-text',
697
1290
  'snn-dyslexia-font',
698
1291
  'snn-pause-animations',
699
- 'snn-text-spacing',
700
- 'snn-line-height',
701
1292
  'snn-text-align',
702
- 'snn-reduced-motion',
703
1293
  'snn-font-arial',
704
1294
  'snn-font-times',
705
1295
  'snn-font-verdana'
706
1296
  ];
707
1297
  cssClasses.forEach(cls => document.body.classList.remove(cls));
708
1298
 
1299
+ const bodyClasses2 = [
1300
+ 'snn-high-contrast-medium',
1301
+ 'snn-high-contrast-high',
1302
+ 'snn-high-contrast-ultra'
1303
+ ];
1304
+ bodyClasses2.forEach(cls => document.body.classList.remove(cls));
1305
+
709
1306
  const documentClasses = [
710
- 'snn-high-contrast',
711
1307
  'snn-filter-protanopia',
712
1308
  'snn-filter-deuteranopia',
713
1309
  'snn-filter-tritanopia',
714
- 'snn-filter-grayscale'
1310
+ 'snn-filter-grayscale',
1311
+ 'snn-saturation-low',
1312
+ 'snn-saturation-high',
1313
+ 'snn-saturation-none'
715
1314
  ];
716
1315
  documentClasses.forEach(cls => document.documentElement.classList.remove(cls));
717
1316
 
1317
+ const textSizeClasses = [
1318
+ 'snn-bigger-text-medium',
1319
+ 'snn-bigger-text-large',
1320
+ 'snn-bigger-text-xlarge'
1321
+ ];
1322
+ textSizeClasses.forEach(cls => document.body.classList.remove(cls));
1323
+
1324
+ // Clear Multi-level classes
1325
+ const spacingClasses = ['snn-text-spacing-light', 'snn-text-spacing-medium', 'snn-text-spacing-heavy'];
1326
+ spacingClasses.forEach(cls => document.body.classList.remove(cls));
1327
+
1328
+ const lineHeightClasses = ['snn-line-height-2em', 'snn-line-height-3em', 'snn-line-height-4em'];
1329
+ lineHeightClasses.forEach(cls => document.body.classList.remove(cls));
1330
+
718
1331
  domCache.getImages().forEach((img) => (img.style.display = ''));
719
1332
 
720
1333
  if (screenReader.active) {
@@ -727,10 +1340,14 @@ function resetAccessibilitySettings() {
727
1340
 
728
1341
  applySettings();
729
1342
 
730
- const buttons = document.querySelectorAll('#snn-accessibility-menu .snn-accessibility-option');
1343
+ const buttons = shadowRoot.querySelectorAll('#snn-accessibility-menu .snn-accessibility-option');
731
1344
  buttons.forEach((button) => {
732
1345
  button.classList.remove('active');
733
1346
  button.setAttribute('aria-pressed', 'false');
1347
+
1348
+ // Reset step indicators
1349
+ const steps = button.querySelectorAll('.snn-option-step');
1350
+ steps.forEach(step => step.classList.remove('active'));
734
1351
  });
735
1352
  }
736
1353
 
@@ -742,18 +1359,25 @@ function createToggleButton(
742
1359
  targetElement = document.body,
743
1360
  customToggleFunction = null,
744
1361
  iconSVG = '',
745
- requiresFeature = null
1362
+ requiresFeature = null,
1363
+ optionId = null
746
1364
  ) {
747
1365
  const button = document.createElement('button');
748
- button.innerHTML = `<span class="snn-icon">${iconSVG}</span><span class="snn-button-text">${buttonText}</span>`;
1366
+ button.innerHTML = `
1367
+ <span class="snn-icon">${iconSVG}</span>
1368
+ <span class="snn-button-text">${buttonText}</span>
1369
+ `;
749
1370
  button.setAttribute('data-key', localStorageKey);
750
1371
  button.setAttribute('aria-label', buttonText);
751
1372
  button.classList.add('snn-accessibility-option');
1373
+ if (optionId) {
1374
+ button.setAttribute('data-accessibility-option-id', optionId);
1375
+ }
752
1376
 
753
1377
  // Check if feature is supported
754
1378
  if (requiresFeature && !requiresFeature.isSupported) {
755
1379
  button.disabled = true;
756
- button.setAttribute('title', `${buttonText} ${WIDGET_CONFIG.lang.notSupportedBrowser}`);
1380
+ button.setAttribute('title', `${buttonText} ${getTranslation('notSupportedBrowser')}`);
757
1381
  button.style.opacity = '0.5';
758
1382
  return button;
759
1383
  }
@@ -768,17 +1392,17 @@ function createToggleButton(
768
1392
  button.addEventListener('click', function () {
769
1393
  handleToggle();
770
1394
  });
771
-
1395
+
772
1396
  button.addEventListener('keydown', function (e) {
773
1397
  if (e.key === 'Enter' || e.key === ' ') {
774
1398
  e.preventDefault();
775
1399
  handleToggle();
776
1400
  }
777
1401
  });
778
-
1402
+
779
1403
  function handleToggle() {
780
1404
  const newIsActive = localStorage.getItem(localStorageKey) !== 'true';
781
-
1405
+
782
1406
  // If there's a custom toggle function, call it and check if it succeeded
783
1407
  if (customToggleFunction) {
784
1408
  const success = customToggleFunction(newIsActive);
@@ -787,7 +1411,7 @@ function createToggleButton(
787
1411
  return;
788
1412
  }
789
1413
  }
790
-
1414
+
791
1415
  localStorage.setItem(localStorageKey, newIsActive);
792
1416
  button.setAttribute('aria-pressed', newIsActive);
793
1417
 
@@ -808,56 +1432,111 @@ function createToggleButton(
808
1432
  }
809
1433
 
810
1434
  // Create special action buttons (for cycling through options)
811
- function createActionButton(buttonText, actionFunction, iconSVG) {
1435
+ function createActionButton(buttonText, actionFunction, iconSVG, optionsConfig = null, optionId = null) {
812
1436
  const button = document.createElement('button');
813
- button.innerHTML = `<span class="snn-icon">${iconSVG}</span><span class="snn-button-text">${buttonText}: <span class="snn-status">${WIDGET_CONFIG.lang.default}</span></span>`;
1437
+
1438
+ let buttonHTML = `
1439
+ <span class="snn-icon">${iconSVG}</span>
1440
+ <span class="snn-button-text">${buttonText}</span>
1441
+ `;
1442
+
1443
+ // Add option steps if configured
1444
+ if (optionsConfig) {
1445
+ buttonHTML += '<div class="snn-option-steps">';
1446
+ for (let i = 0; i < optionsConfig.count; i++) {
1447
+ buttonHTML += '<div class="snn-option-step"></div>';
1448
+ }
1449
+ buttonHTML += '</div>';
1450
+ }
1451
+
1452
+ button.innerHTML = buttonHTML;
814
1453
  button.setAttribute('aria-label', buttonText);
815
1454
  button.classList.add('snn-accessibility-option');
816
-
1455
+ button.setAttribute('data-options-config', optionsConfig ? JSON.stringify(optionsConfig) : '');
1456
+ if (optionId) {
1457
+ button.setAttribute('data-accessibility-option-id', optionId);
1458
+ }
1459
+
817
1460
  // Update initial status
818
- updateActionButtonStatus(button, buttonText, actionFunction);
819
-
1461
+ updateActionButtonStatus(button, optionId, optionsConfig);
1462
+
820
1463
  button.addEventListener('click', function () {
821
1464
  const result = actionFunction();
822
1465
  if (result) {
823
- const statusSpan = button.querySelector('.snn-status');
824
- statusSpan.textContent = result;
1466
+ updateActionButtonStatus(button, optionId, optionsConfig);
825
1467
  }
826
1468
  });
827
-
1469
+
828
1470
  button.addEventListener('keydown', function (e) {
829
1471
  if (e.key === 'Enter' || e.key === ' ') {
830
1472
  e.preventDefault();
831
1473
  const result = actionFunction();
832
1474
  if (result) {
833
- const statusSpan = button.querySelector('.snn-status');
834
- statusSpan.textContent = result;
1475
+ updateActionButtonStatus(button, optionId, optionsConfig);
835
1476
  }
836
1477
  }
837
1478
  });
838
-
1479
+
839
1480
  return button;
840
1481
  }
841
1482
 
842
1483
  // Update action button status on page load
843
- function updateActionButtonStatus(button, buttonText, actionFunction) {
844
- const statusSpan = button.querySelector('.snn-status');
1484
+ function updateActionButtonStatus(button, optionId, optionsConfig) {
1485
+ if (!optionsConfig) return;
1486
+
1487
+ const steps = button.querySelectorAll('.snn-option-step');
1488
+ let currentIndex = -1;
845
1489
 
846
- if (buttonText.includes('Font')) {
1490
+ if (optionId === 'fontSelection') {
847
1491
  const currentFont = localStorage.getItem('fontSelection');
848
- statusSpan.textContent = currentFont ? currentFont.charAt(0).toUpperCase() + currentFont.slice(1) : WIDGET_CONFIG.lang.default;
849
- } else if (buttonText.includes('Color')) {
1492
+ const fonts = ['arial', 'times', 'verdana'];
1493
+ currentIndex = currentFont ? fonts.indexOf(currentFont) : -1;
1494
+ } else if (optionId === 'colorFilter') {
850
1495
  const currentFilter = localStorage.getItem('colorFilter');
851
- statusSpan.textContent = currentFilter ? currentFilter.charAt(0).toUpperCase() + currentFilter.slice(1) : WIDGET_CONFIG.lang.noFilter;
852
- } else if (buttonText.includes('Text Align')) {
1496
+ const filters = ['protanopia', 'deuteranopia', 'tritanopia', 'grayscale'];
1497
+ currentIndex = currentFilter ? filters.indexOf(currentFilter) : -1;
1498
+ } else if (optionId === 'textAlign') {
853
1499
  const currentAlign = localStorage.getItem('textAlign');
854
- statusSpan.textContent = currentAlign ? currentAlign.charAt(0).toUpperCase() + currentAlign.slice(1) : WIDGET_CONFIG.lang.default;
855
- } else if (buttonText.includes('Text Size')) {
1500
+ const alignments = ['left', 'center', 'right'];
1501
+ currentIndex = currentAlign ? alignments.indexOf(currentAlign) : -1;
1502
+ } else if (optionId === 'biggerText') {
856
1503
  const currentSize = localStorage.getItem('biggerText');
857
- statusSpan.textContent = currentSize ? (currentSize === 'xlarge' ? 'X-Large' : currentSize.charAt(0).toUpperCase() + currentSize.slice(1)) : WIDGET_CONFIG.lang.default;
858
- } else if (buttonText.includes('High Contrast')) {
1504
+ const sizes = ['medium', 'large', 'xlarge'];
1505
+ currentIndex = currentSize ? sizes.indexOf(currentSize) : -1;
1506
+ } else if (optionId === 'highContrast') {
859
1507
  const currentContrast = localStorage.getItem('highContrast');
860
- statusSpan.textContent = currentContrast ? currentContrast.charAt(0).toUpperCase() + currentContrast.slice(1) : WIDGET_CONFIG.lang.default;
1508
+ const contrasts = ['medium', 'high', 'ultra'];
1509
+ currentIndex = currentContrast ? contrasts.indexOf(currentContrast) : -1;
1510
+ } else if (optionId === 'textSpacing') {
1511
+ const currentSpacing = localStorage.getItem('textSpacing');
1512
+ const spacings = ['light', 'medium', 'heavy'];
1513
+ currentIndex = currentSpacing ? spacings.indexOf(currentSpacing) : -1;
1514
+ } else if (optionId === 'lineHeight') {
1515
+ const currentLineHeight = localStorage.getItem('lineHeight');
1516
+ const heights = ['2em', '3em', '4em'];
1517
+ currentIndex = currentLineHeight ? heights.indexOf(currentLineHeight) : -1;
1518
+ } else if (optionId === 'saturation') {
1519
+ const currentSaturation = localStorage.getItem('saturation');
1520
+ const saturations = ['low', 'high', 'none'];
1521
+ currentIndex = currentSaturation ? saturations.indexOf(currentSaturation) : -1;
1522
+ }
1523
+
1524
+ // Update step indicators - show all previous steps as active
1525
+ steps.forEach((step, index) => {
1526
+ if (index <= currentIndex) {
1527
+ step.classList.add('active');
1528
+ } else {
1529
+ step.classList.remove('active');
1530
+ }
1531
+ });
1532
+
1533
+ // Toggle active class on button itself if any option is selected
1534
+ if (currentIndex !== -1) {
1535
+ button.classList.add('active');
1536
+ button.setAttribute('aria-pressed', 'true');
1537
+ } else {
1538
+ button.classList.remove('active');
1539
+ button.setAttribute('aria-pressed', 'false');
861
1540
  }
862
1541
  }
863
1542
 
@@ -879,15 +1558,15 @@ function handleFontSelection() {
879
1558
  const currentFont = localStorage.getItem('fontSelection') || 'default';
880
1559
  const currentIndex = fonts.indexOf(currentFont);
881
1560
  const nextIndex = (currentIndex + 1) % (fonts.length + 1); // +1 for default
882
-
1561
+
883
1562
  // Remove all font classes in one operation
884
1563
  const fontClasses = ['snn-font-arial', 'snn-font-times', 'snn-font-verdana'];
885
1564
  domCache.body.classList.remove(...fontClasses);
886
-
1565
+
887
1566
  if (nextIndex === fonts.length) {
888
1567
  // Default font
889
1568
  localStorage.removeItem('fontSelection');
890
- return WIDGET_CONFIG.lang.defaultFont;
1569
+ return getTranslation('defaultFont');
891
1570
  } else {
892
1571
  const selectedFont = fonts[nextIndex];
893
1572
  localStorage.setItem('fontSelection', selectedFont);
@@ -896,21 +1575,47 @@ function handleFontSelection() {
896
1575
  }
897
1576
  }
898
1577
 
1578
+ // Saturation handler with 3 states (low, high, none/grayscale)
1579
+ function handleSaturation() {
1580
+ const saturations = ['low', 'high', 'none'];
1581
+ const currentSaturation = localStorage.getItem('saturation') || 'default';
1582
+ const currentIndex = saturations.indexOf(currentSaturation);
1583
+ const nextIndex = (currentIndex + 1) % (saturations.length + 1); // +1 for default
1584
+
1585
+ // Remove all saturation classes in one operation
1586
+ const saturationClasses = ['snn-saturation-low', 'snn-saturation-high', 'snn-saturation-none'];
1587
+ domCache.documentElement.classList.remove(...saturationClasses);
1588
+
1589
+ if (nextIndex === saturations.length) {
1590
+ // Default saturation
1591
+ localStorage.removeItem('saturation');
1592
+ return 'Default';
1593
+ } else {
1594
+ const selectedSaturation = saturations[nextIndex];
1595
+ localStorage.setItem('saturation', selectedSaturation);
1596
+ domCache.documentElement.classList.add(`snn-saturation-${selectedSaturation}`);
1597
+ if (selectedSaturation === 'none') {
1598
+ return 'No Saturation';
1599
+ }
1600
+ return selectedSaturation.charAt(0).toUpperCase() + selectedSaturation.slice(1) + ' Saturation';
1601
+ }
1602
+ }
1603
+
899
1604
  // Color filter handler (optimized)
900
1605
  function handleColorFilter() {
901
1606
  const filters = ['protanopia', 'deuteranopia', 'tritanopia', 'grayscale'];
902
1607
  const currentFilter = localStorage.getItem('colorFilter') || 'none';
903
1608
  const currentIndex = filters.indexOf(currentFilter);
904
1609
  const nextIndex = (currentIndex + 1) % (filters.length + 1); // +1 for none
905
-
1610
+
906
1611
  // Remove all filter classes in one operation
907
1612
  const filterClasses = ['snn-filter-protanopia', 'snn-filter-deuteranopia', 'snn-filter-tritanopia', 'snn-filter-grayscale'];
908
1613
  domCache.documentElement.classList.remove(...filterClasses);
909
-
1614
+
910
1615
  if (nextIndex === filters.length) {
911
1616
  // No filter
912
1617
  localStorage.removeItem('colorFilter');
913
- return WIDGET_CONFIG.lang.noFilter;
1618
+ return getTranslation('noFilter');
914
1619
  } else {
915
1620
  const selectedFilter = filters[nextIndex];
916
1621
  localStorage.setItem('colorFilter', selectedFilter);
@@ -925,15 +1630,15 @@ function handleTextAlign() {
925
1630
  const currentAlign = localStorage.getItem('textAlign') || 'none';
926
1631
  const currentIndex = alignments.indexOf(currentAlign);
927
1632
  const nextIndex = (currentIndex + 1) % (alignments.length + 1); // +1 for none
928
-
1633
+
929
1634
  // Remove all alignment classes
930
1635
  const alignClasses = ['snn-text-align-left', 'snn-text-align-center', 'snn-text-align-right'];
931
1636
  domCache.body.classList.remove(...alignClasses);
932
-
1637
+
933
1638
  if (nextIndex === alignments.length) {
934
1639
  // Default alignment
935
1640
  localStorage.removeItem('textAlign');
936
- return WIDGET_CONFIG.lang.default;
1641
+ return getTranslation('default');
937
1642
  } else {
938
1643
  const selectedAlign = alignments[nextIndex];
939
1644
  localStorage.setItem('textAlign', selectedAlign);
@@ -948,15 +1653,15 @@ function handleBiggerText() {
948
1653
  const currentSize = localStorage.getItem('biggerText') || 'none';
949
1654
  const currentIndex = textSizes.indexOf(currentSize);
950
1655
  const nextIndex = (currentIndex + 1) % (textSizes.length + 1); // +1 for none
951
-
1656
+
952
1657
  // Remove all text size classes
953
1658
  const textClasses = ['snn-bigger-text-medium', 'snn-bigger-text-large', 'snn-bigger-text-xlarge'];
954
1659
  domCache.body.classList.remove(...textClasses);
955
-
1660
+
956
1661
  if (nextIndex === textSizes.length) {
957
1662
  // Default text size
958
1663
  localStorage.removeItem('biggerText');
959
- return WIDGET_CONFIG.lang.default;
1664
+ return getTranslation('default');
960
1665
  } else {
961
1666
  const selectedSize = textSizes[nextIndex];
962
1667
  localStorage.setItem('biggerText', selectedSize);
@@ -971,23 +1676,69 @@ function handleHighContrast() {
971
1676
  const currentContrast = localStorage.getItem('highContrast') || 'none';
972
1677
  const currentIndex = contrastLevels.indexOf(currentContrast);
973
1678
  const nextIndex = (currentIndex + 1) % (contrastLevels.length + 1); // +1 for none
974
-
1679
+
975
1680
  // Remove all contrast classes
976
1681
  const contrastClasses = ['snn-high-contrast-medium', 'snn-high-contrast-high', 'snn-high-contrast-ultra'];
977
- domCache.documentElement.classList.remove(...contrastClasses);
978
-
1682
+ domCache.body.classList.remove(...contrastClasses);
1683
+
979
1684
  if (nextIndex === contrastLevels.length) {
980
1685
  // Default contrast
981
1686
  localStorage.removeItem('highContrast');
982
- return WIDGET_CONFIG.lang.default;
1687
+ return getTranslation('default');
983
1688
  } else {
984
1689
  const selectedContrast = contrastLevels[nextIndex];
985
1690
  localStorage.setItem('highContrast', selectedContrast);
986
- domCache.documentElement.classList.add(`snn-high-contrast-${selectedContrast}`);
1691
+ domCache.body.classList.add(`snn-high-contrast-${selectedContrast}`);
987
1692
  return selectedContrast.charAt(0).toUpperCase() + selectedContrast.slice(1);
988
1693
  }
989
1694
  }
990
1695
 
1696
+ // Text Spacing Handler with 3 states (1em, 2em, 4em equivalents)
1697
+ function handleTextSpacing() {
1698
+ const spacings = ['light', 'medium', 'heavy']; // Maps to 1, 2, 4 approx
1699
+ const currentSpacing = localStorage.getItem('textSpacing') || 'none';
1700
+ const currentIndex = spacings.indexOf(currentSpacing);
1701
+ const nextIndex = (currentIndex + 1) % (spacings.length + 1); // +1 for none
1702
+
1703
+ // Remove all spacing classes
1704
+ const spacingClasses = ['snn-text-spacing-light', 'snn-text-spacing-medium', 'snn-text-spacing-heavy'];
1705
+ domCache.body.classList.remove(...spacingClasses);
1706
+
1707
+ if (nextIndex === spacings.length) {
1708
+ // Default
1709
+ localStorage.removeItem('textSpacing');
1710
+ return getTranslation('default');
1711
+ } else {
1712
+ const selectedSpacing = spacings[nextIndex];
1713
+ localStorage.setItem('textSpacing', selectedSpacing);
1714
+ domCache.body.classList.add(`snn-text-spacing-${selectedSpacing}`);
1715
+ return selectedSpacing.charAt(0).toUpperCase() + selectedSpacing.slice(1);
1716
+ }
1717
+ }
1718
+
1719
+ // Line Height Handler with 3 states (2em, 3em, 4em)
1720
+ function handleLineHeight() {
1721
+ const heights = ['2em', '3em', '4em'];
1722
+ const currentHeight = localStorage.getItem('lineHeight') || 'none';
1723
+ const currentIndex = heights.indexOf(currentHeight);
1724
+ const nextIndex = (currentIndex + 1) % (heights.length + 1); // +1 for none
1725
+
1726
+ // Remove all line height classes
1727
+ const heightClasses = ['snn-line-height-2em', 'snn-line-height-3em', 'snn-line-height-4em'];
1728
+ domCache.body.classList.remove(...heightClasses);
1729
+
1730
+ if (nextIndex === heights.length) {
1731
+ // Default
1732
+ localStorage.removeItem('lineHeight');
1733
+ return getTranslation('default');
1734
+ } else{
1735
+ const selectedHeight = heights[nextIndex];
1736
+ localStorage.setItem('lineHeight', selectedHeight);
1737
+ domCache.body.classList.add(`snn-line-height-${selectedHeight}`);
1738
+ return selectedHeight;
1739
+ }
1740
+ }
1741
+
991
1742
  // ===========================================
992
1743
  // ACCESSIBILITY FEATURES
993
1744
  // ===========================================
@@ -1004,7 +1755,7 @@ const screenReader = {
1004
1755
  window.speechSynthesis.cancel();
1005
1756
  const speech = new SpeechSynthesisUtterance(content);
1006
1757
  speech.lang = 'en-US';
1007
- speech.onerror = function(event) {
1758
+ speech.onerror = function (event) {
1008
1759
  console.warn('Speech synthesis error:', event.error);
1009
1760
  };
1010
1761
  window.speechSynthesis.speak(speech);
@@ -1016,28 +1767,28 @@ const screenReader = {
1016
1767
  },
1017
1768
  toggle: function (isActive) {
1018
1769
  if (!screenReader.isSupported) {
1019
- console.warn(`Speech synthesis ${WIDGET_CONFIG.lang.notSupportedBrowser}`);
1770
+ console.warn(`Speech synthesis ${getTranslation('notSupportedBrowser')}`);
1020
1771
  return false;
1021
1772
  }
1022
-
1773
+
1023
1774
  screenReader.active = isActive;
1024
1775
  localStorage.setItem('screenReader', isActive);
1025
-
1776
+
1026
1777
  try {
1027
1778
  if (isActive) {
1028
1779
  document.addEventListener('focusin', screenReader.handleFocus);
1029
- const feedbackSpeech = new SpeechSynthesisUtterance(WIDGET_CONFIG.lang.screenReaderOn);
1780
+ const feedbackSpeech = new SpeechSynthesisUtterance(getTranslation('screenReaderOn'));
1030
1781
  feedbackSpeech.lang = 'en-US';
1031
- feedbackSpeech.onerror = function(event) {
1782
+ feedbackSpeech.onerror = function (event) {
1032
1783
  console.warn('Speech synthesis feedback error:', event.error);
1033
1784
  };
1034
1785
  window.speechSynthesis.speak(feedbackSpeech);
1035
1786
  } else {
1036
1787
  document.removeEventListener('focusin', screenReader.handleFocus);
1037
1788
  window.speechSynthesis.cancel();
1038
- const feedbackSpeech = new SpeechSynthesisUtterance(WIDGET_CONFIG.lang.screenReaderOff);
1789
+ const feedbackSpeech = new SpeechSynthesisUtterance(getTranslation('screenReaderOff'));
1039
1790
  feedbackSpeech.lang = 'en-US';
1040
- feedbackSpeech.onerror = function(event) {
1791
+ feedbackSpeech.onerror = function (event) {
1041
1792
  console.warn('Speech synthesis feedback error:', event.error);
1042
1793
  };
1043
1794
  window.speechSynthesis.speak(feedbackSpeech);
@@ -1046,7 +1797,7 @@ const screenReader = {
1046
1797
  console.warn('Screen reader toggle error:', error);
1047
1798
  return false;
1048
1799
  }
1049
-
1800
+
1050
1801
  return true;
1051
1802
  },
1052
1803
  };
@@ -1060,13 +1811,13 @@ const voiceControl = {
1060
1811
  maxRetries: 3,
1061
1812
  toggle: function (isActive) {
1062
1813
  if (!voiceControl.isSupported) {
1063
- console.warn(`Speech Recognition API ${WIDGET_CONFIG.lang.notSupportedBrowser}`);
1814
+ console.warn(`Speech Recognition API ${getTranslation('notSupportedBrowser')}`);
1064
1815
  return false;
1065
1816
  }
1066
-
1817
+
1067
1818
  voiceControl.isActive = isActive;
1068
1819
  localStorage.setItem('voiceControl', isActive);
1069
-
1820
+
1070
1821
  try {
1071
1822
  if (isActive) {
1072
1823
  voiceControl.startListening();
@@ -1081,14 +1832,14 @@ const voiceControl = {
1081
1832
  console.warn('Voice control toggle error:', error);
1082
1833
  return false;
1083
1834
  }
1084
-
1835
+
1085
1836
  return true;
1086
1837
  },
1087
1838
  startListening: function () {
1088
1839
  if (!voiceControl.isSupported) {
1089
1840
  return;
1090
1841
  }
1091
-
1842
+
1092
1843
  try {
1093
1844
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1094
1845
  voiceControl.recognition = new SpeechRecognition();
@@ -1097,7 +1848,7 @@ const voiceControl = {
1097
1848
  voiceControl.recognition.continuous = false;
1098
1849
 
1099
1850
  voiceControl.recognition.onstart = function () {
1100
- console.log(WIDGET_CONFIG.lang.voiceControlActivated);
1851
+ console.log(getTranslation('voiceControlActivated'));
1101
1852
  voiceControl.retryCount = 0;
1102
1853
  };
1103
1854
 
@@ -1139,10 +1890,10 @@ const voiceControl = {
1139
1890
  },
1140
1891
  handleVoiceCommand: function (command) {
1141
1892
  console.log(`Received command: ${command}`);
1142
-
1893
+
1143
1894
  try {
1144
1895
  // Check for show menu commands
1145
- if (WIDGET_CONFIG.voiceCommands.showMenu.includes(command)) {
1896
+ if (WIDGET_CONFIG.voiceCommands.showMenu.some(cmd => command.includes(cmd))) {
1146
1897
  if (!menuCache.button) menuCache.init();
1147
1898
  if (menuCache.button) {
1148
1899
  menuCache.button.click();
@@ -1151,36 +1902,36 @@ const voiceControl = {
1151
1902
  }
1152
1903
 
1153
1904
  // Check for reset all commands
1154
- if (WIDGET_CONFIG.voiceCommands.resetAll.includes(command)) {
1905
+ if (WIDGET_CONFIG.voiceCommands.resetAll.some(cmd => command.includes(cmd))) {
1155
1906
  resetAccessibilitySettings();
1156
1907
  return;
1157
1908
  }
1158
1909
 
1159
1910
  // Build dynamic command map based on configuration
1160
1911
  let localStorageKey = null;
1161
-
1912
+
1162
1913
  // Check each command group
1163
- if (WIDGET_CONFIG.voiceCommands.highContrast.includes(command)) {
1914
+ if (WIDGET_CONFIG.voiceCommands.highContrast.some(cmd => command.includes(cmd))) {
1164
1915
  localStorageKey = 'highContrast';
1165
- } else if (WIDGET_CONFIG.voiceCommands.biggerText.includes(command)) {
1916
+ } else if (WIDGET_CONFIG.voiceCommands.biggerText.some(cmd => command.includes(cmd))) {
1166
1917
  localStorageKey = 'biggerText';
1167
- } else if (WIDGET_CONFIG.voiceCommands.textSpacing.includes(command)) {
1918
+ } else if (WIDGET_CONFIG.voiceCommands.textSpacing.some(cmd => command.includes(cmd))) {
1168
1919
  localStorageKey = 'textSpacing';
1169
- } else if (WIDGET_CONFIG.voiceCommands.pauseAnimations.includes(command)) {
1920
+ } else if (WIDGET_CONFIG.voiceCommands.pauseAnimations.some(cmd => command.includes(cmd))) {
1170
1921
  localStorageKey = 'pauseAnimations';
1171
- } else if (WIDGET_CONFIG.voiceCommands.hideImages.includes(command)) {
1922
+ } else if (WIDGET_CONFIG.voiceCommands.hideImages.some(cmd => command.includes(cmd))) {
1172
1923
  localStorageKey = 'hideImages';
1173
- } else if (WIDGET_CONFIG.voiceCommands.dyslexiaFont.includes(command)) {
1924
+ } else if (WIDGET_CONFIG.voiceCommands.dyslexiaFont.some(cmd => command.includes(cmd))) {
1174
1925
  localStorageKey = 'dyslexiaFont';
1175
- } else if (WIDGET_CONFIG.voiceCommands.biggerCursor.includes(command)) {
1926
+ } else if (WIDGET_CONFIG.voiceCommands.biggerCursor.some(cmd => command.includes(cmd))) {
1176
1927
  localStorageKey = 'biggerCursor';
1177
- } else if (WIDGET_CONFIG.voiceCommands.lineHeight.includes(command)) {
1928
+ } else if (WIDGET_CONFIG.voiceCommands.lineHeight.some(cmd => command.includes(cmd))) {
1178
1929
  localStorageKey = 'lineHeight';
1179
- } else if (WIDGET_CONFIG.voiceCommands.textAlign.includes(command)) {
1930
+ } else if (WIDGET_CONFIG.voiceCommands.textAlign.some(cmd => command.includes(cmd))) {
1180
1931
  localStorageKey = 'textAlign';
1181
- } else if (WIDGET_CONFIG.voiceCommands.screenReader.includes(command)) {
1932
+ } else if (WIDGET_CONFIG.voiceCommands.screenReader.some(cmd => command.includes(cmd))) {
1182
1933
  localStorageKey = 'screenReader';
1183
- } else if (WIDGET_CONFIG.voiceCommands.voiceControl.includes(command)) {
1934
+ } else if (WIDGET_CONFIG.voiceCommands.voiceControl.some(cmd => command.includes(cmd))) {
1184
1935
  localStorageKey = 'voiceControl';
1185
1936
  }
1186
1937
 
@@ -1216,20 +1967,28 @@ function createAccessibilityMenu() {
1216
1967
  const header = document.createElement('div');
1217
1968
  header.classList.add('snn-header');
1218
1969
 
1219
- const title = document.createElement('h2');
1970
+ const title = document.createElement('div');
1220
1971
  title.classList.add('snn-title');
1221
1972
  title.id = 'snn-accessibility-title';
1222
- title.textContent = WIDGET_CONFIG.lang.accessibilityTools;
1973
+ title.textContent = getTranslation('accessibilityTools');
1223
1974
 
1975
+ // Create reset button
1976
+ const resetButton = document.createElement('button');
1977
+ resetButton.classList.add('snn-reset-button');
1978
+ resetButton.innerHTML = `${icons.resetAll}<span class="snn-tooltip">${getTranslation('reset')}</span>`;
1979
+ resetButton.setAttribute('aria-label', getTranslation('resetAllSettings'));
1980
+ resetButton.addEventListener('click', resetAccessibilitySettings);
1981
+
1982
+ // Create close button
1224
1983
  const closeButton = document.createElement('button');
1225
1984
  closeButton.className = 'snn-close';
1226
- closeButton.innerHTML = '';
1227
- closeButton.setAttribute('title', WIDGET_CONFIG.lang.closeAccessibilityMenu);
1985
+ closeButton.innerHTML = `<span class="snn-tooltip">${getTranslation('close')}</span>`;
1986
+ closeButton.setAttribute('aria-label', getTranslation('closeAccessibilityMenu'));
1228
1987
 
1229
1988
  closeButton.addEventListener('click', function () {
1230
1989
  closeMenu();
1231
1990
  });
1232
-
1991
+
1233
1992
  closeButton.addEventListener('keydown', function (e) {
1234
1993
  if (e.key === 'Enter' || e.key === ' ') {
1235
1994
  e.preventDefault();
@@ -1238,6 +1997,7 @@ function createAccessibilityMenu() {
1238
1997
  });
1239
1998
 
1240
1999
  header.appendChild(title);
2000
+ header.appendChild(resetButton);
1241
2001
  header.appendChild(closeButton);
1242
2002
  menu.appendChild(header);
1243
2003
 
@@ -1245,136 +2005,271 @@ function createAccessibilityMenu() {
1245
2005
  const content = document.createElement('div');
1246
2006
  content.classList.add('snn-content');
1247
2007
 
1248
- // Create reset button (outside grid, full width)
1249
- const resetButton = document.createElement('button');
1250
- resetButton.innerHTML = `<span class="snn-icon">${icons.resetAll}</span><span class="snn-button-text">${WIDGET_CONFIG.lang.resetAllSettings}</span>`;
1251
- resetButton.setAttribute('aria-label', WIDGET_CONFIG.lang.resetAllSettings);
1252
- resetButton.classList.add('snn-reset-button');
1253
- resetButton.addEventListener('click', resetAccessibilitySettings);
1254
- content.appendChild(resetButton);
2008
+ // Create language selector dropdown
2009
+ const languageSelector = document.createElement('select');
2010
+ languageSelector.classList.add('snn-language-selector');
2011
+ languageSelector.setAttribute('aria-label', getTranslation('selectLanguage'));
2012
+
2013
+ const languages = [
2014
+ { code: 'en', name: 'English' },
2015
+ { code: 'de', name: 'Deutsch' },
2016
+ { code: 'es', name: 'Español' },
2017
+ { code: 'it', name: 'Italiano' },
2018
+ { code: 'fr', name: 'Français' },
2019
+ { code: 'ru', name: 'Русский' },
2020
+ { code: 'tr', name: 'Türkçe' },
2021
+ { code: 'ar', name: 'العربية' },
2022
+ { code: 'hi', name: 'हिन्दी' },
2023
+ { code: 'zh-cn', name: '简体中文' },
2024
+ { code: 'jp', name: '日本語' }
2025
+ ];
2026
+
2027
+ languages.forEach(lang => {
2028
+ const option = document.createElement('option');
2029
+ option.value = lang.code;
2030
+ option.textContent = lang.name;
2031
+ if (lang.code === currentLanguage) {
2032
+ option.selected = true;
2033
+ }
2034
+ languageSelector.appendChild(option);
2035
+ });
2036
+
2037
+ languageSelector.addEventListener('change', function(e) {
2038
+ const newLang = e.target.value;
2039
+ if (setLanguage(newLang)) {
2040
+ // Recreate the menu with new language
2041
+ updateMenuLanguage();
2042
+ }
2043
+ });
2044
+
2045
+ content.appendChild(languageSelector);
1255
2046
 
1256
2047
  // Create grid wrapper for accessibility options
1257
2048
  const optionsGrid = document.createElement('div');
1258
2049
  optionsGrid.classList.add('snn-options-grid');
1259
2050
 
1260
- // Add accessibility options based on configuration
1261
- const options = [
2051
+ // ===================================================================
2052
+ // UNIFIED BUTTON CONFIGURATION WITH EXPLICIT ORDERING
2053
+ // Add/remove/reorder buttons by changing the 'order' property
2054
+ // Lower order numbers appear first, higher numbers appear last
2055
+ // ===================================================================
2056
+ const allButtonConfigs = [
2057
+ // Order 1-4: Primary accessibility features
1262
2058
  {
1263
- text: WIDGET_CONFIG.lang.screenReader,
1264
- key: 'screenReader',
1265
- customToggleFunction: screenReader.toggle,
1266
- icon: icons.screenReader,
1267
- requiresFeature: screenReader,
1268
- enabled: WIDGET_CONFIG.enableScreenReader,
2059
+ order: 1,
2060
+ type: 'action',
2061
+ text: getTranslation('textSize'),
2062
+ actionFunction: handleBiggerText,
2063
+ icon: icons.biggerText,
2064
+ enabled: WIDGET_CONFIG.enableBiggerText,
2065
+ optionsConfig: { count: 3 },
2066
+ optionId: 'biggerText'
1269
2067
  },
1270
2068
  {
1271
- text: WIDGET_CONFIG.lang.voiceCommand,
1272
- key: 'voiceControl',
1273
- customToggleFunction: voiceControl.toggle,
1274
- icon: icons.voiceControl,
1275
- requiresFeature: voiceControl,
1276
- enabled: WIDGET_CONFIG.enableVoiceControl,
2069
+ order: 2,
2070
+ type: 'action',
2071
+ text: getTranslation('highContrast'),
2072
+ actionFunction: handleHighContrast,
2073
+ icon: icons.highContrast,
2074
+ enabled: WIDGET_CONFIG.enableHighContrast,
2075
+ optionsConfig: { count: 3 },
2076
+ optionId: 'highContrast'
1277
2077
  },
1278
2078
  {
1279
- text: WIDGET_CONFIG.lang.textSpacing,
1280
- key: 'textSpacing',
1281
- className: 'snn-text-spacing',
2079
+ order: 3,
2080
+ type: 'action',
2081
+ text: getTranslation('textAlign'),
2082
+ actionFunction: handleTextAlign,
2083
+ icon: icons.textAlign,
2084
+ enabled: WIDGET_CONFIG.enableTextAlign,
2085
+ optionsConfig: { count: 3 },
2086
+ optionId: 'textAlign'
2087
+ },
2088
+ {
2089
+ order: 4,
2090
+ type: 'action',
2091
+ text: getTranslation('colorFilter'),
2092
+ actionFunction: handleColorFilter,
2093
+ icon: icons.colorFilter,
2094
+ enabled: WIDGET_CONFIG.enableColorFilter,
2095
+ optionsConfig: { count: 4 },
2096
+ optionId: 'colorFilter'
2097
+ },
2098
+
2099
+ // Order 5-11: Other visual/text features
2100
+ {
2101
+ order: 5,
2102
+ type: 'action', // Changed from toggle to action
2103
+ text: getTranslation('textSpacing'),
2104
+ actionFunction: handleTextSpacing,
1282
2105
  icon: icons.textSpacing,
1283
2106
  enabled: WIDGET_CONFIG.enableTextSpacing,
2107
+ optionsConfig: { count: 3 },
2108
+ optionId: 'textSpacing'
1284
2109
  },
1285
2110
  {
1286
- text: WIDGET_CONFIG.lang.pauseAnimations,
1287
- key: 'pauseAnimations',
1288
- className: 'snn-pause-animations',
1289
- icon: icons.pauseAnimations,
1290
- enabled: WIDGET_CONFIG.enablePauseAnimations,
2111
+ order: 6,
2112
+ type: 'action', // Changed from toggle to action
2113
+ text: getTranslation('lineHeight'),
2114
+ actionFunction: handleLineHeight,
2115
+ icon: icons.lineHeight,
2116
+ enabled: WIDGET_CONFIG.enableLineHeight,
2117
+ optionsConfig: { count: 3 },
2118
+ optionId: 'lineHeight'
1291
2119
  },
1292
2120
  {
1293
- text: WIDGET_CONFIG.lang.hideImages,
1294
- key: 'hideImages',
1295
- icon: icons.hideImages,
1296
- customToggleFunction: toggleHideImages,
1297
- enabled: WIDGET_CONFIG.enableHideImages,
2121
+ order: 7,
2122
+ type: 'action',
2123
+ text: getTranslation('fontSelection'),
2124
+ actionFunction: handleFontSelection,
2125
+ icon: icons.fontSelection,
2126
+ enabled: WIDGET_CONFIG.enableFontSelection,
2127
+ optionsConfig: { count: 3 },
2128
+ optionId: 'fontSelection'
2129
+ },
2130
+ {
2131
+ order: 7.5,
2132
+ type: 'action',
2133
+ text: getTranslation('saturation'),
2134
+ actionFunction: handleSaturation,
2135
+ icon: icons.saturation,
2136
+ enabled: true,
2137
+ optionsConfig: { count: 3 },
2138
+ optionId: 'saturation'
1298
2139
  },
1299
2140
  {
1300
- text: WIDGET_CONFIG.lang.dyslexiaFriendly,
2141
+ order: 8,
2142
+ type: 'toggle',
2143
+ text: getTranslation('dyslexiaFriendly'),
1301
2144
  key: 'dyslexiaFont',
1302
2145
  className: 'snn-dyslexia-font',
1303
2146
  icon: icons.dyslexiaFont,
1304
2147
  enabled: WIDGET_CONFIG.enableDyslexiaFont,
2148
+ optionId: 'dyslexiaFont'
1305
2149
  },
1306
2150
  {
1307
- text: WIDGET_CONFIG.lang.biggerCursor,
2151
+ order: 9,
2152
+ type: 'toggle',
2153
+ text: getTranslation('biggerCursor'),
1308
2154
  key: 'biggerCursor',
1309
2155
  className: 'snn-bigger-cursor',
1310
2156
  icon: icons.biggerCursor,
1311
2157
  enabled: WIDGET_CONFIG.enableBiggerCursor,
2158
+ optionId: 'biggerCursor'
1312
2159
  },
1313
2160
  {
1314
- text: WIDGET_CONFIG.lang.lineHeight,
1315
- key: 'lineHeight',
1316
- className: 'snn-line-height',
1317
- icon: icons.lineHeight,
1318
- enabled: WIDGET_CONFIG.enableLineHeight,
2161
+ order: 10,
2162
+ type: 'toggle',
2163
+ text: getTranslation('hideImages'),
2164
+ key: 'hideImages',
2165
+ icon: icons.hideImages,
2166
+ customToggleFunction: toggleHideImages,
2167
+ enabled: WIDGET_CONFIG.enableHideImages,
2168
+ optionId: 'hideImages'
2169
+ },
2170
+
2171
+ // Order 11: Animation controls (Reduced Motion merged here)
2172
+ {
2173
+ order: 11,
2174
+ type: 'toggle',
2175
+ text: getTranslation('pauseAnimations'),
2176
+ key: 'pauseAnimations',
2177
+ className: 'snn-pause-animations',
2178
+ icon: icons.pauseAnimations,
2179
+ enabled: WIDGET_CONFIG.enablePauseAnimations,
2180
+ optionId: 'pauseAnimations'
2181
+ },
2182
+
2183
+ // Order 98-99: Screen Reader and Voice Control (always last)
2184
+ {
2185
+ order: 98,
2186
+ type: 'toggle',
2187
+ text: getTranslation('screenReader'),
2188
+ key: 'screenReader',
2189
+ customToggleFunction: screenReader.toggle,
2190
+ icon: icons.screenReader,
2191
+ requiresFeature: screenReader,
2192
+ enabled: WIDGET_CONFIG.enableScreenReader,
2193
+ optionId: 'screenReader'
1319
2194
  },
1320
2195
  {
1321
- text: WIDGET_CONFIG.lang.reducedMotion,
1322
- key: 'reducedMotion',
1323
- className: 'snn-reduced-motion',
1324
- icon: icons.reducedMotion,
1325
- enabled: WIDGET_CONFIG.enableReducedMotion,
2196
+ order: 99,
2197
+ type: 'toggle',
2198
+ text: getTranslation('voiceCommand'),
2199
+ key: 'voiceControl',
2200
+ customToggleFunction: voiceControl.toggle,
2201
+ icon: icons.voiceControl,
2202
+ requiresFeature: voiceControl,
2203
+ enabled: WIDGET_CONFIG.enableVoiceControl,
2204
+ optionId: 'voiceControl'
1326
2205
  },
1327
2206
  ];
2207
+
2208
+ // Sort buttons by order and add only enabled ones to the grid
2209
+ allButtonConfigs
2210
+ .filter(config => config.enabled)
2211
+ .sort((a, b) => a.order - b.order)
2212
+ .forEach((config) => {
2213
+ let button;
2214
+
2215
+ if (config.type === 'action') {
2216
+ button = createActionButton(config.text, config.actionFunction, config.icon, config.optionsConfig, config.optionId);
2217
+ } else if (config.type === 'toggle') {
2218
+ button = createToggleButton(
2219
+ config.text,
2220
+ config.key,
2221
+ config.className,
2222
+ config.target || document.body,
2223
+ config.customToggleFunction,
2224
+ config.icon,
2225
+ config.requiresFeature,
2226
+ config.optionId
2227
+ );
2228
+ }
2229
+
2230
+ if (button) {
2231
+ optionsGrid.appendChild(button);
2232
+ }
2233
+ });
2234
+
2235
+ // Add grid to content
2236
+ content.appendChild(optionsGrid);
2237
+
2238
+ // Add content to menu
2239
+ menu.appendChild(content);
2240
+
2241
+ shadowRoot.appendChild(menu);
2242
+ }
2243
+
2244
+ // Update menu language without recreating everything
2245
+ function updateMenuLanguage() {
2246
+ const menu = shadowRoot.getElementById('snn-accessibility-menu');
2247
+ if (!menu) return;
1328
2248
 
1329
- // Add enabled toggle options to grid
1330
- options.forEach((option) => {
1331
- if (option.enabled) {
1332
- const button = createToggleButton(
1333
- option.text,
1334
- option.key,
1335
- option.className,
1336
- option.target,
1337
- option.customToggleFunction,
1338
- option.icon,
1339
- option.requiresFeature
1340
- );
1341
- optionsGrid.appendChild(button);
1342
- }
1343
- });
2249
+ const wasOpen = menu.style.display === 'block';
1344
2250
 
1345
- // Add action buttons (font selection and color filters) to grid if enabled
1346
- if (WIDGET_CONFIG.enableFontSelection) {
1347
- const fontButton = createActionButton(WIDGET_CONFIG.lang.fontSelection, handleFontSelection, icons.fontSelection);
1348
- optionsGrid.appendChild(fontButton);
1349
- }
2251
+ // Remove old menu
2252
+ menu.remove();
1350
2253
 
1351
- if (WIDGET_CONFIG.enableColorFilter) {
1352
- const colorButton = createActionButton(WIDGET_CONFIG.lang.colorFilter, handleColorFilter, icons.colorFilter);
1353
- optionsGrid.appendChild(colorButton);
1354
- }
2254
+ // Clear cache
2255
+ menuCache.menu = null;
2256
+ menuCache.closeButton = null;
2257
+ keyboardCache.focusableElements = null;
1355
2258
 
1356
- if (WIDGET_CONFIG.enableTextAlign) {
1357
- const textAlignButton = createActionButton(WIDGET_CONFIG.lang.textAlign, handleTextAlign, icons.textAlign);
1358
- optionsGrid.appendChild(textAlignButton);
1359
- }
2259
+ // Recreate menu
2260
+ createAccessibilityMenu();
1360
2261
 
1361
- if (WIDGET_CONFIG.enableBiggerText) {
1362
- const biggerTextButton = createActionButton(WIDGET_CONFIG.lang.textSize, handleBiggerText, icons.biggerText);
1363
- optionsGrid.appendChild(biggerTextButton);
2262
+ // Update button aria-label
2263
+ const mainButton = shadowRoot.getElementById('snn-accessibility-button');
2264
+ if (mainButton) {
2265
+ mainButton.setAttribute('aria-label', getTranslation('accessibilityMenu'));
1364
2266
  }
1365
2267
 
1366
- if (WIDGET_CONFIG.enableHighContrast) {
1367
- const highContrastButton = createActionButton(WIDGET_CONFIG.lang.highContrast, handleHighContrast, icons.highContrast);
1368
- optionsGrid.appendChild(highContrastButton);
2268
+ // Reopen if it was open
2269
+ if (wasOpen) {
2270
+ menuCache.init();
2271
+ openMenu();
1369
2272
  }
1370
-
1371
- // Add grid to content
1372
- content.appendChild(optionsGrid);
1373
-
1374
- // Add content to menu
1375
- menu.appendChild(content);
1376
-
1377
- document.body.appendChild(menu);
1378
2273
  }
1379
2274
 
1380
2275
  // ===========================================
@@ -1386,9 +2281,9 @@ const menuCache = {
1386
2281
  menu: null,
1387
2282
  button: null,
1388
2283
  closeButton: null,
1389
- init: function() {
1390
- this.menu = document.getElementById('snn-accessibility-menu');
1391
- this.button = document.getElementById('snn-accessibility-button');
2284
+ init: function () {
2285
+ this.menu = shadowRoot.getElementById('snn-accessibility-menu');
2286
+ this.button = shadowRoot.getElementById('snn-accessibility-button');
1392
2287
  this.closeButton = this.menu?.querySelector('.snn-close');
1393
2288
  }
1394
2289
  };
@@ -1397,7 +2292,7 @@ const menuCache = {
1397
2292
  function toggleMenu() {
1398
2293
  if (!menuCache.menu) menuCache.init();
1399
2294
  const isOpen = menuCache.menu.style.display === 'block';
1400
-
2295
+
1401
2296
  if (isOpen) {
1402
2297
  closeMenu();
1403
2298
  } else {
@@ -1409,11 +2304,15 @@ function openMenu() {
1409
2304
  if (!menuCache.menu) menuCache.init();
1410
2305
  menuCache.menu.style.display = 'block';
1411
2306
  menuCache.menu.setAttribute('aria-hidden', 'false');
1412
-
1413
- if (menuCache.closeButton) {
2307
+
2308
+ // UPDATED: Now focuses on the first tool button instead of the Close button
2309
+ const firstOption = menuCache.menu.querySelector('.snn-accessibility-option');
2310
+ if (firstOption) {
2311
+ firstOption.focus();
2312
+ } else if (menuCache.closeButton) {
1414
2313
  menuCache.closeButton.focus();
1415
2314
  }
1416
-
2315
+
1417
2316
  // Add keyboard navigation
1418
2317
  document.addEventListener('keydown', handleMenuKeyboard);
1419
2318
  }
@@ -1422,11 +2321,11 @@ function closeMenu() {
1422
2321
  if (!menuCache.menu) menuCache.init();
1423
2322
  menuCache.menu.style.display = 'none';
1424
2323
  menuCache.menu.setAttribute('aria-hidden', 'true');
1425
-
2324
+
1426
2325
  if (menuCache.button) {
1427
2326
  menuCache.button.focus();
1428
2327
  }
1429
-
2328
+
1430
2329
  // Remove keyboard navigation
1431
2330
  document.removeEventListener('keydown', handleMenuKeyboard);
1432
2331
  }
@@ -1435,13 +2334,13 @@ function closeMenu() {
1435
2334
  let keyboardCache = {
1436
2335
  focusableElements: null,
1437
2336
  lastUpdate: 0,
1438
- getFocusableElements: function() {
2337
+ getFocusableElements: function () {
1439
2338
  const now = Date.now();
1440
2339
  if (!this.focusableElements || now - this.lastUpdate > 1000) {
1441
2340
  if (menuCache.menu) {
1442
2341
  this.focusableElements = {
1443
2342
  all: menuCache.menu.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),
1444
- options: Array.from(menuCache.menu.querySelectorAll('.snn-accessibility-option, .snn-close'))
2343
+ options: Array.from(menuCache.menu.querySelectorAll('.snn-accessibility-option, .snn-close, .snn-reset-button'))
1445
2344
  };
1446
2345
  this.lastUpdate = now;
1447
2346
  }
@@ -1452,20 +2351,20 @@ let keyboardCache = {
1452
2351
 
1453
2352
  function handleMenuKeyboard(e) {
1454
2353
  if (!menuCache.menu || menuCache.menu.style.display !== 'block') return;
1455
-
2354
+
1456
2355
  if (e.key === 'Escape') {
1457
2356
  e.preventDefault();
1458
2357
  closeMenu();
1459
2358
  return;
1460
2359
  }
1461
-
2360
+
1462
2361
  const elements = keyboardCache.getFocusableElements();
1463
2362
  if (!elements) return;
1464
-
2363
+
1465
2364
  if (e.key === 'Tab') {
1466
2365
  const firstElement = elements.all[0];
1467
2366
  const lastElement = elements.all[elements.all.length - 1];
1468
-
2367
+
1469
2368
  if (e.shiftKey) {
1470
2369
  if (document.activeElement === firstElement) {
1471
2370
  e.preventDefault();
@@ -1478,18 +2377,18 @@ function handleMenuKeyboard(e) {
1478
2377
  }
1479
2378
  }
1480
2379
  }
1481
-
2380
+
1482
2381
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
1483
2382
  e.preventDefault();
1484
- const currentIndex = elements.options.indexOf(document.activeElement);
2383
+ const currentIndex = elements.options.indexOf(shadowRoot.activeElement);
1485
2384
  let nextIndex;
1486
-
2385
+
1487
2386
  if (e.key === 'ArrowDown') {
1488
2387
  nextIndex = currentIndex === elements.options.length - 1 ? 0 : currentIndex + 1;
1489
2388
  } else {
1490
2389
  nextIndex = currentIndex === 0 ? elements.options.length - 1 : currentIndex - 1;
1491
2390
  }
1492
-
2391
+
1493
2392
  elements.options[nextIndex].focus();
1494
2393
  }
1495
2394
  }
@@ -1500,8 +2399,16 @@ function handleMenuKeyboard(e) {
1500
2399
 
1501
2400
  // Initialize the widget
1502
2401
  function initAccessibilityWidget() {
1503
- injectStyles();
2402
+ // Create shadow DOM first
2403
+ createShadowContainer();
2404
+
2405
+ // Inject page styles (for accessibility features)
2406
+ injectPageStyles();
2407
+
2408
+ // Apply saved settings
1504
2409
  applySettings();
2410
+
2411
+ // Create widget UI inside shadow DOM
1505
2412
  createAccessibilityButton();
1506
2413
  createAccessibilityMenu();
1507
2414
  }
@@ -1515,103 +2422,4 @@ if (document.readyState === 'loading') {
1515
2422
  document.addEventListener('DOMContentLoaded', initAccessibilityWidget);
1516
2423
  } else {
1517
2424
  initAccessibilityWidget();
1518
- }
1519
-
1520
- /*
1521
- ===========================================
1522
- WIDGET FEATURES SUMMARY:
1523
-
1524
- Core Features:
1525
- - High contrast mode
1526
- - Text size adjustment
1527
- - Text spacing modification
1528
- - Animation pausing
1529
- - Image hiding
1530
- - Dyslexia-friendly font
1531
- - Cursor size adjustment
1532
- - Line height adjustment
1533
- - Text alignment
1534
-
1535
- Advanced Features:
1536
- - Screen reader with speech synthesis
1537
- - Voice control with speech recognition
1538
- - Reading mode
1539
- - Enhanced focus indicators
1540
- - Reduced motion mode
1541
- - Font selection (Arial, Times, Verdana)
1542
- - Color blindness filters (Protanopia, Deuteranopia, Tritanopia, Grayscale)
1543
-
1544
- Technical Features:
1545
- - Persistent settings via localStorage
1546
- - Full keyboard navigation
1547
- - ARIA compliance
1548
- - Error handling for browser compatibility
1549
- - Performance optimization with DOM caching
1550
- - Single file deployment
1551
-
1552
- Configuration Options:
1553
- - Customizable language/text strings
1554
- - Customizable voice command strings
1555
- - Configurable grid layout (columns and gap)
1556
- - All widget styling and positioning options
1557
- - Feature enable/disable toggles
1558
-
1559
- ===========================================
1560
-
1561
- CONFIGURATION EXAMPLE:
1562
-
1563
- To customize the widget, define window.ACCESSIBILITY_WIDGET_CONFIG
1564
- before loading the widget script:
1565
-
1566
- <script>
1567
- window.ACCESSIBILITY_WIDGET_CONFIG = {
1568
- // Customize language/text strings
1569
- lang: {
1570
- accessibilityMenu: 'Menu de Accesibilidad',
1571
- screenReader: 'Lector de Pantalla',
1572
- biggerText: 'Texto Más Grande',
1573
- highContrast: 'Alto Contraste',
1574
- // ... customize any text string
1575
- },
1576
-
1577
- // Customize voice commands for different languages
1578
- voiceCommands: {
1579
- showMenu: ['mostrar menú', 'abrir menú', 'menú de accesibilidad'],
1580
- highContrast: ['alto contraste'],
1581
- biggerText: ['texto más grande', 'texto grande'],
1582
- textSpacing: ['espaciado de texto'],
1583
- pauseAnimations: ['pausar animaciones', 'detener animaciones'],
1584
- hideImages: ['ocultar imágenes'],
1585
- dyslexiaFont: ['fuente para dislexia', 'fuente dislexia'],
1586
- biggerCursor: ['cursor más grande', 'cursor grande'],
1587
- lineHeight: ['altura de línea'],
1588
- textAlign: ['alinear texto', 'alineación de texto'],
1589
- screenReader: ['lector de pantalla'],
1590
- voiceControl: ['comando de voz', 'control de voz'],
1591
- resetAll: ['reiniciar todo', 'resetear todo']
1592
- },
1593
-
1594
- // Customize grid layout
1595
- gridLayout: {
1596
- columns: '1fr 1fr 1fr', // 3-column layout
1597
- gap: '15px' // Custom gap between buttons
1598
- },
1599
-
1600
- // Other customizable options
1601
- widgetWidth: '500px',
1602
- widgetPosition: {
1603
- side: 'left',
1604
- left: '30px',
1605
- bottom: '30px'
1606
- },
1607
- colors: {
1608
- primary: '#2196F3',
1609
- primaryHover: '#1976D2',
1610
- // ... customize any color
1611
- }
1612
- };
1613
- </script>
1614
- <script src="widget.js"></script>
1615
-
1616
- ===========================================
1617
- */
2425
+ }