PageSourceSearch

https://www.prabhubank.com/assets/frontend/js/a11y/accessibility.js

js prabhubank.com collected 2026-10-02 03:40:31 UTC 18,055 bytes, 396 lines download raw bytes

1/* global localStorage */
2/* Prabhu Bank — Accessibility Widget Controller
3   WCAG 2.1 AA | Runs in <head> for FOAC prevention */
4(function () {
5    'use strict';
6
7    var STORAGE_KEY = 'prabhu_a11y';
8    var html = document.documentElement;
9
10    var DEFAULTS = {
11        contrast:        'normal',
12        tintColor:       '#ffff00',
13        tintOpacity:     0,
14        saturation:      100,
15        fontSize:        '1',
16        highlightTitles: false,
17        highlightLinks:  false,
18        dyslexiaFont:    false,
19        letterSpacing:   false,
20        lineHeight:      false,
21        fontWeight:      false,
22        readingGuide:    false,
23        stopAnimations:  false,
24        bigCursor:       false
25    };
26
27    var FONT_OPTIONS = [
28        { scale: '1',    label: 'A'  },
29        { scale: '1.15', label: 'A+' },
30        { scale: '1.3',  label: 'A++'}
31    ];
32
33    /* ---- Persistence ---- */
34
35    function loadPrefs() {
36        try {
37            var raw = localStorage.getItem(STORAGE_KEY);
38            return raw ? JSON.parse(raw) : clone(DEFAULTS);
39        } catch (e) {
40            return clone(DEFAULTS);
41        }
42    }
43
44    function savePrefs(p) {
45        try { localStorage.setItem(STORAGE_KEY, JSON.stringify(p)); } catch (e) { /* ignore */ }
46    }
47
48    function clone(obj) {
49        return JSON.parse(JSON.stringify(obj));
50    }
51
52    /* ---- Apply functions (safe to call before body exists) ---- */
53
54    function applyContrast(mode) {
55        html.classList.remove('a11y-high-contrast', 'a11y-inverted');
56        if (mode === 'high-contrast') html.classList.add('a11y-high-contrast');
57        if (mode === 'inverted')      html.classList.add('a11y-inverted');
58        updateBodyFilter();
59    }
60
61    /* Combines greyscale contrast + saturation into a single CSS filter on body children */
62    function updateBodyFilter() {
63        var filters = [];
64        if (prefs.contrast === 'grayscale') filters.push('grayscale(1)');
65        var sat = prefs.saturation !== undefined ? parseInt(prefs.saturation, 10) : 100;
66        if (sat < 100) filters.push('saturate(' + sat + '%)');
67        html.style.setProperty('--a11y-body-filter', filters.join(' ') || 'none');
68        html.classList.toggle('a11y-body-filtered', filters.length > 0);
69    }
70
71    function applyFontScale(scale) {
72        var s = parseFloat(scale) || 1;
73        html.style.setProperty('--a11y-font-scale', String(s));
74        html.classList.toggle('a11y-font-scaled', s !== 1);
75    }
76
77    function applyTint(color, opacity) {
78        var overlay = document.getElementById('a11y-tint-overlay');
79        if (!overlay) return;
80        overlay.style.backgroundColor = opacity > 0 ? color : 'transparent';
81        overlay.style.opacity = opacity > 0 ? opacity : 0;
82    }
83
84    function applySaturation(value) {
85        prefs.saturation = value !== undefined ? parseInt(value, 10) : 100;
86        updateBodyFilter();
87    }
88
89    function applyHighlightTitles(on)  { html.classList.toggle('a11y-highlight-titles', !!on); }
90    function applyHighlightLinks(on)   { html.classList.toggle('a11y-highlight-links', !!on); }
91    function applyDyslexiaFont(on)     { html.classList.toggle('a11y-dyslexia-font', !!on); }
92    function applyLetterSpacing(on)    { html.classList.toggle('a11y-letter-spacing', !!on); }
93    function applyLineHeight(on)       { html.classList.toggle('a11y-line-height', !!on); }
94    function applyFontWeight(on)       { html.classList.toggle('a11y-font-weight', !!on); }
95    function applyStopAnimations(on)   { html.classList.toggle('a11y-stop-animations', !!on); }
96    function applyReadingGuide(on)     { html.classList.toggle('a11y-reading-guide-active', !!on); }
97    function applyBigCursor(on)        { html.classList.toggle('a11y-big-cursor', !!on); }
98
99    /* ---- Early apply (before DOMContentLoaded, prevents FOAC) ---- */
100    var prefs = loadPrefs();
101    applyContrast(prefs.contrast);
102    applyFontScale(prefs.fontSize);
103    applyStopAnimations(prefs.stopAnimations);
104    /* tint, saturation, and overlay features need body — deferred to DOMContentLoaded */
105
106    /* ---- Widget build (after DOM ready) ---- */
107    document.addEventListener('DOMContentLoaded', function () {
108
109        applySaturation(prefs.saturation);
110
111        /* Inject tint overlay div */
112        var overlay = document.createElement('div');
113        overlay.id = 'a11y-tint-overlay';
114        document.body.appendChild(overlay);
115        applyTint(prefs.tintColor, prefs.tintOpacity);
116
117        /* Inject reading guide div */
118        var guide = document.createElement('div');
119        guide.id = 'a11y-reading-guide';
120        document.body.appendChild(guide);
121
122        document.addEventListener('mousemove', function (e) {
123            if (prefs.readingGuide) { guide.style.top = e.clientY + 'px'; }
124        });
125        document.addEventListener('touchmove', function (e) {
126            if (prefs.readingGuide && e.touches.length) {
127                guide.style.top = e.touches[0].clientY + 'px';
128            }
129        }, { passive: true });
130
131        /* Inject big cursor div — disabled on touch-only devices */
132        var bigCursorEl = document.createElement('div');
133        bigCursorEl.id = 'a11y-big-cursor-el';
134        document.body.appendChild(bigCursorEl);
135
136        if (window.matchMedia('(pointer: coarse)').matches) {
137            prefs.bigCursor = false;
138        } else {
139            document.addEventListener('mousemove', function (e) {
140                if (prefs.bigCursor) {
141                    bigCursorEl.style.transform = 'translate(' + e.clientX + 'px,' + e.clientY + 'px)';
142                }
143            });
144        }
145
146        applyHighlightTitles(prefs.highlightTitles);
147        applyHighlightLinks(prefs.highlightLinks);
148        applyDyslexiaFont(prefs.dyslexiaFont);
149        applyLetterSpacing(prefs.letterSpacing);
150        applyLineHeight(prefs.lineHeight);
151        applyFontWeight(prefs.fontWeight);
152        applyReadingGuide(prefs.readingGuide);
153        applyBigCursor(prefs.bigCursor);
154
155        buildWidget();
156    });
157
158    function buildWidget() {
159        var widget = document.createElement('div');
160        widget.id = 'a11y-widget';
161        widget.innerHTML =
162            '<div id="a11y-panel" role="dialog" aria-label="Accessibility Controls" aria-modal="false">' +
163            '  <p class="a11y-panel-title">Accessibility</p>' +
164
165            '  <div class="a11y-section">' +
166            '    <span class="a11y-section-label" id="a11y-contrast-label">Colour Contrast</span>' +
167            '    <div class="a11y-btn-group a11y-btn-group-2col" role="group" aria-labelledby="a11y-contrast-label">' +
168            '      <button class="a11y-btn" data-contrast="normal"        aria-pressed="false">Normal</button>' +
169            '      <button class="a11y-btn" data-contrast="high-contrast" aria-pressed="false">High Contrast</button>' +
170            '      <button class="a11y-btn" data-contrast="inverted"      aria-pressed="false">Inverted</button>' +
171            '      <button class="a11y-btn" data-contrast="grayscale"     aria-pressed="false">Grayscale</button>' +
172            '    </div>' +
173            '  </div>' +
174
175            '  <div class="a11y-section">' +
176            '    <span class="a11y-section-label">Colour Tint</span>' +
177            '    <div class="a11y-control-row">' +
178            '      <label for="a11y-tint-color">Colour</label>' +
179            '      <input type="color" id="a11y-tint-color" value="' + esc(prefs.tintColor) + '" aria-label="Tint colour picker">' +
180            '    </div>' +
181            '    <div class="a11y-control-row">' +
182            '      <label for="a11y-tint-opacity">Opacity</label>' +
183            '      <input type="range" id="a11y-tint-opacity" class="a11y-slider"' +
184            '             min="0" max="0.4" step="0.01" value="' + (prefs.tintOpacity || 0) + '"' +
185            '             aria-label="Tint opacity">' +
186            '    </div>' +
187            '  </div>' +
188
189            '  <div class="a11y-section">' +
190            '    <span class="a11y-section-label">Saturation</span>' +
191            '    <div class="a11y-control-row">' +
192            '      <label for="a11y-saturation">Level</label>' +
193            '      <input type="range" id="a11y-saturation" class="a11y-slider"' +
194            '             min="0" max="100" step="1" value="' + (prefs.saturation !== undefined ? prefs.saturation : 100) + '"' +
195            '             aria-label="Colour saturation (0 = greyscale, 100 = full colour)">' +
196            '    </div>' +
197            '  </div>' +
198
199            '  <div class="a11y-section">' +
200            '    <span class="a11y-section-label" id="a11y-font-label">Text Size</span>' +
201            '    <div class="a11y-btn-group" role="group" aria-labelledby="a11y-font-label">' +
202            FONT_OPTIONS.map(function (f) {
203                return '<button class="a11y-btn" data-font="' + f.scale + '" aria-pressed="false">' + f.label + '</button>';
204            }).join('') +
205            '    </div>' +
206            '  </div>' +
207
208            '  <span class="a11y-cat-header">' +
209            '    <i class="fas fa-pen-nib" aria-hidden="true"></i> Content Adjustment' +
210            '  </span>' +
211            '  <div class="a11y-section">' +
212            '    <div class="a11y-btn-group a11y-btn-group-2col" role="group" aria-label="Content adju
212stment options">' +
213            '      <button class="a11y-btn" data-toggle="highlightTitles" aria-pressed="false"><i class="fas fa-heading" aria-hidden="true"></i> Highlight Titles</button>' +
214            '      <button class="a11y-btn" data-toggle="highlightLinks"  aria-pressed="false"><i class="fas fa-link"         aria-hidden="true"></i> Highlight Links</button>' +
215            '      <button class="a11y-btn" data-toggle="dyslexiaFont"    aria-pressed="false"><i class="fas fa-font"         aria-hidden="true"></i> Dyslexia Font</button>' +
216            '      <button class="a11y-btn" data-toggle="letterSpacing"   aria-pressed="false"><i class="fas fa-arrows-alt-h" aria-hidden="true"></i> Letter Spacing</button>' +
217            '      <button class="a11y-btn" data-toggle="lineHeight"      aria-pressed="false"><i class="fas fa-text-height"  aria-hidden="true"></i> Line Height</button>' +
218            '      <button class="a11y-btn" data-toggle="fontWeight"      aria-pressed="false"><i class="fas fa-bold"         aria-hidden="true"></i> Bold Text</button>' +
219            '    </div>' +
220            '  </div>' +
221
222            '  <span class="a11y-cat-header">' +
223            '    <i class="fas fa-tools" aria-hidden="true"></i> Tools' +
224            '  </span>' +
225            '  <div class="a11y-section">' +
226            '    <div class="a11y-btn-group a11y-btn-group-2col" role="group" aria-label="Accessibility tools">' +
227            '      <button class="a11y-btn" data-toggle="readingGuide"   aria-pressed="false"><i class="fas fa-ruler-horizontal" aria-hidden="true"></i> Reading Guide</button>' +
228            '      <button class="a11y-btn" data-toggle="stopAnimations" aria-pressed="false"><i class="fas fa-pause-circle"     aria-hidden="true"></i> Stop Animations</button>' +
229            '      <button class="a11y-btn" data-toggle="bigCursor"      aria-pressed="false"><i class="fas fa-mouse-pointer"    aria-hidden="true"></i> Big Cursor</button>' +
230            '    </div>' +
231            '  </div>' +
232
233            '  <button id="a11y-reset" aria-label="Reset all accessibility settings to defaults">Reset</button>' +
234            '</div>' +
235
236            '<button id="a11y-toggle" aria-expanded="false" aria-controls="a11y-panel"' +
237            '        aria-label="Open accessibility controls" title="Accessibility">' +
238            '  <i class="fas fa-universal-access" aria-hidden="true"></i>' +
239            '</button>';
240
241        document.body.appendChild(widget);
242
243        var toggle = document.getElementById('a11y-toggle');
244        var panel  = document.getElementById('a11y-panel');
245
246        syncActiveStates();
247
248        /* Toggle panel open/close */
249        toggle.addEventListener('click', function () {
250            var open = panel.classList.toggle('a11y-open');
251            toggle.setAttribute('aria-expanded', String(open));
252            if (open) {
253                var first = panel.querySelector('button, input');
254                if (first) first.focus();
255            }
256        });
257
258        /* Nav toggle (mobile only) — wired to the same panel */
259        var navToggle = document.getElementById('a11y-nav-toggle');
260        if (navToggle) {
261            navToggle.addEventListener('click', function () {
262                var open = panel.classList.toggle('a11y-open');
263                toggle.setAttribute('aria-expanded', String(open));
264                navToggle.setAttribute('aria-expanded', String(open));
265                if (open) {
266                    var first = panel.querySelector('button, input');
267                    if (first) first.focus();
268                }
269            });
270        }
271
272        /* Close on Escape — syncs aria-expanded on both buttons */
273        document.addEventListener('keydown', function (e) {
274            if ((e.key === 'Escape' || e.keyCode === 27) && panel.classList.contains('a11y-open')) {
275                panel.classList.remove('a11y-open');
276                toggle.setAttribute('aria-expanded', 'false');
277                if (navToggle) navToggle.setAttribute('aria-expanded', 'false');
278                toggle.focus();
279            }
280        });
281
282        /* Contrast buttons */
283        panel.querySelectorAll('[data-contrast]').forEach(function (btn) {
284            btn.addEventListener('click', function () {
285                prefs.contrast = btn.getAttribute('data-contrast');
286                applyContrast(prefs.contrast);
287                savePrefs(prefs);
288                syncActiveStates();
289            });
290        });
291
292        /* Tint colour */
293        document.getElementById('a11y-tint-color').addEventListener('input', function () {
294            prefs.tintColor = this.value;
295            applyTint(prefs.tintColor, prefs.tintOpacity);
296            savePrefs(prefs);
297        });
298
299        /* Tint opacity */
300        document.getElementById('a11y-tint-opacity').addEventListener('input', function () {
301            prefs.tintOpacity = parseFloat(this.value);
302            applyTint(prefs.tintColor, prefs.tintOpacity);
303            savePrefs(prefs);
304        });
305
306        /* Saturation */
307        document.getElementById('a11y-saturation').addEventListener('input', function () {
308            prefs.saturation = parseInt(this.value, 10);
309            applySaturation(prefs.saturation);
310            savePrefs(prefs);
311        });
312
313        /* Font size buttons */
314        panel.querySelectorAll('[data-font]').forEach(function (btn) {
315            btn.addEventListener('click', function () {
316                prefs.fontSize = btn.getAttribute('data-font');
317                applyFontScale(prefs.fontSize);
318                savePrefs(prefs);
319                syncActiveStates();
320            });
321        });
322
323        /* applyFnMap — maps data-toggle key → apply function */
324        var applyFnMap = {
325            highlightTitles: applyHighlightTitles,
326            highlightLinks:  applyHighlightLinks,
327            dyslexiaFont:    applyDyslexiaFont,
328            letterSpacing:   applyLetterSpacing,
329            lineHeight:      applyLineHeight,
330            fontWeight:      applyFontWeight,
331            readingGuide:    applyReadingGuide,
332            stopAnimations:  applyStopAnimations,
333            bigCursor:       applyBigCursor
334        };
335
336        /* Content Adjustment + Tools toggle buttons */
337        panel.querySelectorAll('[data-toggle]').forEach(function (btn) {
338            btn.addEventListener('click', function () {
339                var key = btn.getAttribute('data-toggle');
340                prefs[key] = !prefs[key];
341                applyFnMap[key](prefs[key]);
342                savePrefs(prefs);
343                syncActiveStates();
344            });
345        });
346
347        /* Reset */
348        document.getElementById('a11y-reset').addEventListener('click', function () {
349            prefs = clone(DEFAULTS);
350            savePrefs(prefs);
351            applyContrast(prefs.contrast);
352            applyFontScale(prefs.fontSize);
353            applySaturation(prefs.saturation);
354            applyTint(prefs.tintColor, prefs.tintOpacity);
355            document.getElementById('a11y-tint-color').value   = prefs.tintColor;
356            document.getElementById('a11y-tint-opacity').value = prefs.tintOpacity;
357            document.getElementById('a11y-saturation').value   = prefs.saturation;
358            applyHighlightTitles(false);
359            applyHighlightLinks(false);
360            applyDyslexiaFont(false);
361            applyLetterSpacing(false);
362            applyLineHeight(false);
363            applyFontWeight(false);
364            applyReadingGuide(false);
365            applyStopAnimations(false);
366            applyBigCursor(false);
367            syncActiveStates();
368        });
369    }
370
371    function syncActiveStates() {
372        var widget = document.getElementById('a11y-widget');
373        if (!widget) return;
374        widget.querySelectorAll('[data-contrast]').forEach(function (btn) {
375            var active = btn.getAttribute('data-contrast') === (prefs.contrast || 'normal');
376            btn.classList.toggle('a11y-active', active);
377            btn.setAttribute('aria-pressed', String(active));
378        });
379        widget.querySelectorAll('[data-font]').forEach(function (btn) {
380            var active = btn.getAttribute('data-font') === (prefs.fontSize || '1');
381            btn.classList.toggle('a11y-active', active);
382            btn.setAttribute('aria-pressed', String(active));
383        });
384        widget.querySelectorAll('[data-toggle]').forEach(function (btn) {
385            var key = btn.getAttribute('data-toggle');
386            var active = !!prefs[key];
387            btn.classList.toggle('a11y-active', active);
388            btn.setAttribute('aria-pressed', String(active));
389        });
390    }
391
392    function esc(str) {
393        return String(str).replace(/"/g, '&quot;');
394    }
395
396}());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.