accessify-widget 0.1.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.
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/dist/accessify.min.js +2 -0
- package/dist/accessify.min.js.map +1 -0
- package/dist/accessify.mjs +6 -0
- package/dist/accessify.mjs.map +1 -0
- package/dist/alt-text-CLxbmwG6.js +567 -0
- package/dist/alt-text-CLxbmwG6.js.map +1 -0
- package/dist/animation-stop-DXebPS8D.js +88 -0
- package/dist/animation-stop-DXebPS8D.js.map +1 -0
- package/dist/auto-scan-pg-09o7A.js +885 -0
- package/dist/auto-scan-pg-09o7A.js.map +1 -0
- package/dist/big-cursor-B2UKu9dQ.js +88 -0
- package/dist/big-cursor-B2UKu9dQ.js.map +1 -0
- package/dist/color-blind-0LFng55r.js +108 -0
- package/dist/color-blind-0LFng55r.js.map +1 -0
- package/dist/contrast-DCkE0NXZ.js +64 -0
- package/dist/contrast-DCkE0NXZ.js.map +1 -0
- package/dist/dyslexia-font-wONgIy2T.js +77 -0
- package/dist/dyslexia-font-wONgIy2T.js.map +1 -0
- package/dist/focus-highlight-CjERyyUF.js +93 -0
- package/dist/focus-highlight-CjERyyUF.js.map +1 -0
- package/dist/hide-images-DJwmsV2C.js +39 -0
- package/dist/hide-images-DJwmsV2C.js.map +1 -0
- package/dist/index-CUQfpnwR.js +6520 -0
- package/dist/index-CUQfpnwR.js.map +1 -0
- package/dist/keyboard-nav-BdPyLaZt.js +312 -0
- package/dist/keyboard-nav-BdPyLaZt.js.map +1 -0
- package/dist/line-height-BT98qgEF.js +54 -0
- package/dist/line-height-BT98qgEF.js.map +1 -0
- package/dist/link-highlight-DBGm067Y.js +87 -0
- package/dist/link-highlight-DBGm067Y.js.map +1 -0
- package/dist/loader.min.js +1 -0
- package/dist/page-structure-2X8mOSpC.js +166 -0
- package/dist/page-structure-2X8mOSpC.js.map +1 -0
- package/dist/reading-guide-VT8NciIL.js +122 -0
- package/dist/reading-guide-VT8NciIL.js.map +1 -0
- package/dist/reading-mask-BABChuCz.js +76 -0
- package/dist/reading-mask-BABChuCz.js.map +1 -0
- package/dist/saturation-D8ZXpWAN.js +59 -0
- package/dist/saturation-D8ZXpWAN.js.map +1 -0
- package/dist/spacing-DENai3JU.js +106 -0
- package/dist/spacing-DENai3JU.js.map +1 -0
- package/dist/text-align-BDRPqPvl.js +51 -0
- package/dist/text-align-BDRPqPvl.js.map +1 -0
- package/dist/text-simplify-CELklw5A.js +223 -0
- package/dist/text-simplify-CELklw5A.js.map +1 -0
- package/dist/text-size-B-uv436p.js +69 -0
- package/dist/text-size-B-uv436p.js.map +1 -0
- package/dist/tts-02b9iV0h.js +505 -0
- package/dist/tts-02b9iV0h.js.map +1 -0
- package/dist/widget.js +2 -0
- package/dist/widget.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
function createKeyboardNavModule() {
|
|
2
|
+
let enabled = false;
|
|
3
|
+
const STYLE_ID = "accessify-keyboard-nav";
|
|
4
|
+
const SKIP_LINK_ID = "accessify-skip-link";
|
|
5
|
+
const OVERLAY_ID = "accessify-keyboard-help";
|
|
6
|
+
const STORAGE_KEY = "accessify-keyboard-nav";
|
|
7
|
+
let headingIndex = -1;
|
|
8
|
+
let landmarkIndex = -1;
|
|
9
|
+
let headings = [];
|
|
10
|
+
let landmarks = [];
|
|
11
|
+
let helpOverlayVisible = false;
|
|
12
|
+
function findMainContent() {
|
|
13
|
+
return document.querySelector("main") || document.getElementById("content") || document.getElementById("main-content") || document.querySelector('[role="main"]') || document.querySelector("h1");
|
|
14
|
+
}
|
|
15
|
+
function hasSkipLink() {
|
|
16
|
+
const links = document.querySelectorAll('a[href^="#"]');
|
|
17
|
+
for (const link of links) {
|
|
18
|
+
const text = link.textContent?.toLowerCase() || "";
|
|
19
|
+
if (text.includes("skip") || text.includes("main content") || text.includes("navigation")) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function injectSkipLink() {
|
|
26
|
+
if (hasSkipLink()) return;
|
|
27
|
+
const target = findMainContent();
|
|
28
|
+
if (!target) return;
|
|
29
|
+
if (!target.id) {
|
|
30
|
+
target.id = "accessify-main-content";
|
|
31
|
+
}
|
|
32
|
+
const skipLink = document.createElement("a");
|
|
33
|
+
skipLink.id = SKIP_LINK_ID;
|
|
34
|
+
skipLink.href = `#${target.id}`;
|
|
35
|
+
skipLink.textContent = "Skip to main content";
|
|
36
|
+
skipLink.addEventListener("click", (e) => {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
target.setAttribute("tabindex", "-1");
|
|
39
|
+
target.focus();
|
|
40
|
+
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
41
|
+
});
|
|
42
|
+
document.body.insertBefore(skipLink, document.body.firstChild);
|
|
43
|
+
}
|
|
44
|
+
function removeSkipLink() {
|
|
45
|
+
const skipLink = document.getElementById(SKIP_LINK_ID);
|
|
46
|
+
skipLink?.remove();
|
|
47
|
+
}
|
|
48
|
+
function collectHeadings() {
|
|
49
|
+
headings = Array.from(
|
|
50
|
+
document.querySelectorAll("h1, h2, h3, h4, h5, h6")
|
|
51
|
+
).filter((el) => {
|
|
52
|
+
const style = window.getComputedStyle(el);
|
|
53
|
+
return style.display !== "none" && style.visibility !== "hidden";
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function navigateToNextHeading() {
|
|
57
|
+
collectHeadings();
|
|
58
|
+
if (headings.length === 0) return;
|
|
59
|
+
headingIndex = (headingIndex + 1) % headings.length;
|
|
60
|
+
const heading = headings[headingIndex];
|
|
61
|
+
heading.setAttribute("tabindex", "-1");
|
|
62
|
+
heading.focus();
|
|
63
|
+
heading.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
64
|
+
}
|
|
65
|
+
function collectLandmarks() {
|
|
66
|
+
const selectors = [
|
|
67
|
+
'header, [role="banner"]',
|
|
68
|
+
'nav, [role="navigation"]',
|
|
69
|
+
'main, [role="main"]',
|
|
70
|
+
'aside, [role="complementary"]',
|
|
71
|
+
'[role="search"]',
|
|
72
|
+
'[role="form"]',
|
|
73
|
+
'footer, [role="contentinfo"]',
|
|
74
|
+
'[role="region"][aria-label]'
|
|
75
|
+
];
|
|
76
|
+
landmarks = Array.from(
|
|
77
|
+
document.querySelectorAll(selectors.join(", "))
|
|
78
|
+
).filter((el) => {
|
|
79
|
+
const style = window.getComputedStyle(el);
|
|
80
|
+
return style.display !== "none" && style.visibility !== "hidden";
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function navigateToNextLandmark() {
|
|
84
|
+
collectLandmarks();
|
|
85
|
+
if (landmarks.length === 0) return;
|
|
86
|
+
landmarkIndex = (landmarkIndex + 1) % landmarks.length;
|
|
87
|
+
const landmark = landmarks[landmarkIndex];
|
|
88
|
+
landmark.setAttribute("tabindex", "-1");
|
|
89
|
+
landmark.focus();
|
|
90
|
+
landmark.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
91
|
+
}
|
|
92
|
+
function toggleHelpOverlay() {
|
|
93
|
+
const existing = document.getElementById(OVERLAY_ID);
|
|
94
|
+
if (existing) {
|
|
95
|
+
existing.remove();
|
|
96
|
+
helpOverlayVisible = false;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
helpOverlayVisible = true;
|
|
100
|
+
const overlay = document.createElement("div");
|
|
101
|
+
overlay.id = OVERLAY_ID;
|
|
102
|
+
overlay.setAttribute("role", "dialog");
|
|
103
|
+
overlay.setAttribute("aria-label", "Keyboard shortcuts");
|
|
104
|
+
overlay.setAttribute("aria-modal", "true");
|
|
105
|
+
overlay.innerHTML = `
|
|
106
|
+
<div style="
|
|
107
|
+
position: fixed; inset: 0; background: rgba(0,0,0,0.6);
|
|
108
|
+
display: flex; align-items: center; justify-content: center;
|
|
109
|
+
z-index: 2147483647;
|
|
110
|
+
">
|
|
111
|
+
<div style="
|
|
112
|
+
background: #fff; color: #222; border-radius: 12px; padding: 32px;
|
|
113
|
+
max-width: 480px; width: 90%; max-height: 80vh; overflow-y: auto;
|
|
114
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.3); font-family: system-ui, sans-serif;
|
|
115
|
+
" role="document">
|
|
116
|
+
<h2 style="margin: 0 0 16px; font-size: 20px; color: #1a73e8;">Keyboard Shortcuts</h2>
|
|
117
|
+
<table style="width: 100%; border-collapse: collapse; font-size: 14px;">
|
|
118
|
+
<thead>
|
|
119
|
+
<tr style="border-bottom: 2px solid #e0e0e0;">
|
|
120
|
+
<th style="text-align: left; padding: 8px 12px;">Shortcut</th>
|
|
121
|
+
<th style="text-align: left; padding: 8px 12px;">Action</th>
|
|
122
|
+
</tr>
|
|
123
|
+
</thead>
|
|
124
|
+
<tbody>
|
|
125
|
+
<tr style="border-bottom: 1px solid #f0f0f0;">
|
|
126
|
+
<td style="padding: 8px 12px;"><kbd style="background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;">Alt + H</kbd></td>
|
|
127
|
+
<td style="padding: 8px 12px;">Navigate to next heading</td>
|
|
128
|
+
</tr>
|
|
129
|
+
<tr style="border-bottom: 1px solid #f0f0f0;">
|
|
130
|
+
<td style="padding: 8px 12px;"><kbd style="background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;">Alt + L</kbd></td>
|
|
131
|
+
<td style="padding: 8px 12px;">Navigate to next landmark</td>
|
|
132
|
+
</tr>
|
|
133
|
+
<tr style="border-bottom: 1px solid #f0f0f0;">
|
|
134
|
+
<td style="padding: 8px 12px;"><kbd style="background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;">Alt + K</kbd></td>
|
|
135
|
+
<td style="padding: 8px 12px;">Toggle this help overlay</td>
|
|
136
|
+
</tr>
|
|
137
|
+
<tr style="border-bottom: 1px solid #f0f0f0;">
|
|
138
|
+
<td style="padding: 8px 12px;"><kbd style="background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;">Tab</kbd></td>
|
|
139
|
+
<td style="padding: 8px 12px;">Move to next focusable element</td>
|
|
140
|
+
</tr>
|
|
141
|
+
<tr>
|
|
142
|
+
<td style="padding: 8px 12px;"><kbd style="background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;">Escape</kbd></td>
|
|
143
|
+
<td style="padding: 8px 12px;">Close this overlay</td>
|
|
144
|
+
</tr>
|
|
145
|
+
</tbody>
|
|
146
|
+
</table>
|
|
147
|
+
<button style="
|
|
148
|
+
margin-top: 20px; padding: 8px 24px; background: #1a73e8; color: #fff;
|
|
149
|
+
border: none; border-radius: 6px; cursor: pointer; font-size: 14px;
|
|
150
|
+
" id="accessify-keyboard-help-close">Close</button>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
`;
|
|
154
|
+
document.body.appendChild(overlay);
|
|
155
|
+
const closeBtn = document.getElementById("accessify-keyboard-help-close");
|
|
156
|
+
closeBtn?.focus();
|
|
157
|
+
closeBtn?.addEventListener("click", () => {
|
|
158
|
+
overlay.remove();
|
|
159
|
+
helpOverlayVisible = false;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function injectTabindex() {
|
|
163
|
+
const selectors = [
|
|
164
|
+
"a[href]",
|
|
165
|
+
"button",
|
|
166
|
+
"input",
|
|
167
|
+
"select",
|
|
168
|
+
"textarea",
|
|
169
|
+
'[role="button"]',
|
|
170
|
+
'[role="link"]',
|
|
171
|
+
'[role="checkbox"]',
|
|
172
|
+
'[role="radio"]',
|
|
173
|
+
'[role="tab"]',
|
|
174
|
+
'[role="menuitem"]',
|
|
175
|
+
'[role="switch"]',
|
|
176
|
+
'[contenteditable="true"]',
|
|
177
|
+
"summary"
|
|
178
|
+
];
|
|
179
|
+
const elements = document.querySelectorAll(selectors.join(", "));
|
|
180
|
+
elements.forEach((el) => {
|
|
181
|
+
if (!el.hasAttribute("tabindex") && !isNativelyFocusable(el)) {
|
|
182
|
+
el.setAttribute("tabindex", "0");
|
|
183
|
+
el.dataset.accessifyTabindex = "true";
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function removeInjectedTabindex() {
|
|
188
|
+
const elements = document.querySelectorAll('[data-accessify-tabindex="true"]');
|
|
189
|
+
elements.forEach((el) => {
|
|
190
|
+
el.removeAttribute("tabindex");
|
|
191
|
+
delete el.dataset.accessifyTabindex;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function isNativelyFocusable(el) {
|
|
195
|
+
const tag = el.tagName.toLowerCase();
|
|
196
|
+
if (["a", "button", "input", "select", "textarea"].includes(tag)) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (tag === "a" && !el.href) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
function handleKeyDown(e) {
|
|
205
|
+
if (!enabled) return;
|
|
206
|
+
if (e.key === "Escape" && helpOverlayVisible) {
|
|
207
|
+
const overlay = document.getElementById(OVERLAY_ID);
|
|
208
|
+
overlay?.remove();
|
|
209
|
+
helpOverlayVisible = false;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!e.altKey) return;
|
|
213
|
+
switch (e.key.toLowerCase()) {
|
|
214
|
+
case "h":
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
navigateToNextHeading();
|
|
217
|
+
break;
|
|
218
|
+
case "l":
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
navigateToNextLandmark();
|
|
221
|
+
break;
|
|
222
|
+
case "k":
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
toggleHelpOverlay();
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function getStyles() {
|
|
229
|
+
return `
|
|
230
|
+
/* accessify keyboard navigation */
|
|
231
|
+
#${SKIP_LINK_ID} {
|
|
232
|
+
position: fixed;
|
|
233
|
+
top: -100px;
|
|
234
|
+
left: 50%;
|
|
235
|
+
transform: translateX(-50%);
|
|
236
|
+
background: #1a73e8;
|
|
237
|
+
color: #fff;
|
|
238
|
+
padding: 12px 24px;
|
|
239
|
+
border-radius: 0 0 8px 8px;
|
|
240
|
+
font-size: 16px;
|
|
241
|
+
font-family: system-ui, sans-serif;
|
|
242
|
+
text-decoration: none;
|
|
243
|
+
z-index: 2147483647;
|
|
244
|
+
transition: top 0.2s ease;
|
|
245
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
246
|
+
}
|
|
247
|
+
#${SKIP_LINK_ID}:focus {
|
|
248
|
+
top: 0;
|
|
249
|
+
outline: 3px solid #ffdd00;
|
|
250
|
+
outline-offset: 2px;
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
function injectStyles() {
|
|
255
|
+
let styleEl = document.getElementById(STYLE_ID);
|
|
256
|
+
if (!styleEl) {
|
|
257
|
+
styleEl = document.createElement("style");
|
|
258
|
+
styleEl.id = STYLE_ID;
|
|
259
|
+
document.head.appendChild(styleEl);
|
|
260
|
+
}
|
|
261
|
+
styleEl.textContent = getStyles();
|
|
262
|
+
}
|
|
263
|
+
function removeStyles() {
|
|
264
|
+
const styleEl = document.getElementById(STYLE_ID);
|
|
265
|
+
styleEl?.remove();
|
|
266
|
+
}
|
|
267
|
+
function activate() {
|
|
268
|
+
enabled = true;
|
|
269
|
+
headingIndex = -1;
|
|
270
|
+
landmarkIndex = -1;
|
|
271
|
+
injectStyles();
|
|
272
|
+
injectSkipLink();
|
|
273
|
+
injectTabindex();
|
|
274
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
275
|
+
localStorage.setItem(STORAGE_KEY, "true");
|
|
276
|
+
}
|
|
277
|
+
function deactivate() {
|
|
278
|
+
enabled = false;
|
|
279
|
+
helpOverlayVisible = false;
|
|
280
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
281
|
+
removeSkipLink();
|
|
282
|
+
removeInjectedTabindex();
|
|
283
|
+
removeStyles();
|
|
284
|
+
const overlay = document.getElementById(OVERLAY_ID);
|
|
285
|
+
overlay?.remove();
|
|
286
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
id: "keyboard-nav",
|
|
290
|
+
name: () => "Keyboard Navigation",
|
|
291
|
+
description: "Enhanced keyboard navigation with skip links, heading/landmark jumping, and shortcut help",
|
|
292
|
+
icon: "keyboard-nav",
|
|
293
|
+
category: "motor",
|
|
294
|
+
activate,
|
|
295
|
+
deactivate,
|
|
296
|
+
getState: () => ({
|
|
297
|
+
id: "keyboard-nav",
|
|
298
|
+
enabled
|
|
299
|
+
}),
|
|
300
|
+
setState: (state) => {
|
|
301
|
+
if (state.enabled) {
|
|
302
|
+
activate();
|
|
303
|
+
} else {
|
|
304
|
+
deactivate();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
export {
|
|
310
|
+
createKeyboardNavModule as default
|
|
311
|
+
};
|
|
312
|
+
//# sourceMappingURL=keyboard-nav-BdPyLaZt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard-nav-BdPyLaZt.js","sources":["../src/features/keyboard-nav.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\n\nexport default function createKeyboardNavModule(): FeatureModule {\n let enabled = false;\n const STYLE_ID = 'accessify-keyboard-nav';\n const SKIP_LINK_ID = 'accessify-skip-link';\n const OVERLAY_ID = 'accessify-keyboard-help';\n const STORAGE_KEY = 'accessify-keyboard-nav';\n\n let headingIndex = -1;\n let landmarkIndex = -1;\n let headings: HTMLElement[] = [];\n let landmarks: HTMLElement[] = [];\n let helpOverlayVisible = false;\n\n // --- Skip-to-content link ---\n\n function findMainContent(): HTMLElement | null {\n return (\n document.querySelector('main') ||\n document.getElementById('content') ||\n document.getElementById('main-content') ||\n document.querySelector('[role=\"main\"]') ||\n document.querySelector('h1')\n );\n }\n\n function hasSkipLink(): boolean {\n const links = document.querySelectorAll('a[href^=\"#\"]');\n for (const link of links) {\n const text = (link as HTMLElement).textContent?.toLowerCase() || '';\n if (text.includes('skip') || text.includes('main content') || text.includes('navigation')) {\n return true;\n }\n }\n return false;\n }\n\n function injectSkipLink() {\n if (hasSkipLink()) return;\n const target = findMainContent();\n if (!target) return;\n\n // Ensure target has an ID for the skip link to reference\n if (!target.id) {\n target.id = 'accessify-main-content';\n }\n\n const skipLink = document.createElement('a');\n skipLink.id = SKIP_LINK_ID;\n skipLink.href = `#${target.id}`;\n skipLink.textContent = 'Skip to main content';\n skipLink.addEventListener('click', (e) => {\n e.preventDefault();\n target.setAttribute('tabindex', '-1');\n target.focus();\n target.scrollIntoView({ behavior: 'smooth', block: 'start' });\n });\n\n document.body.insertBefore(skipLink, document.body.firstChild);\n }\n\n function removeSkipLink() {\n const skipLink = document.getElementById(SKIP_LINK_ID);\n skipLink?.remove();\n }\n\n // --- Heading navigation (Alt+H) ---\n\n function collectHeadings() {\n headings = Array.from(\n document.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6')\n ).filter((el) => {\n // Only visible headings\n const style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n }\n\n function navigateToNextHeading() {\n collectHeadings();\n if (headings.length === 0) return;\n headingIndex = (headingIndex + 1) % headings.length;\n const heading = headings[headingIndex];\n heading.setAttribute('tabindex', '-1');\n heading.focus();\n heading.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }\n\n // --- Landmark navigation (Alt+L) ---\n\n function collectLandmarks() {\n const selectors = [\n 'header, [role=\"banner\"]',\n 'nav, [role=\"navigation\"]',\n 'main, [role=\"main\"]',\n 'aside, [role=\"complementary\"]',\n '[role=\"search\"]',\n '[role=\"form\"]',\n 'footer, [role=\"contentinfo\"]',\n '[role=\"region\"][aria-label]',\n ];\n landmarks = Array.from(\n document.querySelectorAll<HTMLElement>(selectors.join(', '))\n ).filter((el) => {\n const style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n }\n\n function navigateToNextLandmark() {\n collectLandmarks();\n if (landmarks.length === 0) return;\n landmarkIndex = (landmarkIndex + 1) % landmarks.length;\n const landmark = landmarks[landmarkIndex];\n landmark.setAttribute('tabindex', '-1');\n landmark.focus();\n landmark.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }\n\n // --- Keyboard shortcut help overlay (Alt+K) ---\n\n function toggleHelpOverlay() {\n const existing = document.getElementById(OVERLAY_ID);\n if (existing) {\n existing.remove();\n helpOverlayVisible = false;\n return;\n }\n\n helpOverlayVisible = true;\n const overlay = document.createElement('div');\n overlay.id = OVERLAY_ID;\n overlay.setAttribute('role', 'dialog');\n overlay.setAttribute('aria-label', 'Keyboard shortcuts');\n overlay.setAttribute('aria-modal', 'true');\n\n overlay.innerHTML = `\n <div style=\"\n position: fixed; inset: 0; background: rgba(0,0,0,0.6);\n display: flex; align-items: center; justify-content: center;\n z-index: 2147483647;\n \">\n <div style=\"\n background: #fff; color: #222; border-radius: 12px; padding: 32px;\n max-width: 480px; width: 90%; max-height: 80vh; overflow-y: auto;\n box-shadow: 0 8px 32px rgba(0,0,0,0.3); font-family: system-ui, sans-serif;\n \" role=\"document\">\n <h2 style=\"margin: 0 0 16px; font-size: 20px; color: #1a73e8;\">Keyboard Shortcuts</h2>\n <table style=\"width: 100%; border-collapse: collapse; font-size: 14px;\">\n <thead>\n <tr style=\"border-bottom: 2px solid #e0e0e0;\">\n <th style=\"text-align: left; padding: 8px 12px;\">Shortcut</th>\n <th style=\"text-align: left; padding: 8px 12px;\">Action</th>\n </tr>\n </thead>\n <tbody>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + H</kbd></td>\n <td style=\"padding: 8px 12px;\">Navigate to next heading</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + L</kbd></td>\n <td style=\"padding: 8px 12px;\">Navigate to next landmark</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + K</kbd></td>\n <td style=\"padding: 8px 12px;\">Toggle this help overlay</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Tab</kbd></td>\n <td style=\"padding: 8px 12px;\">Move to next focusable element</td>\n </tr>\n <tr>\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Escape</kbd></td>\n <td style=\"padding: 8px 12px;\">Close this overlay</td>\n </tr>\n </tbody>\n </table>\n <button style=\"\n margin-top: 20px; padding: 8px 24px; background: #1a73e8; color: #fff;\n border: none; border-radius: 6px; cursor: pointer; font-size: 14px;\n \" id=\"accessify-keyboard-help-close\">Close</button>\n </div>\n </div>\n `;\n\n document.body.appendChild(overlay);\n\n // Focus management for the dialog\n const closeBtn = document.getElementById('accessify-keyboard-help-close');\n closeBtn?.focus();\n closeBtn?.addEventListener('click', () => {\n overlay.remove();\n helpOverlayVisible = false;\n });\n }\n\n // --- Tabindex injection for interactive elements ---\n\n function injectTabindex() {\n const selectors = [\n 'a[href]',\n 'button',\n 'input',\n 'select',\n 'textarea',\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[role=\"checkbox\"]',\n '[role=\"radio\"]',\n '[role=\"tab\"]',\n '[role=\"menuitem\"]',\n '[role=\"switch\"]',\n '[contenteditable=\"true\"]',\n 'summary',\n ];\n\n const elements = document.querySelectorAll<HTMLElement>(selectors.join(', '));\n elements.forEach((el) => {\n // Only add tabindex if the element is not already focusable via native mechanism\n // and does not already have an explicit tabindex\n if (!el.hasAttribute('tabindex') && !isNativelyFocusable(el)) {\n el.setAttribute('tabindex', '0');\n el.dataset.accessifyTabindex = 'true';\n }\n });\n }\n\n function removeInjectedTabindex() {\n const elements = document.querySelectorAll<HTMLElement>('[data-accessify-tabindex=\"true\"]');\n elements.forEach((el) => {\n el.removeAttribute('tabindex');\n delete el.dataset.accessifyTabindex;\n });\n }\n\n function isNativelyFocusable(el: HTMLElement): boolean {\n const tag = el.tagName.toLowerCase();\n if (['a', 'button', 'input', 'select', 'textarea'].includes(tag)) {\n return true;\n }\n if (tag === 'a' && !(el as HTMLAnchorElement).href) {\n return false;\n }\n return false;\n }\n\n // --- Keyboard event handler ---\n\n function handleKeyDown(e: KeyboardEvent) {\n if (!enabled) return;\n\n // Close help overlay on Escape\n if (e.key === 'Escape' && helpOverlayVisible) {\n const overlay = document.getElementById(OVERLAY_ID);\n overlay?.remove();\n helpOverlayVisible = false;\n return;\n }\n\n if (!e.altKey) return;\n\n switch (e.key.toLowerCase()) {\n case 'h':\n e.preventDefault();\n navigateToNextHeading();\n break;\n case 'l':\n e.preventDefault();\n navigateToNextLandmark();\n break;\n case 'k':\n e.preventDefault();\n toggleHelpOverlay();\n break;\n }\n }\n\n // --- Styles for skip link ---\n\n function getStyles(): string {\n return `\n /* accessify keyboard navigation */\n #${SKIP_LINK_ID} {\n position: fixed;\n top: -100px;\n left: 50%;\n transform: translateX(-50%);\n background: #1a73e8;\n color: #fff;\n padding: 12px 24px;\n border-radius: 0 0 8px 8px;\n font-size: 16px;\n font-family: system-ui, sans-serif;\n text-decoration: none;\n z-index: 2147483647;\n transition: top 0.2s ease;\n box-shadow: 0 2px 8px rgba(0,0,0,0.3);\n }\n #${SKIP_LINK_ID}:focus {\n top: 0;\n outline: 3px solid #ffdd00;\n outline-offset: 2px;\n }\n `;\n }\n\n function injectStyles() {\n let styleEl = document.getElementById(STYLE_ID);\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ID;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = getStyles();\n }\n\n function removeStyles() {\n const styleEl = document.getElementById(STYLE_ID);\n styleEl?.remove();\n }\n\n // --- Module lifecycle ---\n\n function activate() {\n enabled = true;\n headingIndex = -1;\n landmarkIndex = -1;\n\n injectStyles();\n injectSkipLink();\n injectTabindex();\n document.addEventListener('keydown', handleKeyDown);\n localStorage.setItem(STORAGE_KEY, 'true');\n }\n\n function deactivate() {\n enabled = false;\n helpOverlayVisible = false;\n\n document.removeEventListener('keydown', handleKeyDown);\n removeSkipLink();\n removeInjectedTabindex();\n removeStyles();\n\n const overlay = document.getElementById(OVERLAY_ID);\n overlay?.remove();\n\n localStorage.removeItem(STORAGE_KEY);\n }\n\n return {\n id: 'keyboard-nav',\n name: () => 'Keyboard Navigation',\n description: 'Enhanced keyboard navigation with skip links, heading/landmark jumping, and shortcut help',\n icon: 'keyboard-nav',\n category: 'motor',\n activate,\n deactivate,\n getState: (): FeatureState => ({\n id: 'keyboard-nav',\n enabled,\n }),\n setState: (state: { enabled: boolean }) => {\n if (state.enabled) {\n activate();\n } else {\n deactivate();\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
function createLineHeightModule() {
|
|
2
|
+
let enabled = false;
|
|
3
|
+
let currentValue = 1.8;
|
|
4
|
+
const STYLE_ID = "accessify-line-height";
|
|
5
|
+
const STORAGE_KEY = "accessify-line-height-value";
|
|
6
|
+
function applyStyles(value) {
|
|
7
|
+
let styleEl = document.getElementById(STYLE_ID);
|
|
8
|
+
if (!enabled) {
|
|
9
|
+
styleEl?.remove();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!styleEl) {
|
|
13
|
+
styleEl = document.createElement("style");
|
|
14
|
+
styleEl.id = STYLE_ID;
|
|
15
|
+
document.head.appendChild(styleEl);
|
|
16
|
+
}
|
|
17
|
+
styleEl.textContent = `
|
|
18
|
+
body, body p, body li, body td, body th, body dd, body dt,
|
|
19
|
+
body span, body div, body label, body blockquote {
|
|
20
|
+
line-height: ${value} !important;
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
24
|
+
function activate() {
|
|
25
|
+
enabled = true;
|
|
26
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
27
|
+
if (saved) currentValue = parseFloat(saved);
|
|
28
|
+
applyStyles(currentValue);
|
|
29
|
+
}
|
|
30
|
+
function deactivate() {
|
|
31
|
+
enabled = false;
|
|
32
|
+
applyStyles(currentValue);
|
|
33
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: "line-height",
|
|
37
|
+
name: () => "Line Height",
|
|
38
|
+
description: "Increase space between lines of text",
|
|
39
|
+
icon: "line-height",
|
|
40
|
+
category: "visual",
|
|
41
|
+
activate,
|
|
42
|
+
deactivate,
|
|
43
|
+
getState: () => ({ id: "line-height", enabled, value: currentValue }),
|
|
44
|
+
setState: (value) => {
|
|
45
|
+
currentValue = Math.min(3, Math.max(1, value));
|
|
46
|
+
applyStyles(currentValue);
|
|
47
|
+
localStorage.setItem(STORAGE_KEY, String(currentValue));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
createLineHeightModule as default
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=line-height-BT98qgEF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-height-BT98qgEF.js","sources":["../src/features/line-height.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\n\nexport default function createLineHeightModule(): FeatureModule {\n let enabled = false;\n let currentValue = 1.8;\n const STYLE_ID = 'accessify-line-height';\n const STORAGE_KEY = 'accessify-line-height-value';\n\n function applyStyles(value: number) {\n let styleEl = document.getElementById(STYLE_ID);\n if (!enabled) {\n styleEl?.remove();\n return;\n }\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ID;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = `\n body, body p, body li, body td, body th, body dd, body dt,\n body span, body div, body label, body blockquote {\n line-height: ${value} !important;\n }\n `;\n }\n\n function activate() {\n enabled = true;\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) currentValue = parseFloat(saved);\n applyStyles(currentValue);\n }\n\n function deactivate() {\n enabled = false;\n applyStyles(currentValue);\n localStorage.removeItem(STORAGE_KEY);\n }\n\n return {\n id: 'line-height',\n name: () => 'Line Height',\n description: 'Increase space between lines of text',\n icon: 'line-height',\n category: 'visual',\n activate,\n deactivate,\n getState: (): FeatureState => ({ id: 'line-height', enabled, value: currentValue }),\n setState: (value: number) => {\n currentValue = Math.min(3, Math.max(1, value));\n applyStyles(currentValue);\n localStorage.setItem(STORAGE_KEY, String(currentValue));\n },\n };\n}\n"],"names":[],"mappings":"AAEA,SAAwB,yBAAwC;AAC9D,MAAI,UAAU;AACd,MAAI,eAAe;AACnB,QAAM,WAAW;AACjB,QAAM,cAAc;AAEpB,WAAS,YAAY,OAAe;AAClC,QAAI,UAAU,SAAS,eAAe,QAAQ;AAC9C,QAAI,CAAC,SAAS;AACZ,eAAS,OAAA;AACT;AAAA,IACF;AACA,QAAI,CAAC,SAAS;AACZ,gBAAU,SAAS,cAAc,OAAO;AACxC,cAAQ,KAAK;AACb,eAAS,KAAK,YAAY,OAAO;AAAA,IACnC;AACA,YAAQ,cAAc;AAAA;AAAA;AAAA,uBAGH,KAAK;AAAA;AAAA;AAAA,EAG1B;AAEA,WAAS,WAAW;AAClB,cAAU;AACV,UAAM,QAAQ,aAAa,QAAQ,WAAW;AAC9C,QAAI,MAAO,gBAAe,WAAW,KAAK;AAC1C,gBAAY,YAAY;AAAA,EAC1B;AAEA,WAAS,aAAa;AACpB,cAAU;AACV,gBAAY,YAAY;AACxB,iBAAa,WAAW,WAAW;AAAA,EACrC;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM,MAAM;AAAA,IACZ,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU,OAAqB,EAAE,IAAI,eAAe,SAAS,OAAO;IACpE,UAAU,CAAC,UAAkB;AAC3B,qBAAe,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAC7C,kBAAY,YAAY;AACxB,mBAAa,QAAQ,aAAa,OAAO,YAAY,CAAC;AAAA,IACxD;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
function createLinkHighlightModule() {
|
|
2
|
+
let enabled = false;
|
|
3
|
+
const STYLE_ID = "accessify-link-highlight";
|
|
4
|
+
const STORAGE_KEY = "accessify-link-highlight";
|
|
5
|
+
function getStyles() {
|
|
6
|
+
return `
|
|
7
|
+
/* accessify link highlighting */
|
|
8
|
+
a[href] {
|
|
9
|
+
text-decoration: underline !important;
|
|
10
|
+
text-decoration-thickness: 3px !important;
|
|
11
|
+
text-underline-offset: 3px !important;
|
|
12
|
+
text-decoration-color: #1a73e8 !important;
|
|
13
|
+
background-color: rgba(26, 115, 232, 0.1) !important;
|
|
14
|
+
padding: 2px 4px !important;
|
|
15
|
+
border-radius: 2px !important;
|
|
16
|
+
transition: background-color 0.15s ease !important;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
a[href]:hover {
|
|
20
|
+
background-color: rgba(26, 115, 232, 0.25) !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
a[href]:visited {
|
|
24
|
+
text-decoration-color: #681da8 !important;
|
|
25
|
+
background-color: rgba(104, 29, 168, 0.1) !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
a[href]:visited:hover {
|
|
29
|
+
background-color: rgba(104, 29, 168, 0.25) !important;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* External link indicator */
|
|
33
|
+
a[href^="http"]:not([href*="${typeof window !== "undefined" ? window.location.hostname : ""}"]):after {
|
|
34
|
+
content: " \\2197";
|
|
35
|
+
font-size: 0.8em;
|
|
36
|
+
vertical-align: super;
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
function injectStyles() {
|
|
41
|
+
let styleEl = document.getElementById(STYLE_ID);
|
|
42
|
+
if (!styleEl) {
|
|
43
|
+
styleEl = document.createElement("style");
|
|
44
|
+
styleEl.id = STYLE_ID;
|
|
45
|
+
document.head.appendChild(styleEl);
|
|
46
|
+
}
|
|
47
|
+
styleEl.textContent = getStyles();
|
|
48
|
+
}
|
|
49
|
+
function removeStyles() {
|
|
50
|
+
const styleEl = document.getElementById(STYLE_ID);
|
|
51
|
+
styleEl?.remove();
|
|
52
|
+
}
|
|
53
|
+
function activate() {
|
|
54
|
+
enabled = true;
|
|
55
|
+
injectStyles();
|
|
56
|
+
localStorage.setItem(STORAGE_KEY, "true");
|
|
57
|
+
}
|
|
58
|
+
function deactivate() {
|
|
59
|
+
enabled = false;
|
|
60
|
+
removeStyles();
|
|
61
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
id: "link-highlight",
|
|
65
|
+
name: () => "Link Highlight",
|
|
66
|
+
description: "Highlight all links with thick underlines and background colors",
|
|
67
|
+
icon: "link-highlight",
|
|
68
|
+
category: "visual",
|
|
69
|
+
activate,
|
|
70
|
+
deactivate,
|
|
71
|
+
getState: () => ({
|
|
72
|
+
id: "link-highlight",
|
|
73
|
+
enabled
|
|
74
|
+
}),
|
|
75
|
+
setState: (state) => {
|
|
76
|
+
if (state.enabled) {
|
|
77
|
+
activate();
|
|
78
|
+
} else {
|
|
79
|
+
deactivate();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
createLinkHighlightModule as default
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=link-highlight-DBGm067Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-highlight-DBGm067Y.js","sources":["../src/features/link-highlight.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\n\nexport default function createLinkHighlightModule(): FeatureModule {\n let enabled = false;\n const STYLE_ID = 'accessify-link-highlight';\n const STORAGE_KEY = 'accessify-link-highlight';\n\n function getStyles(): string {\n return `\n /* accessify link highlighting */\n a[href] {\n text-decoration: underline !important;\n text-decoration-thickness: 3px !important;\n text-underline-offset: 3px !important;\n text-decoration-color: #1a73e8 !important;\n background-color: rgba(26, 115, 232, 0.1) !important;\n padding: 2px 4px !important;\n border-radius: 2px !important;\n transition: background-color 0.15s ease !important;\n }\n\n a[href]:hover {\n background-color: rgba(26, 115, 232, 0.25) !important;\n }\n\n a[href]:visited {\n text-decoration-color: #681da8 !important;\n background-color: rgba(104, 29, 168, 0.1) !important;\n }\n\n a[href]:visited:hover {\n background-color: rgba(104, 29, 168, 0.25) !important;\n }\n\n /* External link indicator */\n a[href^=\"http\"]:not([href*=\"${typeof window !== 'undefined' ? window.location.hostname : ''}\"]):after {\n content: \" \\\\2197\";\n font-size: 0.8em;\n vertical-align: super;\n }\n `;\n }\n\n function injectStyles() {\n let styleEl = document.getElementById(STYLE_ID);\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ID;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = getStyles();\n }\n\n function removeStyles() {\n const styleEl = document.getElementById(STYLE_ID);\n styleEl?.remove();\n }\n\n function activate() {\n enabled = true;\n injectStyles();\n localStorage.setItem(STORAGE_KEY, 'true');\n }\n\n function deactivate() {\n enabled = false;\n removeStyles();\n localStorage.removeItem(STORAGE_KEY);\n }\n\n return {\n id: 'link-highlight',\n name: () => 'Link Highlight',\n description: 'Highlight all links with thick underlines and background colors',\n icon: 'link-highlight',\n category: 'visual',\n activate,\n deactivate,\n getState: (): FeatureState => ({\n id: 'link-highlight',\n enabled,\n }),\n setState: (state: { enabled: boolean }) => {\n if (state.enabled) {\n activate();\n } else {\n deactivate();\n }\n },\n };\n}\n"],"names":[],"mappings":"AAEA,SAAwB,4BAA2C;AACjE,MAAI,UAAU;AACd,QAAM,WAAW;AACjB,QAAM,cAAc;AAEpB,WAAS,YAAoB;AAC3B,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oCA2ByB,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/F;AAEA,WAAS,eAAe;AACtB,QAAI,UAAU,SAAS,eAAe,QAAQ;AAC9C,QAAI,CAAC,SAAS;AACZ,gBAAU,SAAS,cAAc,OAAO;AACxC,cAAQ,KAAK;AACb,eAAS,KAAK,YAAY,OAAO;AAAA,IACnC;AACA,YAAQ,cAAc,UAAA;AAAA,EACxB;AAEA,WAAS,eAAe;AACtB,UAAM,UAAU,SAAS,eAAe,QAAQ;AAChD,aAAS,OAAA;AAAA,EACX;AAEA,WAAS,WAAW;AAClB,cAAU;AACV,iBAAA;AACA,iBAAa,QAAQ,aAAa,MAAM;AAAA,EAC1C;AAEA,WAAS,aAAa;AACpB,cAAU;AACV,iBAAA;AACA,iBAAa,WAAW,WAAW;AAAA,EACrC;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM,MAAM;AAAA,IACZ,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,UAAU,OAAqB;AAAA,MAC7B,IAAI;AAAA,MACJ;AAAA,IAAA;AAAA,IAEF,UAAU,CAAC,UAAgC;AACzC,UAAI,MAAM,SAAS;AACjB,iBAAA;AAAA,MACF,OAAO;AACL,mBAAA;AAAA,MACF;AAAA,IACF;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(){"use strict";var e=document.currentScript,t=[],n=function(e){n._loaded&&window.__accessify_exec?window.__accessify_exec.apply(null,arguments):t.push(Array.prototype.slice.call(arguments))};function o(){var o=document.createElement("script");o.src=function(){if(e&&e.src)try{var t=new URL(e.src);return t.origin+t.pathname.replace(/\/[^\/]*$/,"")}catch(e){}return"https://cdn.jsdelivr.net/npm/accessify-widget/dist"}()+"/accessify.min.js",o.async=!0,o.crossOrigin="anonymous",o.onload=function(){n._loaded=!0;var o=function(){if(!e)return{};var t=e.dataset,n={position:t.position,theme:t.theme,lang:t.lang,openRouterKey:t.openrouterKey,siteKey:t.siteKey,proxyUrl:t.proxyUrl,zIndex:t.zIndex?parseInt(t.zIndex,10):void 0,compact:"true"===t.compact};return n.siteKey&&!n.proxyUrl&&(n.proxyUrl="https://accessify-api.accessify.workers.dev"),n}();window.__accessify_dataConfig=o,o.siteKey&&!t.some(function(e){return"init"===e[0]})&&n("init",o)},o.onerror=function(){console.warn("[Accessify] Failed to load widget.")},(document.head||document.documentElement).appendChild(o)}n.q=t,n._loaded=!1,window.Accessify=n,window.A11yPlus=n,"complete"===document.readyState||"interactive"===document.readyState?setTimeout(o,1):document.addEventListener("DOMContentLoaded",o)}();
|