@m00rl0ck/a11y-widget 0.1.0 → 0.2.0

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.
@@ -0,0 +1,575 @@
1
+ import { locales, detectLang } from './locales.js'
2
+ import { questions, suggestType } from './quiz.js'
3
+ import { injectStyles, removeStyles } from './styles.js'
4
+
5
+ const storageKey = 'colorblind-adapt-settings'
6
+
7
+ class ColorblindAdaptWidget extends HTMLElement {
8
+ static observedAttributes = ['lang']
9
+
10
+ constructor() {
11
+ super()
12
+ this.attachShadow({ mode: 'open' })
13
+ this._open = false
14
+ this._tab = 'test'
15
+ this._quizAnswers = {}
16
+ this._quizStep = 0
17
+ this._quizDone = false
18
+ this._quizResult = null
19
+ this._activeType = null
20
+ this._activeSeverity = 'moderate'
21
+ }
22
+
23
+ get lang() {
24
+ return this.getAttribute('lang') || detectLang()
25
+ }
26
+
27
+ t(key) {
28
+ return (locales[this.lang] || locales.en)[key] || key
29
+ }
30
+
31
+ connectedCallback() {
32
+ this._loadState()
33
+ this._render()
34
+ this._applyAdaptation()
35
+ }
36
+
37
+ attributeChangedCallback() {
38
+ if (this.shadowRoot.innerHTML) this._render()
39
+ }
40
+
41
+ _loadState() {
42
+ try {
43
+ const saved = JSON.parse(localStorage.getItem(storageKey))
44
+ if (saved && saved.type) {
45
+ this._activeType = saved.type
46
+ this._activeSeverity = saved.severity || 'moderate'
47
+ }
48
+ } catch {}
49
+ }
50
+
51
+ _saveState() {
52
+ if (this._activeType) {
53
+ localStorage.setItem(storageKey, JSON.stringify({
54
+ type: this._activeType,
55
+ severity: this._activeSeverity,
56
+ }))
57
+ } else {
58
+ localStorage.removeItem(storageKey)
59
+ }
60
+ }
61
+
62
+ _setOpen(val) {
63
+ this._open = val
64
+ this._render()
65
+ }
66
+
67
+ _switchTab(tab) {
68
+ this._tab = tab
69
+ if (tab === 'test') {
70
+ this._quizAnswers = {}
71
+ this._quizStep = 0
72
+ this._quizDone = false
73
+ this._quizResult = null
74
+ }
75
+ this._render()
76
+ }
77
+
78
+ _answerQuestion(value) {
79
+ const q = questions[this._quizStep]
80
+ if (!q) return
81
+ this._quizAnswers[q.id] = value
82
+ if (this._quizStep < questions.length - 1) {
83
+ this._quizStep++
84
+ this._render()
85
+ } else {
86
+ this._quizResult = suggestType(this._quizAnswers)
87
+ this._quizDone = true
88
+ this._activeType = this._quizResult.type
89
+ this._activeSeverity = this._quizResult.severity
90
+ this._saveState()
91
+ this._applyAdaptation()
92
+ this._render()
93
+ }
94
+ }
95
+
96
+ _selectType(type) {
97
+ this._activeType = type
98
+ this._saveState()
99
+ this._applyAdaptation()
100
+ this._render()
101
+ }
102
+
103
+ _setSeverity(sev) {
104
+ this._activeSeverity = sev
105
+ if (this._activeType) {
106
+ this._saveState()
107
+ this._applyAdaptation()
108
+ }
109
+ this._render()
110
+ }
111
+
112
+ _reset() {
113
+ this._activeType = null
114
+ this._activeSeverity = 'moderate'
115
+ this._quizAnswers = {}
116
+ this._quizStep = 0
117
+ this._quizDone = false
118
+ this._quizResult = null
119
+ this._tab = 'test'
120
+ localStorage.removeItem(storageKey)
121
+ removeStyles()
122
+ document.documentElement.removeAttribute('data-cba-active')
123
+ this._render()
124
+ }
125
+
126
+ _applyAdaptation() {
127
+ document.documentElement.removeAttribute('data-cba-active')
128
+ removeStyles()
129
+ if (this._activeType && this._activeType !== 'normal') {
130
+ document.documentElement.setAttribute('data-cba-active', this._activeType)
131
+ injectStyles(this._activeType, this._activeSeverity)
132
+ }
133
+ }
134
+
135
+ _render() {
136
+ const t = (k) => this.t(k)
137
+
138
+ this.shadowRoot.innerHTML = `
139
+ <style>
140
+ :host {
141
+ position: fixed;
142
+ right: 20px;
143
+ bottom: 80px;
144
+ z-index: 99999;
145
+ font-family: Arial, Helvetica, sans-serif;
146
+ }
147
+ * { box-sizing: border-box; }
148
+ .widget {
149
+ display: flex;
150
+ flex-direction: column;
151
+ align-items: flex-end;
152
+ gap: 10px;
153
+ }
154
+ .toggle {
155
+ display: inline-flex;
156
+ align-items: center;
157
+ gap: 8px;
158
+ border: 2px solid #111;
159
+ border-radius: 999px;
160
+ padding: 12px 16px;
161
+ background: #fff;
162
+ color: #111;
163
+ font-size: 15px;
164
+ font-weight: 700;
165
+ cursor: pointer;
166
+ box-shadow: 0 10px 30px rgba(0,0,0,.18);
167
+ transition: transform 160ms ease, box-shadow 160ms ease;
168
+ }
169
+ .toggle:hover {
170
+ transform: translateY(-1px);
171
+ box-shadow: 0 14px 34px rgba(0,0,0,.22);
172
+ }
173
+ .toggle:focus-visible,
174
+ .control:focus-visible,
175
+ .tab:focus-visible {
176
+ outline: 3px solid #ffcc00;
177
+ outline-offset: 3px;
178
+ }
179
+ .panel {
180
+ width: min(360px, calc(100vw - 32px));
181
+ padding: 18px;
182
+ border: 2px solid #111;
183
+ border-radius: 18px;
184
+ background: #fff;
185
+ color: #111;
186
+ box-shadow: 0 18px 50px rgba(0,0,0,.25);
187
+ }
188
+ .panel[hidden] { display: none; }
189
+ .header {
190
+ display: flex;
191
+ align-items: flex-start;
192
+ justify-content: space-between;
193
+ gap: 12px;
194
+ margin-bottom: 14px;
195
+ }
196
+ .title {
197
+ margin: 0;
198
+ font-size: 18px;
199
+ line-height: 1.25;
200
+ font-weight: 800;
201
+ }
202
+ .subtitle {
203
+ margin: 4px 0 0;
204
+ font-size: 13px;
205
+ line-height: 1.4;
206
+ color: #555;
207
+ }
208
+ .close {
209
+ width: 32px; height: 32px;
210
+ border: 1px solid #ddd;
211
+ border-radius: 50%;
212
+ background: #f7f7f7;
213
+ cursor: pointer;
214
+ font-size: 20px;
215
+ line-height: 1;
216
+ }
217
+ .tabs {
218
+ display: flex;
219
+ gap: 4px;
220
+ margin-bottom: 14px;
221
+ background: #f0f0f0;
222
+ border-radius: 10px;
223
+ padding: 3px;
224
+ }
225
+ .tab {
226
+ flex: 1;
227
+ padding: 8px;
228
+ border: none;
229
+ border-radius: 8px;
230
+ background: transparent;
231
+ color: #555;
232
+ font-size: 13px;
233
+ font-weight: 700;
234
+ cursor: pointer;
235
+ transition: background 140ms, color 140ms;
236
+ }
237
+ .tab.active {
238
+ background: #fff;
239
+ color: #111;
240
+ box-shadow: 0 1px 3px rgba(0,0,0,.1);
241
+ }
242
+ .option-grid {
243
+ display: flex;
244
+ flex-direction: column;
245
+ gap: 8px;
246
+ }
247
+ .option {
248
+ display: block;
249
+ width: 100%;
250
+ padding: 12px 14px;
251
+ border: 2px solid #d6d6d6;
252
+ border-radius: 12px;
253
+ background: #fafafa;
254
+ color: #111;
255
+ font-size: 14px;
256
+ font-weight: 700;
257
+ cursor: pointer;
258
+ text-align: left;
259
+ transition: border-color 140ms ease, background 140ms ease;
260
+ }
261
+ .option:hover {
262
+ border-color: #111;
263
+ background: #f0f0f0;
264
+ }
265
+ .option.active {
266
+ border-color: #111;
267
+ background: #111;
268
+ color: #fff;
269
+ }
270
+ .step-indicator {
271
+ font-size: 12px;
272
+ color: #888;
273
+ margin-bottom: 12px;
274
+ }
275
+ .result-card {
276
+ padding: 16px;
277
+ border: 2px solid #111;
278
+ border-radius: 12px;
279
+ background: #fafafa;
280
+ margin-bottom: 12px;
281
+ }
282
+ .result-title {
283
+ font-weight: 800;
284
+ font-size: 16px;
285
+ margin-bottom: 6px;
286
+ }
287
+ .result-desc {
288
+ font-size: 13px;
289
+ line-height: 1.5;
290
+ color: #333;
291
+ }
292
+ .result-type {
293
+ display: inline-block;
294
+ padding: 3px 10px;
295
+ border-radius: 999px;
296
+ background: #111;
297
+ color: #fff;
298
+ font-size: 12px;
299
+ font-weight: 700;
300
+ margin-top: 8px;
301
+ }
302
+ .select-group {
303
+ margin-bottom: 12px;
304
+ }
305
+ .select-label {
306
+ display: block;
307
+ font-weight: 700;
308
+ font-size: 13px;
309
+ margin-bottom: 6px;
310
+ color: #333;
311
+ }
312
+ .select-input {
313
+ width: 100%;
314
+ padding: 10px 12px;
315
+ border: 2px solid #d6d6d6;
316
+ border-radius: 10px;
317
+ font-size: 14px;
318
+ font-weight: 600;
319
+ background: #fafafa;
320
+ color: #111;
321
+ cursor: pointer;
322
+ }
323
+ .select-input:focus {
324
+ border-color: #111;
325
+ outline: none;
326
+ }
327
+ .severity-group {
328
+ display: flex;
329
+ gap: 6px;
330
+ margin-top: 8px;
331
+ }
332
+ .severity-btn {
333
+ flex: 1;
334
+ padding: 8px;
335
+ border: 2px solid #d6d6d6;
336
+ border-radius: 8px;
337
+ background: #fafafa;
338
+ color: #555;
339
+ font-size: 12px;
340
+ font-weight: 700;
341
+ cursor: pointer;
342
+ text-align: center;
343
+ transition: all 140ms;
344
+ }
345
+ .severity-btn.active {
346
+ border-color: #111;
347
+ background: #111;
348
+ color: #fff;
349
+ }
350
+ .action-row {
351
+ display: flex;
352
+ gap: 8px;
353
+ margin-top: 14px;
354
+ }
355
+ .btn-primary {
356
+ flex: 1;
357
+ padding: 10px;
358
+ border: 2px solid #111;
359
+ border-radius: 10px;
360
+ background: #111;
361
+ color: #fff;
362
+ font-weight: 700;
363
+ font-size: 13px;
364
+ cursor: pointer;
365
+ }
366
+ .btn-reset {
367
+ flex: 1;
368
+ padding: 10px;
369
+ border: 2px solid #f0b4b4;
370
+ border-radius: 10px;
371
+ background: #fff4f4;
372
+ color: #8a0000;
373
+ font-weight: 700;
374
+ font-size: 13px;
375
+ cursor: pointer;
376
+ }
377
+ .btn-reset:hover {
378
+ background: #ffe0e0;
379
+ }
380
+ .tag {
381
+ display: inline-block;
382
+ padding: 2px 8px;
383
+ border-radius: 6px;
384
+ font-size: 11px;
385
+ font-weight: 700;
386
+ margin-left: 8px;
387
+ }
388
+ .tag-active {
389
+ background: #111;
390
+ color: #fff;
391
+ }
392
+ @media (max-width: 520px) {
393
+ :host {
394
+ right: 12px !important;
395
+ bottom: 70px !important;
396
+ }
397
+ .toggle { padding: 11px 14px; font-size: 14px; }
398
+ }
399
+ </style>
400
+
401
+ <div class="widget">
402
+ <section class="panel" ${this._open ? '' : 'hidden'} aria-label="${t('panelTitle')}">
403
+ <div class="header">
404
+ <div>
405
+ <h2 class="title">
406
+ ${t('panelTitle')}
407
+ ${this._activeType && this._activeType !== 'normal' ? `<span class="tag tag-active">${this._activeType}</span>` : ''}
408
+ </h2>
409
+ <p class="subtitle">${t('panelSubtitle')}</p>
410
+ </div>
411
+ <button class="close" type="button" aria-label="${t('closeLabel')}">×</button>
412
+ </div>
413
+
414
+ ${this._renderBody()}
415
+
416
+ ${this._activeType && this._activeType !== 'normal' ? `
417
+ <div class="action-row">
418
+ <button class="btn-reset" type="button">${t('reset')}</button>
419
+ </div>
420
+ ` : ''}
421
+ </section>
422
+
423
+ <button
424
+ class="toggle"
425
+ type="button"
426
+ aria-expanded="${this._open ? 'true' : 'false'}"
427
+ aria-label="${t('toggleLabel')}"
428
+ >
429
+ <span class="icon" aria-hidden="true">🔵</span>
430
+ <span>${t('toggleText')}</span>
431
+ ${this._activeType && this._activeType !== 'normal' ? ` ✓` : ''}
432
+ </button>
433
+ </div>
434
+ `
435
+
436
+ this.shadowRoot.querySelector('.toggle').addEventListener('click', () => this._setOpen(!this._open))
437
+ this.shadowRoot.querySelector('.close').addEventListener('click', () => this._setOpen(false))
438
+
439
+ const panel = this.shadowRoot.querySelector('.panel')
440
+ if (panel) {
441
+ panel.querySelectorAll('button[data-answer]').forEach((btn) => {
442
+ btn.addEventListener('click', () => this._answerQuestion(btn.dataset.answer))
443
+ })
444
+ }
445
+
446
+ if (this._open && this._tab === 'test' && !this._quizDone) {
447
+ this.shadowRoot.querySelectorAll('.tab').forEach((el) => {
448
+ el.addEventListener('click', () => this._switchTab(el.dataset.tab))
449
+ })
450
+ }
451
+
452
+ if (this._open && this._tab === 'manual') {
453
+ const select = this.shadowRoot.querySelector('#cba-type-select')
454
+ if (select) {
455
+ select.addEventListener('change', () => this._selectType(select.value))
456
+
457
+ const severityBtns = this.shadowRoot.querySelectorAll('[data-severity]')
458
+ severityBtns.forEach((btn) => {
459
+ btn.addEventListener('click', () => this._setSeverity(btn.dataset.severity))
460
+ })
461
+ }
462
+
463
+ const resetBtn = this.shadowRoot.querySelector('.btn-reset')
464
+ if (resetBtn) resetBtn.addEventListener('click', () => this._reset())
465
+ }
466
+
467
+ if (this._open && this._quizDone) {
468
+ const retakeBtn = this.shadowRoot.querySelector('.btn-reset')
469
+ if (retakeBtn) retakeBtn.addEventListener('click', () => this._reset())
470
+ }
471
+ }
472
+
473
+ _renderBody() {
474
+ const t = (k) => this.t(k)
475
+
476
+ if (this._activeType && this._activeType !== 'normal') {
477
+ return this._renderActiveState(t)
478
+ }
479
+
480
+ if (this._quizDone) {
481
+ return this._renderResult(t)
482
+ }
483
+
484
+ return `
485
+ <div class="tabs">
486
+ <button class="tab ${this._tab === 'test' ? 'active' : ''}" data-tab="test" type="button">${t('testTab')}</button>
487
+ <button class="tab ${this._tab === 'manual' ? 'active' : ''}" data-tab="manual" type="button">${t('manualTab')}</button>
488
+ </div>
489
+ ${this._tab === 'test' ? this._renderQuiz(t) : this._renderManual(t)}
490
+ `
491
+ }
492
+
493
+ _renderQuiz(t) {
494
+ const q = questions[this._quizStep]
495
+ if (!q) return ''
496
+
497
+ return `
498
+ <div class="step-indicator">${t('question' + (this._quizStep + 1))} (${this._quizStep + 1}/${questions.length})</div>
499
+ <p style="font-weight:700;font-size:15px;margin:0 0 12px">${t(q.promptKey)}</p>
500
+ <div class="option-grid">
501
+ ${q.options.map((opt, i) => `
502
+ <button class="option" data-answer="${opt.value}" type="button">
503
+ ${t(opt.labelKey)}
504
+ </button>
505
+ `).join('')}
506
+ </div>
507
+ `
508
+ }
509
+
510
+ _renderManual(t) {
511
+ const types = ['deuteranopia', 'protanopia', 'tritanopia', 'monochromacy']
512
+ const severities = ['mild', 'moderate', 'severe']
513
+
514
+ return `
515
+ <div class="select-group">
516
+ <label class="select-label" for="cba-type-select">${t('selectType')}</label>
517
+ <select class="select-input" id="cba-type-select">
518
+ <option value="">— ${t('selectType')} —</option>
519
+ ${types.map((type) => `
520
+ <option value="${type}" ${this._activeType === type ? 'selected' : ''}>
521
+ ${t(type)}
522
+ </option>
523
+ `).join('')}
524
+ </select>
525
+ </div>
526
+ ${this._activeType ? `
527
+ <div class="select-group">
528
+ <label class="select-label">${t('severity')}</label>
529
+ <div class="severity-group">
530
+ ${severities.map((sev) => `
531
+ <button class="severity-btn ${this._activeSeverity === sev ? 'active' : ''}" data-severity="${sev}" type="button">
532
+ ${t(sev)}
533
+ </button>
534
+ `).join('')}
535
+ </div>
536
+ </div>
537
+ ` : ''}
538
+ `
539
+ }
540
+
541
+ _renderResult(t) {
542
+ const r = this._quizResult
543
+ if (!r) return ''
544
+
545
+ const typeKey = 'type' + r.type.charAt(0).toUpperCase() + r.type.slice(1)
546
+
547
+ return `
548
+ <div class="result-card">
549
+ <div class="result-title">${t('resultTitle')}</div>
550
+ <div class="result-desc">
551
+ ${t('resultDesc').replace('{type}', t(typeKey))}
552
+ </div>
553
+ <span class="result-type">${t(r.severity)} · ${t(typeKey)}</span>
554
+ </div>
555
+ <button class="btn-reset" type="button">${t('reset')}</button>
556
+ `
557
+ }
558
+
559
+ _renderActiveState(t) {
560
+ const r = this._quizResult
561
+ return `
562
+ <div class="result-card">
563
+ <div class="result-title">${t('resultTitle')}</div>
564
+ <span class="result-type">${this._activeType} · ${t(this._activeSeverity)}</span>
565
+ </div>
566
+ `
567
+ }
568
+ }
569
+
570
+ export { ColorblindAdaptWidget }
571
+ export default ColorblindAdaptWidget
572
+
573
+ if (!customElements.get('colorblind-adapt-widget')) {
574
+ customElements.define('colorblind-adapt-widget', ColorblindAdaptWidget)
575
+ }
@@ -0,0 +1,131 @@
1
+ export const locales = {
2
+ uk: {
3
+ toggleText: 'Дальтонізм',
4
+ toggleLabel: 'Налаштувати сайт для людей із дальтонізмом',
5
+ closeLabel: 'Закрити панель',
6
+ panelTitle: 'Налаштування для дальтонізму',
7
+ panelSubtitle: 'Пройдіть швидкий тест або оберіть свій тип зору.',
8
+ testTab: 'Тест',
9
+ manualTab: 'Вручну',
10
+ startTest: 'Розпочати тест',
11
+ nextQuestion: 'Далі',
12
+ question1: 'Який колір ви бачите в центрі?',
13
+ question2: 'Яке число бачите?',
14
+ question3: 'Який квадрат яскравіший?',
15
+ optRedGreen: 'Червоний / Зелений',
16
+ optBlueYellow: 'Синій / Жовтий',
17
+ optNone: 'Не бачу різниці',
18
+ optNumber74: '74',
19
+ optNumber21: '21',
20
+ optNoNumber: 'Не бачу числа',
21
+ optLeft: 'Лівий',
22
+ optRight: 'Правий',
23
+ optSame: 'Однакові',
24
+ resultTitle: 'Результат тесту',
25
+ resultDesc: 'Схоже, у вас {type}. Сайт підлаштовано.',
26
+ typeNormal: 'нормальний зір',
27
+ typeDeuteranopia: 'дальтонізм на зелений (deuteranopia)',
28
+ typeProtanopia: 'дальтонізм на червоний (protanopia)',
29
+ typeTritanopia: 'дальтонізм на синій (tritanopia)',
30
+ typeMonochromacy: 'повна відсутність кольорового зору (monochromacy)',
31
+ selectType: 'Оберіть ваш тип зору:',
32
+ deuteranopia: 'Не розрізняю зелений',
33
+ protanopia: 'Не розрізняю червоний',
34
+ tritanopia: 'Не розрізняю синій',
35
+ monochromacy: 'Чорно-білий зір',
36
+ apply: 'Застосувати',
37
+ reset: 'Скинути налаштування',
38
+ severity: 'Ступінь:',
39
+ mild: 'Легка',
40
+ moderate: 'Помірна',
41
+ severe: 'Сильна',
42
+ },
43
+ en: {
44
+ toggleText: 'Colorblind',
45
+ toggleLabel: 'Adapt website for colorblind users',
46
+ closeLabel: 'Close panel',
47
+ panelTitle: 'Colorblind Settings',
48
+ panelSubtitle: 'Take a quick test or select your vision type.',
49
+ testTab: 'Test',
50
+ manualTab: 'Manual',
51
+ startTest: 'Start test',
52
+ nextQuestion: 'Next',
53
+ question1: 'What color do you see in the center?',
54
+ question2: 'What number do you see?',
55
+ question3: 'Which square is brighter?',
56
+ optRedGreen: 'Red / Green',
57
+ optBlueYellow: 'Blue / Yellow',
58
+ optNone: 'No difference',
59
+ optNumber74: '74',
60
+ optNumber21: '21',
61
+ optNoNumber: 'No number',
62
+ optLeft: 'Left',
63
+ optRight: 'Right',
64
+ optSame: 'Same',
65
+ resultTitle: 'Test result',
66
+ resultDesc: 'It appears you have {type}. The site has been adapted.',
67
+ typeNormal: 'normal vision',
68
+ typeDeuteranopia: 'green-blindness (deuteranopia)',
69
+ typeProtanopia: 'red-blindness (protanopia)',
70
+ typeTritanopia: 'blue-blindness (tritanopia)',
71
+ typeMonochromacy: 'complete color blindness (monochromacy)',
72
+ selectType: 'Select your vision type:',
73
+ deuteranopia: 'Cannot distinguish green',
74
+ protanopia: 'Cannot distinguish red',
75
+ tritanopia: 'Cannot distinguish blue',
76
+ monochromacy: 'Black and white vision',
77
+ apply: 'Apply',
78
+ reset: 'Reset settings',
79
+ severity: 'Severity:',
80
+ mild: 'Mild',
81
+ moderate: 'Moderate',
82
+ severe: 'Severe',
83
+ },
84
+ de: {
85
+ toggleText: 'Farbenblindheit',
86
+ toggleLabel: 'Website für Farbenblinde anpassen',
87
+ closeLabel: 'Schließen',
88
+ panelTitle: 'Farbenblind-Einstellungen',
89
+ panelSubtitle: 'Machen Sie einen schnellen Test oder wählen Sie Ihren Sehtyp.',
90
+ testTab: 'Test',
91
+ manualTab: 'Manuell',
92
+ startTest: 'Test starten',
93
+ nextQuestion: 'Weiter',
94
+ question1: 'Welche Farbe sehen Sie in der Mitte?',
95
+ question2: 'Welche Zahl sehen Sie?',
96
+ question3: 'Welches Quadrat ist heller?',
97
+ optRedGreen: 'Rot / Grün',
98
+ optBlueYellow: 'Blau / Gelb',
99
+ optNone: 'Kein Unterschied',
100
+ optNumber74: '74',
101
+ optNumber21: '21',
102
+ optNoNumber: 'Keine Zahl',
103
+ optLeft: 'Links',
104
+ optRight: 'Rechts',
105
+ optSame: 'Gleich',
106
+ resultTitle: 'Testergebnis',
107
+ resultDesc: 'Sie haben wahrscheinlich {type}. Die Website wurde angepasst.',
108
+ typeNormal: 'normales Sehen',
109
+ typeDeuteranopia: 'Grünblindheit (Deuteranopie)',
110
+ typeProtanopia: 'Rotblindheit (Protanopie)',
111
+ typeTritanopia: 'Blaublindheit (Tritanopie)',
112
+ typeMonochromacy: 'vollständige Farbenblindheit (Monochromasie)',
113
+ selectType: 'Wählen Sie Ihren Sehtyp:',
114
+ deuteranopia: 'Kann Grün nicht unterscheiden',
115
+ protanopia: 'Kann Rot nicht unterscheiden',
116
+ tritanopia: 'Kann Blau nicht unterscheiden',
117
+ monochromacy: 'Schwarz-Weiß-Sehen',
118
+ apply: 'Anwenden',
119
+ reset: 'Zurücksetzen',
120
+ severity: 'Stärke:',
121
+ mild: 'Leicht',
122
+ moderate: 'Mäßig',
123
+ severe: 'Stark',
124
+ },
125
+ }
126
+
127
+ export function detectLang() {
128
+ const html = document.documentElement
129
+ const htmlLang = (html.lang || navigator.language || 'en').slice(0, 2).toLowerCase()
130
+ return htmlLang in locales ? htmlLang : 'en'
131
+ }