1'use strict'; 2 3/* ================================================================ 4 ACCESSIBILITY PREFERENCES PANEL 5 Persists user choices to localStorage. Offers real controls only â 6 it does not claim compliance or auto-remediate the page. 7 ================================================================ */ 8 9(function () { 10 var KEY = 'upt-a11y'; 11 var root = document.documentElement; 12 13 var DEFAULTS = { 14 text: 'normal', // normal | large | larger 15 motion: 'on', // on | off 16 contrast: 'off', // off | on 17 underline: 'off' // off | on 18 }; 19 20 function read() { 21 try { 22 var raw = localStorage.getItem(KEY); 23 if (!raw) return Object.assign({}, DEFAULTS); 24 return Object.assign({}, DEFAULTS, JSON.parse(raw)); 25 } catch (e) { 26 return Object.assign({}, DEFAULTS); 27 } 28 } 29 30 function write(p) { 31 try { localStorage.setItem(KEY, JSON.stringify(p)); } catch (e) {} 32 } 33 34 function apply(p) { 35 root.setAttribute('data-a11y-text', p.text); 36 root.setAttribute('data-a11y-motion', p.motion); 37 root.setAttribute('data-a11y-contrast', p.contrast); 38 root.setAttribute('data-a11y-underline', p.underline); 39 } 40 41 var prefs = read(); 42 apply(prefs); // applied immediately â see the inline head snippet 43 44 /* ---- Build the UI ---- */ 45 function el(tag, attrs, html) { 46 var n = document.createElement(tag); 47 if (attrs) Object.keys(attrs).forEach(function (k) { n.setAttribute(k, attrs[k]); }); 48 if (html != null) n.innerHTML = html; 49 return n; 50 } 51 52 var ICON = 53 '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" ' + 54 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + 55 '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="7.5" r="1.3" fill="currentColor" stroke="none"/>' + 56 '<path d="M5.5 10.5c4 1.2 9 1.2 13 0"/><path d="M12 10.8v4"/>
56' + 57 '<path d="M12 14.8l-2.6 4.2"/><path d="M12 14.8l2.6 4.2"/></svg>'; 58 59 var btn = el('button', { 60 'class': 'a11y-btn', 61 'type': 'button', 62 'aria-expanded': 'false', 63 'aria-controls': 'a11y-panel', 64 'aria-label': 'Accessibility preferences' 65 }, ICON); 66 67 var panel = el('div', { 68 'class': 'a11y-panel', 69 'id': 'a11y-panel', 70 'role': 'dialog', 71 'aria-modal': 'false', 72 'aria-labelledby': 'a11y-title', 73 'data-open': 'false' 74 }, 75 '<div class="a11y-panel__head">' + 76 '<h2 id="a11y-title">Display options</h2>' + 77 '<button type="button" class="a11y-close" aria-label="Close accessibility preferences">×</button>' + 78 '</div>' + 79 '<p class="a11y-panel__note">These settings change how this site looks for you, and are saved on this device.</p>' + 80 81 '<div class="a11y-group">' + 82 '<span class="a11y-group__label" id="a11y-text-label">Text size</span>' + 83 '<div class="a11y-seg" role="group" aria-labelledby="a11y-text-label">' + 84 '<button type="button" data-text="normal" aria-pressed="false">Normal</button>' + 85 '<button type="button" data-text="large" aria-pressed="false">Large</button>' + 86 '<button type="button" data-text="larger" aria-pressed="false">Larger</button>' + 87 '</div>' + 88 '</div>' + 89 90 '<div class="a11y-group">' + 91 '<button type="button" class="a11y-toggle" data-toggle="motion" aria-pressed="false">' + 92 '<span>Reduce motion</span><span class="a11y-switch" aria-hidden="true"></span>' + 93 '</button>' + 94 '<button type="button" class="a11y-toggle" data-toggle="contrast" aria-pressed="false">' + 95 '<span>Higher contrast</span><span class="a11y-switch" aria-hidden="true"></span>' + 96 '</button>' + 97 '<button type="button" class="a11y-toggle" data-toggle="underline" aria-pressed="false">' + 98 '<span>Underline links</span><span class="a11y-switch" aria-hidden="true"></span>' + 99 '</button>' + 100 '</div>' + 101 102 '<button type="button" class="a11y-reset">Reset to defaults</button>' 103 ); 104 105 document.body.appendChild(btn); 106 document.body.appendChild(panel); 107 108 /* ---- Sync UI to state ---- */ 109 function sync() { 110 panel.querySelectorAll('[data-text]').forEach(function (b) { 111 b.setAttribute('aria-pressed', String(b.dataset.text === prefs.text)); 112 }); 113 panel.querySelectorAll('[data-toggle]').forEach(function (b) { 114 var k = b.dataset.toggle; 115 var on = (k === 'motion') ? prefs.motion === 'off' : prefs[k] === 'on'; 116 b.setAttribute('aria-pressed', String(on)); 117 }); 118 } 119 sync(); 120 121 function commit() { apply(prefs); write(prefs); sync(); } 122 123 /* ---- Open / close ---- */ 124 var lastFocus = null; 125 126 function open() { 127 lastFocus = document.activeElement; 128 panel.setAttribute('data-open', 'true'); 129 btn.setAttribute('aria-expanded', 'true'); 130 var first = panel.querySelector('button'); 131 if (first) first.focus(); 132 } 133 134 function close(restore) { 135 panel.setAttribute('data-open', 'false'); 136 btn.setAttribute('aria-expanded', 'false'); 137 if (restore !== false && lastFocus) lastFocus.focus(); 138 } 139 140 btn.addEventListener('click', function () { 141 panel.getAttribute('data-open') === 'true' ? close() : open(); 142 }); 143 144 panel.querySelector('.a11y-close').addEventListener('click', function () { close(); }); 145 146 /* ---- Controls ---- */ 147 panel.addEventListener('click', function (e) { 148 var seg = e.target.closest('[data-text]'); 149 if (seg) { prefs.text = seg.dataset.text; commit(); return; } 150 151 var tog = e.target.closest('[data-toggle]'); 152 if (tog) { 153 var k = tog.dataset.toggle; 154 if (k === 'motion') prefs.motion = prefs.motion === 'off' ? 'on' : 'off'; 155 else prefs[k] = prefs[k] === 'on' ? 'off' : 'on'; 156 commit(); 157 return; 158 } 159 160 if (e.target.closest('.a11y-reset')) { 161 prefs = Object.assign({}, DEFAULTS); 162 commit(); 163 } 164 }); 165 166 /* ---- Dismiss ---- */ 167 document.addEventListener('keydown', function (e) { 168 if (e.key === 'Escape' && panel.getAttribute('data-open') === 'true') close(); 169 }); 170 171 document.addEventListener('click', function (e) { 172 if (panel.getAttribute('data-open') !== 'true') return; 173 if (!panel.contains(e.target) && !btn.contains(e.target)) close(false); 174 }); 175 176 /* Keep focus inside the panel while it's open */ 177 panel.addEventListener('keydown', function (e) { 178 if (e.key !== 'Tab') return; 179 var f = panel.querySelectorAll('button'); 180 if (!f.length) return; 181 var first = f[0], last = f[f.length - 1]; 182 if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } 183 else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } 184 }); 185})();
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.