PageSourceSearch

https://untangledpaththerapy.com/a11y.js

js untangledpaththerapy.com collected 2026-10-05 11:04:45 UTC 6,623 bytes, 185 lines download raw bytes

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">&times;</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.