accessify-widget 0.2.0 → 0.2.2
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/dist/accessify.min.js +1 -1
- package/dist/accessify.min.js.map +1 -1
- package/dist/accessify.mjs +1 -1
- package/dist/{index-B6b-ij4T.js → index-i-tNypvI.js} +1867 -1114
- package/dist/index-i-tNypvI.js.map +1 -0
- package/dist/{keyboard-nav-YtijlLYi.js → keyboard-nav-C9zLmfrJ.js} +2 -2
- package/dist/{keyboard-nav-YtijlLYi.js.map → keyboard-nav-C9zLmfrJ.js.map} +1 -1
- package/dist/{page-structure-WFqy5QjQ.js → page-structure-GqhCQsl3.js} +2 -2
- package/dist/{page-structure-WFqy5QjQ.js.map → page-structure-GqhCQsl3.js.map} +1 -1
- package/dist/text-simplify-C9gzE3t0.js +632 -0
- package/dist/text-simplify-C9gzE3t0.js.map +1 -0
- package/dist/{tts-02b9iV0h.js → tts-CjszLRnb.js} +58 -12
- package/dist/tts-CjszLRnb.js.map +1 -0
- package/dist/widget.js +1 -1
- package/dist/widget.js.map +1 -1
- package/package.json +10 -10
- package/dist/alt-text-CrPRUX83.js +0 -567
- package/dist/alt-text-CrPRUX83.js.map +0 -1
- package/dist/auto-scan-pg-09o7A.js +0 -885
- package/dist/auto-scan-pg-09o7A.js.map +0 -1
- package/dist/focus-highlight-CjERyyUF.js +0 -93
- package/dist/focus-highlight-CjERyyUF.js.map +0 -1
- package/dist/index-B6b-ij4T.js.map +0 -1
- package/dist/text-simplify-CELklw5A.js +0 -223
- package/dist/text-simplify-CELklw5A.js.map +0 -1
- package/dist/tts-02b9iV0h.js.map +0 -1
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
function createTextSimplifyModule(aiService, lang = "de") {
|
|
2
|
-
let enabled = false;
|
|
3
|
-
let level = "einfache";
|
|
4
|
-
let savedParagraphs = [];
|
|
5
|
-
let abortController = null;
|
|
6
|
-
let overlayEl = null;
|
|
7
|
-
const STORAGE_KEY = "accessify-text-simplify";
|
|
8
|
-
const SIMPLIFIED_ATTR = "data-accessify-simplified";
|
|
9
|
-
const ORIGINAL_ATTR = "data-accessify-original";
|
|
10
|
-
function getTextElements() {
|
|
11
|
-
const selectors = "p, li, td, th, blockquote, figcaption, .hero-subtitle, h1, h2, h3, h4, h5, h6";
|
|
12
|
-
const elements = [];
|
|
13
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14
|
-
document.querySelectorAll(selectors).forEach((el) => {
|
|
15
|
-
const htmlEl = el;
|
|
16
|
-
if (htmlEl.closest("#accessify-root")) return;
|
|
17
|
-
if (htmlEl.offsetParent === null && htmlEl.style.display !== "fixed") return;
|
|
18
|
-
const text = htmlEl.textContent?.trim() || "";
|
|
19
|
-
if (text.length < 20) return;
|
|
20
|
-
if (seen.has(htmlEl)) return;
|
|
21
|
-
let dominated = false;
|
|
22
|
-
for (const s of seen) {
|
|
23
|
-
if (s.contains(htmlEl)) {
|
|
24
|
-
dominated = true;
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
if (dominated) return;
|
|
29
|
-
seen.add(htmlEl);
|
|
30
|
-
elements.push(htmlEl);
|
|
31
|
-
});
|
|
32
|
-
return elements;
|
|
33
|
-
}
|
|
34
|
-
function markLoading(el) {
|
|
35
|
-
el.style.opacity = "0.5";
|
|
36
|
-
el.style.transition = "opacity 0.3s ease";
|
|
37
|
-
}
|
|
38
|
-
function clearLoading(el) {
|
|
39
|
-
el.style.opacity = "";
|
|
40
|
-
el.style.transition = "";
|
|
41
|
-
}
|
|
42
|
-
function showProgress(current, total) {
|
|
43
|
-
if (!overlayEl) {
|
|
44
|
-
overlayEl = document.createElement("div");
|
|
45
|
-
overlayEl.setAttribute("role", "status");
|
|
46
|
-
overlayEl.setAttribute("aria-live", "polite");
|
|
47
|
-
Object.assign(overlayEl.style, {
|
|
48
|
-
position: "fixed",
|
|
49
|
-
bottom: "80px",
|
|
50
|
-
right: "24px",
|
|
51
|
-
zIndex: "2147483645",
|
|
52
|
-
padding: "10px 18px",
|
|
53
|
-
background: "#1a1a2e",
|
|
54
|
-
color: "#4ea8de",
|
|
55
|
-
borderRadius: "10px",
|
|
56
|
-
boxShadow: "0 4px 20px rgba(0,0,0,0.3)",
|
|
57
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
58
|
-
fontSize: "13px",
|
|
59
|
-
fontWeight: "600",
|
|
60
|
-
display: "flex",
|
|
61
|
-
alignItems: "center",
|
|
62
|
-
gap: "8px"
|
|
63
|
-
});
|
|
64
|
-
document.body.appendChild(overlayEl);
|
|
65
|
-
}
|
|
66
|
-
const pct = Math.round(current / total * 100);
|
|
67
|
-
const label = lang.startsWith("de") ? `Wird vereinfacht... ${current}/${total} (${pct}%)` : `Simplifying... ${current}/${total} (${pct}%)`;
|
|
68
|
-
overlayEl.innerHTML = `<span style="display:inline-block;width:14px;height:14px;border:2px solid rgba(78,168,222,0.3);border-top-color:#4ea8de;border-radius:50%;animation:accessify-autospin 0.6s linear infinite"></span> ${label}`;
|
|
69
|
-
if (!document.getElementById("accessify-autospin-style")) {
|
|
70
|
-
const style = document.createElement("style");
|
|
71
|
-
style.id = "accessify-autospin-style";
|
|
72
|
-
style.textContent = "@keyframes accessify-autospin { to { transform: rotate(360deg); } }";
|
|
73
|
-
document.head.appendChild(style);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function showDone(count) {
|
|
77
|
-
if (overlayEl) {
|
|
78
|
-
const label = lang.startsWith("de") ? `${count} Textabschnitte vereinfacht` : `${count} text sections simplified`;
|
|
79
|
-
overlayEl.innerHTML = `<span style="color:#22c55e">✓</span> ${label}`;
|
|
80
|
-
overlayEl.style.color = "#22c55e";
|
|
81
|
-
setTimeout(() => removeProgress(), 3e3);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function showError(msg) {
|
|
85
|
-
if (overlayEl) {
|
|
86
|
-
overlayEl.innerHTML = `<span style="color:#ef4444">✕</span> ${msg}`;
|
|
87
|
-
overlayEl.style.color = "#ef4444";
|
|
88
|
-
setTimeout(() => removeProgress(), 5e3);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function removeProgress() {
|
|
92
|
-
overlayEl?.remove();
|
|
93
|
-
overlayEl = null;
|
|
94
|
-
document.getElementById("accessify-autospin-style")?.remove();
|
|
95
|
-
}
|
|
96
|
-
async function simplifyPage() {
|
|
97
|
-
if (!aiService) {
|
|
98
|
-
showProgress(0, 1);
|
|
99
|
-
showError(lang.startsWith("de") ? "Kein API-Key konfiguriert" : "No API key configured");
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
const elements = getTextElements();
|
|
103
|
-
if (elements.length === 0) return;
|
|
104
|
-
abortController = new AbortController();
|
|
105
|
-
savedParagraphs = [];
|
|
106
|
-
for (const el of elements) {
|
|
107
|
-
savedParagraphs.push({ el, originalText: el.textContent || "" });
|
|
108
|
-
el.setAttribute(ORIGINAL_ATTR, el.innerHTML);
|
|
109
|
-
markLoading(el);
|
|
110
|
-
}
|
|
111
|
-
showProgress(0, elements.length);
|
|
112
|
-
let completed = 0;
|
|
113
|
-
let totalSimplified = 0;
|
|
114
|
-
for (const el of elements) {
|
|
115
|
-
if (abortController.signal.aborted) break;
|
|
116
|
-
const text = el.textContent?.trim() || "";
|
|
117
|
-
if (text.length < 20) {
|
|
118
|
-
clearLoading(el);
|
|
119
|
-
completed++;
|
|
120
|
-
showProgress(completed, elements.length);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
const simplified = await aiService.simplifyText(text, level, lang);
|
|
125
|
-
if (abortController?.signal.aborted) return;
|
|
126
|
-
if (simplified && simplified !== text) {
|
|
127
|
-
el.textContent = simplified;
|
|
128
|
-
el.setAttribute(SIMPLIFIED_ATTR, "true");
|
|
129
|
-
totalSimplified++;
|
|
130
|
-
}
|
|
131
|
-
} catch (err) {
|
|
132
|
-
if (err?.message?.includes("429")) {
|
|
133
|
-
await new Promise((r) => setTimeout(r, 8e3));
|
|
134
|
-
try {
|
|
135
|
-
const retry = await aiService.simplifyText(text, level, lang);
|
|
136
|
-
if (abortController?.signal.aborted) return;
|
|
137
|
-
if (retry && retry !== text) {
|
|
138
|
-
el.textContent = retry;
|
|
139
|
-
el.setAttribute(SIMPLIFIED_ATTR, "true");
|
|
140
|
-
totalSimplified++;
|
|
141
|
-
}
|
|
142
|
-
} catch {
|
|
143
|
-
}
|
|
144
|
-
} else {
|
|
145
|
-
console.warn("[Accessify] Failed to simplify:", err);
|
|
146
|
-
}
|
|
147
|
-
} finally {
|
|
148
|
-
clearLoading(el);
|
|
149
|
-
completed++;
|
|
150
|
-
if (!abortController?.signal.aborted) {
|
|
151
|
-
showProgress(completed, elements.length);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (!abortController?.signal.aborted) {
|
|
156
|
-
showDone(totalSimplified);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function restorePage() {
|
|
160
|
-
for (const { el } of savedParagraphs) {
|
|
161
|
-
const original = el.getAttribute(ORIGINAL_ATTR);
|
|
162
|
-
if (original !== null) {
|
|
163
|
-
el.innerHTML = original;
|
|
164
|
-
el.removeAttribute(ORIGINAL_ATTR);
|
|
165
|
-
el.removeAttribute(SIMPLIFIED_ATTR);
|
|
166
|
-
clearLoading(el);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
savedParagraphs = [];
|
|
170
|
-
}
|
|
171
|
-
function loadPreferences() {
|
|
172
|
-
const saved = localStorage.getItem(STORAGE_KEY);
|
|
173
|
-
if (saved) {
|
|
174
|
-
try {
|
|
175
|
-
const parsed = JSON.parse(saved);
|
|
176
|
-
if (parsed.level === "einfache" || parsed.level === "leichte") {
|
|
177
|
-
level = parsed.level;
|
|
178
|
-
}
|
|
179
|
-
} catch {
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function savePreferences() {
|
|
184
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify({ level }));
|
|
185
|
-
}
|
|
186
|
-
function activate() {
|
|
187
|
-
if (enabled) return;
|
|
188
|
-
enabled = true;
|
|
189
|
-
loadPreferences();
|
|
190
|
-
simplifyPage();
|
|
191
|
-
}
|
|
192
|
-
function deactivate() {
|
|
193
|
-
enabled = false;
|
|
194
|
-
abortController?.abort();
|
|
195
|
-
abortController = null;
|
|
196
|
-
restorePage();
|
|
197
|
-
removeProgress();
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
id: "text-simplify",
|
|
201
|
-
name: () => lang.startsWith("de") ? "Text vereinfachen" : "Simplify Text",
|
|
202
|
-
description: lang.startsWith("de") ? "Text in Einfache Sprache umwandeln" : "Simplify text for easier understanding",
|
|
203
|
-
icon: "simplify-text",
|
|
204
|
-
category: "ai",
|
|
205
|
-
activate,
|
|
206
|
-
deactivate,
|
|
207
|
-
getState: () => ({
|
|
208
|
-
id: "text-simplify",
|
|
209
|
-
enabled,
|
|
210
|
-
value: { level }
|
|
211
|
-
}),
|
|
212
|
-
setState: (newState) => {
|
|
213
|
-
if (newState.level && (newState.level === "einfache" || newState.level === "leichte")) {
|
|
214
|
-
level = newState.level;
|
|
215
|
-
savePreferences();
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
export {
|
|
221
|
-
createTextSimplifyModule as default
|
|
222
|
-
};
|
|
223
|
-
//# sourceMappingURL=text-simplify-CELklw5A.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"text-simplify-CELklw5A.js","sources":["../src/features/text-simplify.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Accessify – Text Simplification Feature (Auto-mode)\n// ---------------------------------------------------------------------------\n// When activated, automatically finds all visible text blocks on the page\n// and replaces them with simplified versions via the AI service.\n// When deactivated, restores the original text.\n// ---------------------------------------------------------------------------\n\nimport type { FeatureModule, FeatureState, AIService } from '../types';\n\ntype SimplifyLevel = 'einfache' | 'leichte';\n\ninterface TextSimplifyState {\n level: SimplifyLevel;\n}\n\ninterface SavedParagraph {\n el: HTMLElement;\n originalText: string;\n}\n\nexport default function createTextSimplifyModule(\n aiService?: AIService,\n lang: string = 'de',\n): FeatureModule {\n let enabled = false;\n let level: SimplifyLevel = 'einfache';\n let savedParagraphs: SavedParagraph[] = [];\n let abortController: AbortController | null = null;\n let overlayEl: HTMLDivElement | null = null;\n\n const STORAGE_KEY = 'accessify-text-simplify';\n const SIMPLIFIED_ATTR = 'data-accessify-simplified';\n const ORIGINAL_ATTR = 'data-accessify-original';\n\n // -------------------------------------------------------------------------\n // Find text-heavy elements on the page\n // -------------------------------------------------------------------------\n\n function getTextElements(): HTMLElement[] {\n const selectors = 'p, li, td, th, blockquote, figcaption, .hero-subtitle, h1, h2, h3, h4, h5, h6';\n const elements: HTMLElement[] = [];\n const seen = new Set<HTMLElement>();\n\n document.querySelectorAll(selectors).forEach((el) => {\n const htmlEl = el as HTMLElement;\n\n // Skip if inside the widget itself\n if (htmlEl.closest('#accessify-root')) return;\n // Skip hidden elements\n if (htmlEl.offsetParent === null && htmlEl.style.display !== 'fixed') return;\n // Skip elements with very little text\n const text = htmlEl.textContent?.trim() || '';\n if (text.length < 20) return;\n // Skip duplicates\n if (seen.has(htmlEl)) return;\n // Skip nested: if parent is already in our list, skip\n let dominated = false;\n for (const s of seen) {\n if (s.contains(htmlEl)) { dominated = true; break; }\n }\n if (dominated) return;\n\n seen.add(htmlEl);\n elements.push(htmlEl);\n });\n\n return elements;\n }\n\n // -------------------------------------------------------------------------\n // Loading overlay on individual elements\n // -------------------------------------------------------------------------\n\n function markLoading(el: HTMLElement) {\n el.style.opacity = '0.5';\n el.style.transition = 'opacity 0.3s ease';\n }\n\n function clearLoading(el: HTMLElement) {\n el.style.opacity = '';\n el.style.transition = '';\n }\n\n // -------------------------------------------------------------------------\n // Global progress indicator\n // -------------------------------------------------------------------------\n\n function showProgress(current: number, total: number) {\n if (!overlayEl) {\n overlayEl = document.createElement('div');\n overlayEl.setAttribute('role', 'status');\n overlayEl.setAttribute('aria-live', 'polite');\n Object.assign(overlayEl.style, {\n position: 'fixed',\n bottom: '80px',\n right: '24px',\n zIndex: '2147483645',\n padding: '10px 18px',\n background: '#1a1a2e',\n color: '#4ea8de',\n borderRadius: '10px',\n boxShadow: '0 4px 20px rgba(0,0,0,0.3)',\n fontFamily: 'system-ui, -apple-system, sans-serif',\n fontSize: '13px',\n fontWeight: '600',\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n });\n document.body.appendChild(overlayEl);\n }\n\n const pct = Math.round((current / total) * 100);\n const label = lang.startsWith('de')\n ? `Wird vereinfacht... ${current}/${total} (${pct}%)`\n : `Simplifying... ${current}/${total} (${pct}%)`;\n overlayEl.innerHTML = `<span style=\"display:inline-block;width:14px;height:14px;border:2px solid rgba(78,168,222,0.3);border-top-color:#4ea8de;border-radius:50%;animation:accessify-autospin 0.6s linear infinite\"></span> ${label}`;\n\n // Inject keyframe if not present\n if (!document.getElementById('accessify-autospin-style')) {\n const style = document.createElement('style');\n style.id = 'accessify-autospin-style';\n style.textContent = '@keyframes accessify-autospin { to { transform: rotate(360deg); } }';\n document.head.appendChild(style);\n }\n }\n\n function showDone(count: number) {\n if (overlayEl) {\n const label = lang.startsWith('de')\n ? `${count} Textabschnitte vereinfacht`\n : `${count} text sections simplified`;\n overlayEl.innerHTML = `<span style=\"color:#22c55e\">✓</span> ${label}`;\n overlayEl.style.color = '#22c55e';\n setTimeout(() => removeProgress(), 3000);\n }\n }\n\n function showError(msg: string) {\n if (overlayEl) {\n overlayEl.innerHTML = `<span style=\"color:#ef4444\">✕</span> ${msg}`;\n overlayEl.style.color = '#ef4444';\n setTimeout(() => removeProgress(), 5000);\n }\n }\n\n function removeProgress() {\n overlayEl?.remove();\n overlayEl = null;\n document.getElementById('accessify-autospin-style')?.remove();\n }\n\n // -------------------------------------------------------------------------\n // Core: simplify all text blocks\n // -------------------------------------------------------------------------\n\n async function simplifyPage() {\n if (!aiService) {\n showProgress(0, 1);\n showError(lang.startsWith('de')\n ? 'Kein API-Key konfiguriert'\n : 'No API key configured');\n return;\n }\n\n const elements = getTextElements();\n if (elements.length === 0) return;\n\n abortController = new AbortController();\n savedParagraphs = [];\n\n // Save originals and dim all elements\n for (const el of elements) {\n savedParagraphs.push({ el, originalText: el.textContent || '' });\n el.setAttribute(ORIGINAL_ATTR, el.innerHTML);\n markLoading(el);\n }\n\n showProgress(0, elements.length);\n\n let completed = 0;\n let totalSimplified = 0;\n\n // Process one element at a time — reliable with any model (including reasoning)\n for (const el of elements) {\n if (abortController.signal.aborted) break;\n\n const text = el.textContent?.trim() || '';\n if (text.length < 20) {\n clearLoading(el);\n completed++;\n showProgress(completed, elements.length);\n continue;\n }\n\n try {\n const simplified = await aiService!.simplifyText(text, level, lang);\n if (abortController?.signal.aborted) return;\n\n if (simplified && simplified !== text) {\n el.textContent = simplified;\n el.setAttribute(SIMPLIFIED_ATTR, 'true');\n totalSimplified++;\n }\n } catch (err: any) {\n // On rate limit, wait and retry once\n if (err?.message?.includes('429')) {\n await new Promise((r) => setTimeout(r, 8000));\n try {\n const retry = await aiService!.simplifyText(text, level, lang);\n if (abortController?.signal.aborted) return;\n if (retry && retry !== text) {\n el.textContent = retry;\n el.setAttribute(SIMPLIFIED_ATTR, 'true');\n totalSimplified++;\n }\n } catch { /* skip this element */ }\n } else {\n console.warn('[Accessify] Failed to simplify:', err);\n }\n } finally {\n clearLoading(el);\n completed++;\n if (!abortController?.signal.aborted) {\n showProgress(completed, elements.length);\n }\n }\n }\n\n if (!abortController?.signal.aborted) {\n showDone(totalSimplified);\n }\n }\n\n // -------------------------------------------------------------------------\n // Restore original text\n // -------------------------------------------------------------------------\n\n function restorePage() {\n for (const { el } of savedParagraphs) {\n const original = el.getAttribute(ORIGINAL_ATTR);\n if (original !== null) {\n el.innerHTML = original;\n el.removeAttribute(ORIGINAL_ATTR);\n el.removeAttribute(SIMPLIFIED_ATTR);\n clearLoading(el);\n }\n }\n savedParagraphs = [];\n }\n\n // -------------------------------------------------------------------------\n // Preferences\n // -------------------------------------------------------------------------\n\n function loadPreferences() {\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n try {\n const parsed = JSON.parse(saved);\n if (parsed.level === 'einfache' || parsed.level === 'leichte') {\n level = parsed.level;\n }\n } catch {\n // use defaults\n }\n }\n }\n\n function savePreferences() {\n localStorage.setItem(STORAGE_KEY, JSON.stringify({ level }));\n }\n\n // -------------------------------------------------------------------------\n // Module lifecycle\n // -------------------------------------------------------------------------\n\n function activate() {\n if (enabled) return;\n enabled = true;\n loadPreferences();\n simplifyPage();\n }\n\n function deactivate() {\n enabled = false;\n abortController?.abort();\n abortController = null;\n restorePage();\n removeProgress();\n }\n\n // -------------------------------------------------------------------------\n // FeatureModule interface\n // -------------------------------------------------------------------------\n\n return {\n id: 'text-simplify',\n name: () => (lang.startsWith('de') ? 'Text vereinfachen' : 'Simplify Text'),\n description: lang.startsWith('de')\n ? 'Text in Einfache Sprache umwandeln'\n : 'Simplify text for easier understanding',\n icon: 'simplify-text',\n category: 'ai',\n activate,\n deactivate,\n getState: (): FeatureState => ({\n id: 'text-simplify',\n enabled,\n value: { level } as TextSimplifyState,\n }),\n setState: (newState: Partial<TextSimplifyState>) => {\n if (newState.level && (newState.level === 'einfache' || newState.level === 'leichte')) {\n level = newState.level;\n savePreferences();\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
package/dist/tts-02b9iV0h.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tts-02b9iV0h.js","sources":["../src/features/tts.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\n\ninterface TTSOptions {\n speed: number;\n}\n\nconst DEFAULT_OPTIONS: TTSOptions = {\n speed: 1.0,\n};\n\nexport default function createTTSModule(): FeatureModule {\n let enabled = false;\n let options: TTSOptions = { ...DEFAULT_OPTIONS };\n let controlBar: HTMLDivElement | null = null;\n let highlightedEl: HTMLElement | null = null;\n let isPlaying = false;\n let isPaused = false;\n let clickToReadActive = true;\n let currentUtterances: SpeechSynthesisUtterance[] = [];\n let currentUtteranceIndex = 0;\n let selectedVoice: SpeechSynthesisVoice | null = null;\n let detectedLang = 'en';\n\n const STORAGE_KEY = 'accessify-tts';\n const CONTROL_BAR_ID = 'accessify-tts-controls';\n const STYLE_ID = 'accessify-tts-styles';\n const HIGHLIGHT_CLASS = 'accessify-tts-highlight';\n\n // ---------------------------------------------------------------------------\n // Language detection\n // ---------------------------------------------------------------------------\n\n function detectPageLanguage(): string {\n const htmlLang = document.documentElement.lang;\n if (htmlLang) {\n return htmlLang.toLowerCase();\n }\n // Fallback: check <meta> content-language\n const meta = document.querySelector<HTMLMetaElement>(\n 'meta[http-equiv=\"content-language\"]'\n );\n if (meta?.content) {\n return meta.content.toLowerCase();\n }\n return 'en';\n }\n\n // ---------------------------------------------------------------------------\n // Voice selection\n // ---------------------------------------------------------------------------\n\n function selectBestVoice(lang: string): SpeechSynthesisVoice | null {\n const voices = speechSynthesis.getVoices();\n if (voices.length === 0) return null;\n\n const langBase = lang.split('-')[0]; // e.g. 'de' from 'de-DE'\n\n // For German, strongly prioritize native German voices\n if (langBase === 'de') {\n // Prefer high-quality (non-local) de-DE voices first\n const deDENetwork = voices.find(\n (v) => v.lang.toLowerCase().startsWith('de-de') && v.localService === false\n );\n if (deDENetwork) return deDENetwork;\n\n // Then any de-DE voice\n const deDE = voices.find(\n (v) => v.lang.toLowerCase().startsWith('de-de')\n );\n if (deDE) return deDE;\n\n // Then any German voice\n const deAny = voices.find((v) =>\n v.lang.toLowerCase().startsWith('de')\n );\n if (deAny) return deAny;\n }\n\n // Exact match (e.g. 'en-US' matches 'en-US')\n const exact = voices.find(\n (v) => v.lang.toLowerCase() === lang\n );\n if (exact) return exact;\n\n // Base language match (e.g. 'en' matches 'en-US', 'en-GB')\n const baseMatch = voices.find(\n (v) => v.lang.toLowerCase().startsWith(langBase)\n );\n if (baseMatch) return baseMatch;\n\n // Ultimate fallback: first available voice\n return voices[0] || null;\n }\n\n function refreshVoice() {\n selectedVoice = selectBestVoice(detectedLang);\n }\n\n // ---------------------------------------------------------------------------\n // Sentence splitting (avoids Chrome's ~15s synthesis cutoff)\n // ---------------------------------------------------------------------------\n\n function splitIntoSentences(text: string): string[] {\n // Split on sentence-ending punctuation followed by whitespace or end\n const raw = text.match(/[^.!?]*[.!?]+[\\s]?|[^.!?]+$/g);\n if (!raw) return [text];\n\n const sentences: string[] = [];\n let buffer = '';\n\n for (const segment of raw) {\n const trimmed = segment.trim();\n if (!trimmed) continue;\n\n buffer += (buffer ? ' ' : '') + trimmed;\n\n // Flush buffer if it's reasonably long (> 80 chars) or is a complete sentence\n if (buffer.length > 80 || /[.!?]$/.test(buffer)) {\n sentences.push(buffer);\n buffer = '';\n }\n }\n\n if (buffer.trim()) {\n sentences.push(buffer.trim());\n }\n\n return sentences.length > 0 ? sentences : [text];\n }\n\n // ---------------------------------------------------------------------------\n // Speech engine\n // ---------------------------------------------------------------------------\n\n function stopSpeech() {\n speechSynthesis.cancel();\n isPlaying = false;\n isPaused = false;\n currentUtterances = [];\n currentUtteranceIndex = 0;\n removeHighlight();\n updateControlBarState();\n }\n\n function speakText(text: string, sourceEl?: HTMLElement) {\n stopSpeech();\n\n if (!text.trim()) return;\n\n if (sourceEl) {\n applyHighlight(sourceEl);\n }\n\n const sentences = splitIntoSentences(text);\n currentUtterances = sentences.map((sentence) => {\n const utterance = new SpeechSynthesisUtterance(sentence);\n utterance.rate = options.speed;\n utterance.lang = detectedLang;\n if (selectedVoice) {\n utterance.voice = selectedVoice;\n }\n return utterance;\n });\n\n currentUtteranceIndex = 0;\n isPlaying = true;\n isPaused = false;\n updateControlBarState();\n\n speakNextUtterance();\n }\n\n function speakNextUtterance() {\n if (currentUtteranceIndex >= currentUtterances.length) {\n // All done\n isPlaying = false;\n isPaused = false;\n removeHighlight();\n updateControlBarState();\n return;\n }\n\n const utterance = currentUtterances[currentUtteranceIndex];\n\n utterance.onend = () => {\n currentUtteranceIndex++;\n // Small delay between sentences for naturalness\n if (currentUtteranceIndex < currentUtterances.length && isPlaying) {\n setTimeout(() => speakNextUtterance(), 50);\n } else {\n isPlaying = false;\n isPaused = false;\n removeHighlight();\n updateControlBarState();\n }\n };\n\n utterance.onerror = (e) => {\n // 'interrupted' and 'canceled' are not real errors — they fire on cancel()\n if (e.error !== 'interrupted' && e.error !== 'canceled') {\n console.warn('[Accessify TTS] Speech error:', e.error);\n }\n isPlaying = false;\n isPaused = false;\n removeHighlight();\n updateControlBarState();\n };\n\n speechSynthesis.speak(utterance);\n }\n\n function togglePause() {\n if (!isPlaying) return;\n\n if (isPaused) {\n speechSynthesis.resume();\n isPaused = false;\n } else {\n speechSynthesis.pause();\n isPaused = true;\n }\n updateControlBarState();\n }\n\n // ---------------------------------------------------------------------------\n // Highlight management\n // ---------------------------------------------------------------------------\n\n function applyHighlight(el: HTMLElement) {\n removeHighlight();\n el.classList.add(HIGHLIGHT_CLASS);\n highlightedEl = el;\n }\n\n function removeHighlight() {\n if (highlightedEl) {\n highlightedEl.classList.remove(HIGHLIGHT_CLASS);\n highlightedEl = null;\n }\n // Safety: remove from any stale elements\n document\n .querySelectorAll(`.${HIGHLIGHT_CLASS}`)\n .forEach((el) => el.classList.remove(HIGHLIGHT_CLASS));\n }\n\n // ---------------------------------------------------------------------------\n // Click-to-read handler\n // ---------------------------------------------------------------------------\n\n function handleClick(e: MouseEvent) {\n if (!enabled || !clickToReadActive) return;\n\n const target = e.target as HTMLElement;\n if (!target) return;\n\n // Ignore clicks on the control bar itself\n if (controlBar?.contains(target)) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n // Walk up to find the most meaningful text container\n const textEl = findTextElement(target);\n if (!textEl) return;\n\n const text = extractTextContent(textEl);\n if (text.trim()) {\n speakText(text, textEl);\n }\n }\n\n function findTextElement(el: HTMLElement): HTMLElement | null {\n // If the element itself has substantial text, use it\n const directText = extractTextContent(el);\n if (directText.trim().length > 0) return el;\n\n // Walk up to find a parent with text\n let current: HTMLElement | null = el;\n while (current && current !== document.body) {\n const text = extractTextContent(current);\n if (text.trim().length > 0) return current;\n current = current.parentElement;\n }\n return null;\n }\n\n function extractTextContent(el: HTMLElement): string {\n // Use innerText rather than textContent to get rendered text with spacing\n return (el.innerText || el.textContent || '').trim();\n }\n\n // ---------------------------------------------------------------------------\n // Control bar UI\n // ---------------------------------------------------------------------------\n\n function createControlBar(): HTMLDivElement {\n const bar = document.createElement('div');\n bar.id = CONTROL_BAR_ID;\n bar.setAttribute('role', 'toolbar');\n bar.setAttribute('aria-label', 'Text-to-Speech controls');\n\n Object.assign(bar.style, {\n position: 'fixed',\n top: '0',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: '2147483646',\n display: 'flex',\n alignItems: 'center',\n gap: '6px',\n padding: '6px 14px',\n background: '#1a1a2e',\n color: '#f0f0f0',\n borderRadius: '0 0 10px 10px',\n boxShadow: '0 4px 16px rgba(0,0,0,0.3)',\n fontFamily: 'system-ui, -apple-system, sans-serif',\n fontSize: '13px',\n lineHeight: '1',\n userSelect: 'none',\n transition: 'opacity 0.2s ease',\n });\n\n // Play/Pause button\n const playPauseBtn = document.createElement('button');\n playPauseBtn.className = 'accessify-tts-btn accessify-tts-play';\n playPauseBtn.setAttribute('aria-label', 'Play / Pause');\n playPauseBtn.title = 'Play / Pause';\n playPauseBtn.innerHTML = iconPlay();\n playPauseBtn.addEventListener('click', () => {\n if (isPlaying) {\n togglePause();\n }\n // If nothing is playing, the user should click an element to start\n });\n\n // Stop button\n const stopBtn = document.createElement('button');\n stopBtn.className = 'accessify-tts-btn accessify-tts-stop';\n stopBtn.setAttribute('aria-label', 'Stop');\n stopBtn.title = 'Stop';\n stopBtn.innerHTML = iconStop();\n stopBtn.addEventListener('click', () => {\n stopSpeech();\n });\n\n // Speed control\n const speedContainer = document.createElement('div');\n Object.assign(speedContainer.style, {\n display: 'flex',\n alignItems: 'center',\n gap: '4px',\n marginLeft: '4px',\n });\n\n const speedLabel = document.createElement('label');\n speedLabel.textContent = 'Speed';\n speedLabel.setAttribute('for', 'accessify-tts-speed');\n Object.assign(speedLabel.style, {\n fontSize: '11px',\n opacity: '0.8',\n whiteSpace: 'nowrap',\n });\n\n const speedSlider = document.createElement('input');\n speedSlider.type = 'range';\n speedSlider.id = 'accessify-tts-speed';\n speedSlider.min = '0.5';\n speedSlider.max = '2';\n speedSlider.step = '0.1';\n speedSlider.value = String(options.speed);\n speedSlider.setAttribute('aria-label', 'Speech speed');\n Object.assign(speedSlider.style, {\n width: '70px',\n accentColor: '#4ea8de',\n cursor: 'pointer',\n });\n\n const speedValue = document.createElement('span');\n speedValue.className = 'accessify-tts-speed-val';\n speedValue.textContent = `${options.speed.toFixed(1)}x`;\n Object.assign(speedValue.style, {\n fontSize: '11px',\n minWidth: '30px',\n textAlign: 'center',\n opacity: '0.8',\n });\n\n speedSlider.addEventListener('input', () => {\n const newSpeed = parseFloat(speedSlider.value);\n options.speed = newSpeed;\n speedValue.textContent = `${newSpeed.toFixed(1)}x`;\n savePreferences();\n\n // Update rate on any currently queued utterances\n for (const u of currentUtterances) {\n u.rate = newSpeed;\n }\n });\n\n speedContainer.appendChild(speedLabel);\n speedContainer.appendChild(speedSlider);\n speedContainer.appendChild(speedValue);\n\n // Separator\n const sep = document.createElement('div');\n Object.assign(sep.style, {\n width: '1px',\n height: '18px',\n background: 'rgba(255,255,255,0.2)',\n margin: '0 4px',\n });\n\n // Hint text\n const hint = document.createElement('span');\n hint.className = 'accessify-tts-hint';\n hint.textContent = 'Click any text to read it aloud';\n Object.assign(hint.style, {\n fontSize: '11px',\n opacity: '0.6',\n whiteSpace: 'nowrap',\n });\n\n // Screen reader note (visually hidden, available to AT)\n const srNote = document.createElement('span');\n Object.assign(srNote.style, {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: '0',\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0,0,0,0)',\n whiteSpace: 'nowrap',\n border: '0',\n });\n srNote.textContent =\n 'For full screen reader support, use NVDA, JAWS, or VoiceOver';\n\n bar.appendChild(playPauseBtn);\n bar.appendChild(stopBtn);\n bar.appendChild(sep);\n bar.appendChild(speedContainer);\n bar.appendChild(sep.cloneNode(true));\n bar.appendChild(hint);\n bar.appendChild(srNote);\n\n return bar;\n }\n\n function updateControlBarState() {\n if (!controlBar) return;\n\n const playBtn = controlBar.querySelector<HTMLButtonElement>(\n '.accessify-tts-play'\n );\n const hintEl = controlBar.querySelector<HTMLSpanElement>(\n '.accessify-tts-hint'\n );\n\n if (playBtn) {\n if (isPlaying && !isPaused) {\n playBtn.innerHTML = iconPause();\n playBtn.setAttribute('aria-label', 'Pause');\n playBtn.title = 'Pause';\n } else if (isPlaying && isPaused) {\n playBtn.innerHTML = iconPlay();\n playBtn.setAttribute('aria-label', 'Resume');\n playBtn.title = 'Resume';\n } else {\n playBtn.innerHTML = iconPlay();\n playBtn.setAttribute('aria-label', 'Play / Pause');\n playBtn.title = 'Play / Pause';\n }\n }\n\n if (hintEl) {\n if (isPlaying && !isPaused) {\n hintEl.textContent = 'Speaking...';\n } else if (isPaused) {\n hintEl.textContent = 'Paused';\n } else {\n hintEl.textContent = 'Click any text to read it aloud';\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Inline SVG icons for control buttons\n // ---------------------------------------------------------------------------\n\n function iconPlay(): string {\n return '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" aria-hidden=\"true\"><polygon points=\"6,3 20,12 6,21\"/></svg>';\n }\n\n function iconPause(): string {\n return '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" aria-hidden=\"true\"><rect x=\"5\" y=\"3\" width=\"4\" height=\"18\"/><rect x=\"15\" y=\"3\" width=\"4\" height=\"18\"/></svg>';\n }\n\n function iconStop(): string {\n return '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" aria-hidden=\"true\"><rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"2\"/></svg>';\n }\n\n // ---------------------------------------------------------------------------\n // Styles (injected into <head>, not Shadow DOM)\n // ---------------------------------------------------------------------------\n\n function getStyles(): string {\n return `\n /* accessify TTS styles */\n .${HIGHLIGHT_CLASS} {\n background-color: rgba(78, 168, 222, 0.15) !important;\n outline: 2px solid rgba(78, 168, 222, 0.5) !important;\n outline-offset: 2px !important;\n border-radius: 3px !important;\n transition: background-color 0.2s ease, outline-color 0.2s ease !important;\n }\n\n #${CONTROL_BAR_ID} .accessify-tts-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n margin: 0;\n border: 1px solid rgba(255,255,255,0.15);\n border-radius: 6px;\n background: rgba(255,255,255,0.08);\n color: #f0f0f0;\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n line-height: 1;\n }\n\n #${CONTROL_BAR_ID} .accessify-tts-btn:hover {\n background: rgba(255,255,255,0.18);\n border-color: rgba(255,255,255,0.3);\n }\n\n #${CONTROL_BAR_ID} .accessify-tts-btn:focus-visible {\n outline: 2px solid #4ea8de;\n outline-offset: 1px;\n }\n\n #${CONTROL_BAR_ID} .accessify-tts-btn:active {\n background: rgba(255,255,255,0.25);\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 // ---------------------------------------------------------------------------\n // Preferences persistence\n // ---------------------------------------------------------------------------\n\n function loadPreferences() {\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n try {\n const parsed = JSON.parse(saved);\n options.speed = Math.min(2, Math.max(0.5, parsed.speed ?? DEFAULT_OPTIONS.speed));\n } catch {\n options = { ...DEFAULT_OPTIONS };\n }\n }\n }\n\n function savePreferences() {\n localStorage.setItem(STORAGE_KEY, JSON.stringify({ speed: options.speed }));\n }\n\n // ---------------------------------------------------------------------------\n // Module lifecycle\n // ---------------------------------------------------------------------------\n\n function activate() {\n if (enabled) return;\n enabled = true;\n\n loadPreferences();\n\n // Detect language\n detectedLang = detectPageLanguage();\n\n // Select voice (voices may load asynchronously)\n refreshVoice();\n if (!selectedVoice && speechSynthesis.onvoiceschanged !== undefined) {\n speechSynthesis.onvoiceschanged = () => {\n refreshVoice();\n };\n }\n\n // Inject styles and control bar\n injectStyles();\n controlBar = createControlBar();\n document.documentElement.appendChild(controlBar);\n\n // Enable click-to-read\n clickToReadActive = true;\n document.addEventListener('click', handleClick, true);\n }\n\n function deactivate() {\n enabled = false;\n\n // Stop all speech\n stopSpeech();\n\n // Remove click handler\n document.removeEventListener('click', handleClick, true);\n clickToReadActive = false;\n\n // Remove control bar\n if (controlBar) {\n controlBar.remove();\n controlBar = null;\n }\n\n // Remove highlights\n removeHighlight();\n\n // Remove styles\n removeStyles();\n\n // Clean up voices listener\n if (speechSynthesis.onvoiceschanged !== undefined) {\n speechSynthesis.onvoiceschanged = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // FeatureModule interface\n // ---------------------------------------------------------------------------\n\n return {\n id: 'tts',\n name: () => 'Text-to-Speech',\n description: 'Click any text to hear it read aloud with adjustable speed',\n icon: 'tts',\n category: 'cognitive',\n activate,\n deactivate,\n getState: (): FeatureState => ({\n id: 'tts',\n enabled,\n value: { speed: options.speed },\n }),\n setState: (newState: Partial<TTSOptions>) => {\n if (newState.speed !== undefined) {\n options.speed = Math.min(2, Math.max(0.5, newState.speed));\n savePreferences();\n\n // Update the slider in the control bar if present\n const slider = document.getElementById(\n 'accessify-tts-speed'\n ) as HTMLInputElement | null;\n if (slider) {\n slider.value = String(options.speed);\n }\n const valDisplay = controlBar?.querySelector<HTMLSpanElement>(\n '.accessify-tts-speed-val'\n );\n if (valDisplay) {\n valDisplay.textContent = `${options.speed.toFixed(1)}x`;\n }\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}
|