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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "accessify-widget",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Accessify-Widget accessibility widget for any website",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/maddesv1-ctrl/accessify-widget",
@@ -36,14 +36,6 @@
36
36
  "dist/",
37
37
  "README.md"
38
38
  ],
39
- "scripts": {
40
- "dev": "vite build --watch",
41
- "build": "vite build && BUILD_WIDGET=true vite build && node scripts/build-loader.js",
42
- "test": "vitest run",
43
- "test:watch": "vitest",
44
- "test:e2e": "playwright test",
45
- "lint": "eslint src/ --ext .ts,.svelte"
46
- },
47
39
  "devDependencies": {
48
40
  "@axe-core/playwright": "^4.9.0",
49
41
  "@playwright/test": "^1.45.0",
@@ -57,5 +49,13 @@
57
49
  },
58
50
  "dependencies": {
59
51
  "axe-core": "^4.9.0"
52
+ },
53
+ "scripts": {
54
+ "dev": "vite build --watch",
55
+ "build": "vite build && BUILD_WIDGET=true vite build && node scripts/build-loader.js",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "test:e2e": "playwright test",
59
+ "lint": "eslint src/ --ext .ts,.svelte"
60
60
  }
61
- }
61
+ }
@@ -1,567 +0,0 @@
1
- import { R as RateLimitError } from "./index-B6b-ij4T.js";
2
- function createAltTextModule(aiService, lang = "de") {
3
- let enabled = false;
4
- let styleEl = null;
5
- let badgeEl = null;
6
- let activePanelEl = null;
7
- let missingAltImages = [];
8
- const processedImages = /* @__PURE__ */ new Map();
9
- const STYLE_ID = "accessify-alt-text-styles";
10
- const HIGHLIGHT_CLASS = "accessify-alt-missing";
11
- const BADGE_CLASS = "accessify-alt-badge";
12
- const PANEL_CLASS = "accessify-alt-panel";
13
- function getStyles() {
14
- return `
15
- .${HIGHLIGHT_CLASS} {
16
- outline: 3px dashed #e63946 !important;
17
- outline-offset: 3px !important;
18
- position: relative !important;
19
- cursor: pointer !important;
20
- transition: outline-color 0.2s ease !important;
21
- }
22
- .${HIGHLIGHT_CLASS}:hover {
23
- outline-color: #f77f00 !important;
24
- outline-style: solid !important;
25
- }
26
- .${HIGHLIGHT_CLASS}::after {
27
- content: 'ALT';
28
- position: absolute;
29
- top: 4px;
30
- left: 4px;
31
- padding: 2px 6px;
32
- background: #e63946;
33
- color: #fff;
34
- font-family: system-ui, -apple-system, sans-serif;
35
- font-size: 10px;
36
- font-weight: 700;
37
- border-radius: 3px;
38
- line-height: 1.4;
39
- pointer-events: none;
40
- z-index: 1;
41
- }
42
-
43
- .${HIGHLIGHT_CLASS}.accessify-alt-processed {
44
- outline-color: #2a9d8f !important;
45
- outline-style: solid !important;
46
- }
47
- .${HIGHLIGHT_CLASS}.accessify-alt-processed::after {
48
- content: '\\2713 ALT';
49
- background: #2a9d8f;
50
- }
51
-
52
- .${BADGE_CLASS} {
53
- position: fixed;
54
- top: 12px;
55
- right: 12px;
56
- z-index: 2147483646;
57
- display: flex;
58
- align-items: center;
59
- gap: 6px;
60
- padding: 8px 14px;
61
- background: #1a1a2e;
62
- color: #f0f0f0;
63
- border-radius: 8px;
64
- box-shadow: 0 4px 16px rgba(0,0,0,0.3);
65
- font-family: system-ui, -apple-system, sans-serif;
66
- font-size: 13px;
67
- user-select: none;
68
- animation: accessify-alt-fade-in 0.2s ease;
69
- }
70
- .${BADGE_CLASS}-count {
71
- display: inline-flex;
72
- align-items: center;
73
- justify-content: center;
74
- min-width: 22px;
75
- height: 22px;
76
- padding: 0 6px;
77
- background: #e63946;
78
- color: #fff;
79
- font-size: 12px;
80
- font-weight: 700;
81
- border-radius: 11px;
82
- }
83
-
84
- .${PANEL_CLASS} {
85
- position: absolute;
86
- z-index: 2147483646;
87
- width: 360px;
88
- max-width: 90vw;
89
- padding: 16px;
90
- background: #1a1a2e;
91
- color: #f0f0f0;
92
- border-radius: 12px;
93
- box-shadow: 0 8px 32px rgba(0,0,0,0.4);
94
- font-family: system-ui, -apple-system, sans-serif;
95
- font-size: 14px;
96
- line-height: 1.5;
97
- animation: accessify-alt-fade-in 0.2s ease;
98
- }
99
- .${PANEL_CLASS}-header {
100
- display: flex;
101
- justify-content: space-between;
102
- align-items: center;
103
- margin-bottom: 10px;
104
- }
105
- .${PANEL_CLASS}-header h3 {
106
- margin: 0;
107
- font-size: 14px;
108
- font-weight: 600;
109
- color: #4ea8de;
110
- }
111
- .${PANEL_CLASS}-close {
112
- display: inline-flex;
113
- align-items: center;
114
- justify-content: center;
115
- width: 24px;
116
- height: 24px;
117
- padding: 0;
118
- border: none;
119
- border-radius: 4px;
120
- background: transparent;
121
- color: #888;
122
- font-size: 16px;
123
- cursor: pointer;
124
- }
125
- .${PANEL_CLASS}-close:hover { color: #fff; background: rgba(255,255,255,0.1); }
126
- .${PANEL_CLASS}-close:focus-visible { outline: 2px solid #4ea8de; outline-offset: 1px; }
127
-
128
- .${PANEL_CLASS}-disclaimer {
129
- padding: 6px 10px;
130
- margin-bottom: 10px;
131
- background: rgba(255,193,7,0.1);
132
- border: 1px solid rgba(255,193,7,0.25);
133
- border-radius: 6px;
134
- font-size: 11px;
135
- color: #ffd166;
136
- }
137
-
138
- .${PANEL_CLASS}-preview {
139
- max-width: 100%;
140
- max-height: 120px;
141
- border-radius: 6px;
142
- margin-bottom: 10px;
143
- object-fit: contain;
144
- background: rgba(255,255,255,0.05);
145
- }
146
-
147
- .${PANEL_CLASS}-textarea {
148
- width: 100%;
149
- min-height: 60px;
150
- padding: 10px;
151
- border: 1px solid rgba(255,255,255,0.15);
152
- border-radius: 8px;
153
- background: rgba(255,255,255,0.04);
154
- color: #f0f0f0;
155
- font-family: inherit;
156
- font-size: 13px;
157
- line-height: 1.5;
158
- resize: vertical;
159
- margin-bottom: 10px;
160
- box-sizing: border-box;
161
- }
162
- .${PANEL_CLASS}-textarea:focus {
163
- border-color: #4ea8de;
164
- outline: none;
165
- }
166
- .${PANEL_CLASS}-textarea:read-only {
167
- opacity: 0.7;
168
- cursor: default;
169
- }
170
-
171
- .${PANEL_CLASS}-actions {
172
- display: flex;
173
- gap: 6px;
174
- justify-content: flex-end;
175
- }
176
- .${PANEL_CLASS}-actions button {
177
- padding: 6px 14px;
178
- border: 1px solid rgba(255,255,255,0.15);
179
- border-radius: 6px;
180
- background: rgba(255,255,255,0.08);
181
- color: #f0f0f0;
182
- font-size: 12px;
183
- cursor: pointer;
184
- transition: background 0.15s ease;
185
- }
186
- .${PANEL_CLASS}-actions button:hover { background: rgba(255,255,255,0.18); }
187
- .${PANEL_CLASS}-actions button:focus-visible { outline: 2px solid #4ea8de; outline-offset: 1px; }
188
- .${PANEL_CLASS}-actions button.primary {
189
- background: rgba(78,168,222,0.2);
190
- color: #4ea8de;
191
- border-color: rgba(78,168,222,0.3);
192
- }
193
- .${PANEL_CLASS}-actions button.primary:hover { background: rgba(78,168,222,0.35); }
194
- .${PANEL_CLASS}-actions button.success {
195
- background: rgba(42,157,143,0.2);
196
- color: #2a9d8f;
197
- border-color: rgba(42,157,143,0.3);
198
- }
199
- .${PANEL_CLASS}-actions button.success:hover { background: rgba(42,157,143,0.35); }
200
-
201
- .${PANEL_CLASS}-spinner {
202
- display: flex;
203
- align-items: center;
204
- gap: 10px;
205
- padding: 16px 0;
206
- justify-content: center;
207
- color: #888;
208
- font-size: 13px;
209
- }
210
- .${PANEL_CLASS}-spinner::before {
211
- content: '';
212
- width: 18px;
213
- height: 18px;
214
- border: 2px solid rgba(78,168,222,0.3);
215
- border-top-color: #4ea8de;
216
- border-radius: 50%;
217
- animation: accessify-alt-spin 0.6s linear infinite;
218
- }
219
-
220
- .${PANEL_CLASS}-error {
221
- padding: 10px;
222
- background: rgba(220,53,69,0.12);
223
- border: 1px solid rgba(220,53,69,0.3);
224
- border-radius: 8px;
225
- color: #f0a0a8;
226
- font-size: 13px;
227
- margin-bottom: 10px;
228
- }
229
-
230
- @keyframes accessify-alt-fade-in {
231
- from { opacity: 0; transform: translateY(4px); }
232
- to { opacity: 1; transform: translateY(0); }
233
- }
234
- @keyframes accessify-alt-spin {
235
- to { transform: rotate(360deg); }
236
- }
237
- `;
238
- }
239
- function injectStyles() {
240
- if (document.getElementById(STYLE_ID)) return;
241
- styleEl = document.createElement("style");
242
- styleEl.id = STYLE_ID;
243
- styleEl.textContent = getStyles();
244
- document.head.appendChild(styleEl);
245
- }
246
- function removeStyles() {
247
- styleEl?.remove();
248
- styleEl = null;
249
- document.getElementById(STYLE_ID)?.remove();
250
- }
251
- function scanForMissingAlt() {
252
- const allImages = document.querySelectorAll("img");
253
- const missing = [];
254
- allImages.forEach((img) => {
255
- if (img.closest("#accessify-root")) return;
256
- if (img.width < 20 || img.height < 20) return;
257
- const alt = img.getAttribute("alt");
258
- if (alt === null || alt.trim() === "") {
259
- missing.push(img);
260
- }
261
- });
262
- return missing;
263
- }
264
- function highlightImages() {
265
- missingAltImages = scanForMissingAlt();
266
- missingAltImages.forEach((img) => {
267
- img.classList.add(HIGHLIGHT_CLASS);
268
- if (processedImages.has(img) && processedImages.get(img).applied) {
269
- img.classList.add("accessify-alt-processed");
270
- }
271
- img.addEventListener("click", handleImageClick);
272
- });
273
- updateBadge();
274
- }
275
- function removeHighlights() {
276
- missingAltImages.forEach((img) => {
277
- img.classList.remove(HIGHLIGHT_CLASS, "accessify-alt-processed");
278
- img.removeEventListener("click", handleImageClick);
279
- });
280
- missingAltImages = [];
281
- }
282
- function updateBadge() {
283
- removeBadge();
284
- const remaining = missingAltImages.filter(
285
- (img) => !processedImages.has(img) || !processedImages.get(img).applied
286
- ).length;
287
- if (remaining === 0 && missingAltImages.length > 0) {
288
- showBadge(
289
- lang.startsWith("de") ? "Alle Bilder haben Alt-Text!" : "All images have alt text!",
290
- "#2a9d8f",
291
- 0
292
- );
293
- return;
294
- }
295
- if (remaining === 0) return;
296
- showBadge(
297
- lang.startsWith("de") ? `${remaining} ${remaining === 1 ? "Bild" : "Bilder"} ohne Alt-Text` : `${remaining} ${remaining === 1 ? "image" : "images"} missing alt text`,
298
- "#e63946",
299
- remaining
300
- );
301
- }
302
- function showBadge(text, color, count) {
303
- const badge = document.createElement("div");
304
- badge.className = BADGE_CLASS;
305
- badge.setAttribute("role", "status");
306
- badge.setAttribute("aria-live", "polite");
307
- if (count > 0) {
308
- const countEl = document.createElement("span");
309
- countEl.className = `${BADGE_CLASS}-count`;
310
- countEl.style.background = color;
311
- countEl.textContent = String(count);
312
- badge.appendChild(countEl);
313
- }
314
- const label = document.createElement("span");
315
- label.textContent = text;
316
- badge.appendChild(label);
317
- document.body.appendChild(badge);
318
- badgeEl = badge;
319
- }
320
- function removeBadge() {
321
- badgeEl?.remove();
322
- badgeEl = null;
323
- }
324
- function handleImageClick(e) {
325
- e.preventDefault();
326
- e.stopPropagation();
327
- const img = e.currentTarget;
328
- showAltTextPanel(img);
329
- }
330
- function showAltTextPanel(img) {
331
- removeActivePanel();
332
- const panel = document.createElement("div");
333
- panel.className = PANEL_CLASS;
334
- panel.setAttribute("role", "dialog");
335
- panel.setAttribute("aria-label", lang.startsWith("de") ? "Alt-Text erzeugen" : "Generate Alt Text");
336
- const imgRect = img.getBoundingClientRect();
337
- const top = imgRect.bottom + window.scrollY + 8;
338
- const left = Math.min(
339
- Math.max(8, imgRect.left + window.scrollX),
340
- window.innerWidth - 380
341
- );
342
- panel.style.top = `${top}px`;
343
- panel.style.left = `${left}px`;
344
- const header = document.createElement("div");
345
- header.className = `${PANEL_CLASS}-header`;
346
- const title = document.createElement("h3");
347
- title.textContent = lang.startsWith("de") ? "Alt-Text erzeugen" : "Generate Alt Text";
348
- const closeBtn = document.createElement("button");
349
- closeBtn.className = `${PANEL_CLASS}-close`;
350
- closeBtn.setAttribute("aria-label", lang.startsWith("de") ? "Schließen" : "Close");
351
- closeBtn.innerHTML = "&times;";
352
- closeBtn.addEventListener("click", () => removeActivePanel());
353
- header.appendChild(title);
354
- header.appendChild(closeBtn);
355
- panel.appendChild(header);
356
- const disclaimer = document.createElement("div");
357
- disclaimer.className = `${PANEL_CLASS}-disclaimer`;
358
- disclaimer.textContent = lang.startsWith("de") ? "KI-generiert – bitte überprüfen" : "AI-generated – please review";
359
- panel.appendChild(disclaimer);
360
- const preview = document.createElement("img");
361
- preview.className = `${PANEL_CLASS}-preview`;
362
- preview.src = img.src;
363
- preview.alt = "Preview of image being described";
364
- panel.appendChild(preview);
365
- const existing = processedImages.get(img);
366
- if (existing && existing.generatedAlt) {
367
- renderEditableResult(panel, img, existing.generatedAlt, existing.applied);
368
- } else {
369
- generateAndShow(panel, img);
370
- }
371
- document.body.appendChild(panel);
372
- activePanelEl = panel;
373
- closeBtn.focus();
374
- const handleEsc = (e) => {
375
- if (e.key === "Escape") {
376
- removeActivePanel();
377
- document.removeEventListener("keydown", handleEsc);
378
- }
379
- };
380
- document.addEventListener("keydown", handleEsc);
381
- }
382
- async function generateAndShow(panel, img) {
383
- if (!aiService) {
384
- showPanelError(
385
- panel,
386
- lang.startsWith("de") ? "Bitte konfigurieren Sie einen OpenRouter API-Schlüssel, um KI-generierte Bildbeschreibungen zu nutzen." : "Please configure an OpenRouter API key to use AI alt-text generation."
387
- );
388
- return;
389
- }
390
- const spinner = document.createElement("div");
391
- spinner.className = `${PANEL_CLASS}-spinner`;
392
- spinner.textContent = lang.startsWith("de") ? "Alt-Text wird erzeugt..." : "Generating alt text...";
393
- panel.appendChild(spinner);
394
- try {
395
- const context = gatherImageContext(img);
396
- const altText = await aiService.generateAltText(img.src, context);
397
- spinner.remove();
398
- if (!altText) {
399
- showPanelError(
400
- panel,
401
- lang.startsWith("de") ? "Keine Beschreibung erhalten. Bitte versuchen Sie es erneut." : "No description received. Please try again."
402
- );
403
- return;
404
- }
405
- processedImages.set(img, {
406
- element: img,
407
- generatedAlt: altText,
408
- applied: false
409
- });
410
- renderEditableResult(panel, img, altText, false);
411
- } catch (err) {
412
- spinner.remove();
413
- if (err instanceof RateLimitError) {
414
- showPanelError(
415
- panel,
416
- lang.startsWith("de") ? "Kostenlose API-Grenze erreicht. Bitte versuchen Sie es in einer Minute erneut oder hinterlegen Sie einen eigenen API-Schlüssel." : "Free tier rate limit reached. Please try again in a minute or configure your own API key."
417
- );
418
- } else {
419
- const msg = err?.message || "";
420
- const is401 = msg.includes("401");
421
- showPanelError(
422
- panel,
423
- is401 ? lang.startsWith("de") ? "OpenRouter API-Schlüssel fehlt oder ungültig. Erstellen Sie einen kostenlosen Key auf openrouter.ai und übergeben Sie ihn als openRouterKey in der Widget-Konfiguration." : "OpenRouter API key missing or invalid. Create a free key at openrouter.ai and pass it as openRouterKey in the widget config." : lang.startsWith("de") ? "Fehler beim Erzeugen des Alt-Texts. Bitte versuchen Sie es erneut." : "Error generating alt text. Please try again."
424
- );
425
- }
426
- }
427
- }
428
- function renderEditableResult(panel, img, altText, alreadyApplied) {
429
- panel.querySelectorAll(
430
- `.${PANEL_CLASS}-textarea, .${PANEL_CLASS}-actions, .${PANEL_CLASS}-error, .${PANEL_CLASS}-spinner`
431
- ).forEach((el) => el.remove());
432
- const textarea = document.createElement("textarea");
433
- textarea.className = `${PANEL_CLASS}-textarea`;
434
- textarea.value = altText;
435
- textarea.readOnly = true;
436
- textarea.setAttribute("aria-label", lang.startsWith("de") ? "Alt-Text Beschreibung" : "Alt text description");
437
- panel.appendChild(textarea);
438
- const actions = document.createElement("div");
439
- actions.className = `${PANEL_CLASS}-actions`;
440
- const editBtn = document.createElement("button");
441
- editBtn.textContent = lang.startsWith("de") ? "Bearbeiten" : "Edit";
442
- editBtn.addEventListener("click", () => {
443
- textarea.readOnly = !textarea.readOnly;
444
- if (!textarea.readOnly) {
445
- textarea.focus();
446
- editBtn.textContent = lang.startsWith("de") ? "Fertig" : "Done";
447
- } else {
448
- editBtn.textContent = lang.startsWith("de") ? "Bearbeiten" : "Edit";
449
- const entry = processedImages.get(img);
450
- if (entry) entry.generatedAlt = textarea.value;
451
- }
452
- });
453
- const regenBtn = document.createElement("button");
454
- regenBtn.textContent = lang.startsWith("de") ? "Neu erzeugen" : "Regenerate";
455
- regenBtn.addEventListener("click", () => {
456
- processedImages.delete(img);
457
- textarea.remove();
458
- actions.remove();
459
- generateAndShow(panel, img);
460
- });
461
- const applyBtn = document.createElement("button");
462
- applyBtn.classList.add(alreadyApplied ? "success" : "primary");
463
- applyBtn.textContent = alreadyApplied ? lang.startsWith("de") ? "Angewendet" : "Applied" : lang.startsWith("de") ? "Übernehmen" : "Apply";
464
- applyBtn.addEventListener("click", () => {
465
- const finalText = textarea.value.trim();
466
- if (!finalText) return;
467
- img.alt = finalText;
468
- img.setAttribute("alt", finalText);
469
- const entry = processedImages.get(img) || {
470
- element: img,
471
- generatedAlt: finalText,
472
- applied: false
473
- };
474
- entry.applied = true;
475
- entry.generatedAlt = finalText;
476
- processedImages.set(img, entry);
477
- img.classList.add("accessify-alt-processed");
478
- applyBtn.classList.remove("primary");
479
- applyBtn.classList.add("success");
480
- applyBtn.textContent = lang.startsWith("de") ? "Angewendet!" : "Applied!";
481
- updateBadge();
482
- setTimeout(() => removeActivePanel(), 1200);
483
- });
484
- actions.appendChild(editBtn);
485
- actions.appendChild(regenBtn);
486
- actions.appendChild(applyBtn);
487
- panel.appendChild(actions);
488
- }
489
- function showPanelError(panel, message) {
490
- const errorEl = document.createElement("div");
491
- errorEl.className = `${PANEL_CLASS}-error`;
492
- errorEl.setAttribute("role", "alert");
493
- errorEl.textContent = message;
494
- panel.appendChild(errorEl);
495
- }
496
- function removeActivePanel() {
497
- activePanelEl?.remove();
498
- activePanelEl = null;
499
- }
500
- function gatherImageContext(img) {
501
- const parts = [];
502
- if (img.title) parts.push(`Title: ${img.title}`);
503
- try {
504
- const url = new URL(img.src, window.location.href);
505
- const filename = url.pathname.split("/").pop();
506
- if (filename) parts.push(`Filename: ${filename}`);
507
- } catch {
508
- }
509
- const figure = img.closest("figure");
510
- if (figure) {
511
- const caption = figure.querySelector("figcaption");
512
- if (caption?.textContent?.trim()) {
513
- parts.push(`Caption: ${caption.textContent.trim()}`);
514
- }
515
- }
516
- const parent = img.parentElement;
517
- if (parent) {
518
- const heading = parent.querySelector("h1, h2, h3, h4, h5, h6");
519
- if (heading?.textContent?.trim()) {
520
- parts.push(`Nearby heading: ${heading.textContent.trim()}`);
521
- }
522
- }
523
- const describedBy = img.getAttribute("aria-describedby");
524
- if (describedBy) {
525
- const descEl = document.getElementById(describedBy);
526
- if (descEl?.textContent?.trim()) {
527
- parts.push(`Description: ${descEl.textContent.trim()}`);
528
- }
529
- }
530
- return parts.join(". ");
531
- }
532
- function activate() {
533
- if (enabled) return;
534
- enabled = true;
535
- injectStyles();
536
- highlightImages();
537
- }
538
- function deactivate() {
539
- enabled = false;
540
- removeActivePanel();
541
- removeHighlights();
542
- removeBadge();
543
- removeStyles();
544
- }
545
- return {
546
- id: "alt-text",
547
- name: () => lang.startsWith("de") ? "Alt-Text erzeugen" : "Alt-Text Generator",
548
- description: lang.startsWith("de") ? "Bildbeschreibungen automatisch erstellen" : "Auto-generate image descriptions",
549
- icon: "alt-text",
550
- category: "ai",
551
- activate,
552
- deactivate,
553
- getState: () => ({
554
- id: "alt-text",
555
- enabled,
556
- value: {
557
- missingCount: missingAltImages.length,
558
- processedCount: processedImages.size,
559
- appliedCount: [...processedImages.values()].filter((p) => p.applied).length
560
- }
561
- })
562
- };
563
- }
564
- export {
565
- createAltTextModule as default
566
- };
567
- //# sourceMappingURL=alt-text-CrPRUX83.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"alt-text-CrPRUX83.js","sources":["../src/features/alt-text.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Accessify – Alt-Text Generation Feature\n// ---------------------------------------------------------------------------\n// Scans the page for images missing alt text, highlights them, and provides\n// AI-powered alt-text generation via the vision model. Users can review,\n// edit, and apply generated descriptions directly to the DOM.\n// ---------------------------------------------------------------------------\n\nimport type { FeatureModule, FeatureState, AIService } from '../types';\nimport { RateLimitError } from '../services/ai-service';\n\ninterface ProcessedImage {\n element: HTMLImageElement;\n generatedAlt: string;\n applied: boolean;\n}\n\nexport default function createAltTextModule(\n aiService?: AIService,\n lang: string = 'de',\n): FeatureModule {\n let enabled = false;\n let styleEl: HTMLStyleElement | null = null;\n let badgeEl: HTMLDivElement | null = null;\n let activePanelEl: HTMLDivElement | null = null;\n let missingAltImages: HTMLImageElement[] = [];\n const processedImages = new Map<HTMLImageElement, ProcessedImage>();\n\n const STYLE_ID = 'accessify-alt-text-styles';\n const HIGHLIGHT_CLASS = 'accessify-alt-missing';\n const BADGE_CLASS = 'accessify-alt-badge';\n const PANEL_CLASS = 'accessify-alt-panel';\n\n // -------------------------------------------------------------------------\n // Styles\n // -------------------------------------------------------------------------\n\n function getStyles(): string {\n return `\n .${HIGHLIGHT_CLASS} {\n outline: 3px dashed #e63946 !important;\n outline-offset: 3px !important;\n position: relative !important;\n cursor: pointer !important;\n transition: outline-color 0.2s ease !important;\n }\n .${HIGHLIGHT_CLASS}:hover {\n outline-color: #f77f00 !important;\n outline-style: solid !important;\n }\n .${HIGHLIGHT_CLASS}::after {\n content: 'ALT';\n position: absolute;\n top: 4px;\n left: 4px;\n padding: 2px 6px;\n background: #e63946;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 10px;\n font-weight: 700;\n border-radius: 3px;\n line-height: 1.4;\n pointer-events: none;\n z-index: 1;\n }\n\n .${HIGHLIGHT_CLASS}.accessify-alt-processed {\n outline-color: #2a9d8f !important;\n outline-style: solid !important;\n }\n .${HIGHLIGHT_CLASS}.accessify-alt-processed::after {\n content: '\\\\2713 ALT';\n background: #2a9d8f;\n }\n\n .${BADGE_CLASS} {\n position: fixed;\n top: 12px;\n right: 12px;\n z-index: 2147483646;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 14px;\n background: #1a1a2e;\n color: #f0f0f0;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.3);\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 13px;\n user-select: none;\n animation: accessify-alt-fade-in 0.2s ease;\n }\n .${BADGE_CLASS}-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n height: 22px;\n padding: 0 6px;\n background: #e63946;\n color: #fff;\n font-size: 12px;\n font-weight: 700;\n border-radius: 11px;\n }\n\n .${PANEL_CLASS} {\n position: absolute;\n z-index: 2147483646;\n width: 360px;\n max-width: 90vw;\n padding: 16px;\n background: #1a1a2e;\n color: #f0f0f0;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n animation: accessify-alt-fade-in 0.2s ease;\n }\n .${PANEL_CLASS}-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 10px;\n }\n .${PANEL_CLASS}-header h3 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: #4ea8de;\n }\n .${PANEL_CLASS}-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: #888;\n font-size: 16px;\n cursor: pointer;\n }\n .${PANEL_CLASS}-close:hover { color: #fff; background: rgba(255,255,255,0.1); }\n .${PANEL_CLASS}-close:focus-visible { outline: 2px solid #4ea8de; outline-offset: 1px; }\n\n .${PANEL_CLASS}-disclaimer {\n padding: 6px 10px;\n margin-bottom: 10px;\n background: rgba(255,193,7,0.1);\n border: 1px solid rgba(255,193,7,0.25);\n border-radius: 6px;\n font-size: 11px;\n color: #ffd166;\n }\n\n .${PANEL_CLASS}-preview {\n max-width: 100%;\n max-height: 120px;\n border-radius: 6px;\n margin-bottom: 10px;\n object-fit: contain;\n background: rgba(255,255,255,0.05);\n }\n\n .${PANEL_CLASS}-textarea {\n width: 100%;\n min-height: 60px;\n padding: 10px;\n border: 1px solid rgba(255,255,255,0.15);\n border-radius: 8px;\n background: rgba(255,255,255,0.04);\n color: #f0f0f0;\n font-family: inherit;\n font-size: 13px;\n line-height: 1.5;\n resize: vertical;\n margin-bottom: 10px;\n box-sizing: border-box;\n }\n .${PANEL_CLASS}-textarea:focus {\n border-color: #4ea8de;\n outline: none;\n }\n .${PANEL_CLASS}-textarea:read-only {\n opacity: 0.7;\n cursor: default;\n }\n\n .${PANEL_CLASS}-actions {\n display: flex;\n gap: 6px;\n justify-content: flex-end;\n }\n .${PANEL_CLASS}-actions button {\n padding: 6px 14px;\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 font-size: 12px;\n cursor: pointer;\n transition: background 0.15s ease;\n }\n .${PANEL_CLASS}-actions button:hover { background: rgba(255,255,255,0.18); }\n .${PANEL_CLASS}-actions button:focus-visible { outline: 2px solid #4ea8de; outline-offset: 1px; }\n .${PANEL_CLASS}-actions button.primary {\n background: rgba(78,168,222,0.2);\n color: #4ea8de;\n border-color: rgba(78,168,222,0.3);\n }\n .${PANEL_CLASS}-actions button.primary:hover { background: rgba(78,168,222,0.35); }\n .${PANEL_CLASS}-actions button.success {\n background: rgba(42,157,143,0.2);\n color: #2a9d8f;\n border-color: rgba(42,157,143,0.3);\n }\n .${PANEL_CLASS}-actions button.success:hover { background: rgba(42,157,143,0.35); }\n\n .${PANEL_CLASS}-spinner {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 0;\n justify-content: center;\n color: #888;\n font-size: 13px;\n }\n .${PANEL_CLASS}-spinner::before {\n content: '';\n width: 18px;\n height: 18px;\n border: 2px solid rgba(78,168,222,0.3);\n border-top-color: #4ea8de;\n border-radius: 50%;\n animation: accessify-alt-spin 0.6s linear infinite;\n }\n\n .${PANEL_CLASS}-error {\n padding: 10px;\n background: rgba(220,53,69,0.12);\n border: 1px solid rgba(220,53,69,0.3);\n border-radius: 8px;\n color: #f0a0a8;\n font-size: 13px;\n margin-bottom: 10px;\n }\n\n @keyframes accessify-alt-fade-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes accessify-alt-spin {\n to { transform: rotate(360deg); }\n }\n `;\n }\n\n function injectStyles() {\n if (document.getElementById(STYLE_ID)) return;\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ID;\n styleEl.textContent = getStyles();\n document.head.appendChild(styleEl);\n }\n\n function removeStyles() {\n styleEl?.remove();\n styleEl = null;\n document.getElementById(STYLE_ID)?.remove();\n }\n\n // -------------------------------------------------------------------------\n // Scan for images missing alt text\n // -------------------------------------------------------------------------\n\n function scanForMissingAlt(): HTMLImageElement[] {\n const allImages = document.querySelectorAll<HTMLImageElement>('img');\n const missing: HTMLImageElement[] = [];\n\n allImages.forEach((img) => {\n // Skip images that are inside the widget itself\n if (img.closest('#accessify-root')) return;\n // Skip tiny tracking pixels / spacers\n if (img.width < 20 || img.height < 20) return;\n\n const alt = img.getAttribute('alt');\n if (alt === null || alt.trim() === '') {\n missing.push(img);\n }\n });\n\n return missing;\n }\n\n // -------------------------------------------------------------------------\n // Highlight images\n // -------------------------------------------------------------------------\n\n function highlightImages() {\n missingAltImages = scanForMissingAlt();\n\n missingAltImages.forEach((img) => {\n img.classList.add(HIGHLIGHT_CLASS);\n\n // Mark already-processed images\n if (processedImages.has(img) && processedImages.get(img)!.applied) {\n img.classList.add('accessify-alt-processed');\n }\n\n img.addEventListener('click', handleImageClick);\n });\n\n updateBadge();\n }\n\n function removeHighlights() {\n missingAltImages.forEach((img) => {\n img.classList.remove(HIGHLIGHT_CLASS, 'accessify-alt-processed');\n img.removeEventListener('click', handleImageClick);\n });\n missingAltImages = [];\n }\n\n // -------------------------------------------------------------------------\n // Badge (count of images needing alt text)\n // -------------------------------------------------------------------------\n\n function updateBadge() {\n removeBadge();\n\n const remaining = missingAltImages.filter(\n (img) => !processedImages.has(img) || !processedImages.get(img)!.applied,\n ).length;\n\n if (remaining === 0 && missingAltImages.length > 0) {\n // All done\n showBadge(\n lang.startsWith('de')\n ? 'Alle Bilder haben Alt-Text!'\n : 'All images have alt text!',\n '#2a9d8f',\n 0,\n );\n return;\n }\n\n if (remaining === 0) return;\n\n showBadge(\n lang.startsWith('de')\n ? `${remaining} ${remaining === 1 ? 'Bild' : 'Bilder'} ohne Alt-Text`\n : `${remaining} ${remaining === 1 ? 'image' : 'images'} missing alt text`,\n '#e63946',\n remaining,\n );\n }\n\n function showBadge(text: string, color: string, count: number) {\n const badge = document.createElement('div');\n badge.className = BADGE_CLASS;\n badge.setAttribute('role', 'status');\n badge.setAttribute('aria-live', 'polite');\n\n if (count > 0) {\n const countEl = document.createElement('span');\n countEl.className = `${BADGE_CLASS}-count`;\n countEl.style.background = color;\n countEl.textContent = String(count);\n badge.appendChild(countEl);\n }\n\n const label = document.createElement('span');\n label.textContent = text;\n badge.appendChild(label);\n\n document.body.appendChild(badge);\n badgeEl = badge;\n }\n\n function removeBadge() {\n badgeEl?.remove();\n badgeEl = null;\n }\n\n // -------------------------------------------------------------------------\n // Image click handler\n // -------------------------------------------------------------------------\n\n function handleImageClick(e: Event) {\n e.preventDefault();\n e.stopPropagation();\n\n const img = e.currentTarget as HTMLImageElement;\n showAltTextPanel(img);\n }\n\n // -------------------------------------------------------------------------\n // Alt-text generation panel\n // -------------------------------------------------------------------------\n\n function showAltTextPanel(img: HTMLImageElement) {\n removeActivePanel();\n\n const panel = document.createElement('div');\n panel.className = PANEL_CLASS;\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-label', lang.startsWith('de') ? 'Alt-Text erzeugen' : 'Generate Alt Text');\n\n // Position near the image\n const imgRect = img.getBoundingClientRect();\n const top = imgRect.bottom + window.scrollY + 8;\n const left = Math.min(\n Math.max(8, imgRect.left + window.scrollX),\n window.innerWidth - 380,\n );\n panel.style.top = `${top}px`;\n panel.style.left = `${left}px`;\n\n // Header\n const header = document.createElement('div');\n header.className = `${PANEL_CLASS}-header`;\n\n const title = document.createElement('h3');\n title.textContent = lang.startsWith('de') ? 'Alt-Text erzeugen' : 'Generate Alt Text';\n\n const closeBtn = document.createElement('button');\n closeBtn.className = `${PANEL_CLASS}-close`;\n closeBtn.setAttribute('aria-label', lang.startsWith('de') ? 'Schließen' : 'Close');\n closeBtn.innerHTML = '&times;';\n closeBtn.addEventListener('click', () => removeActivePanel());\n\n header.appendChild(title);\n header.appendChild(closeBtn);\n panel.appendChild(header);\n\n // AI disclaimer\n const disclaimer = document.createElement('div');\n disclaimer.className = `${PANEL_CLASS}-disclaimer`;\n disclaimer.textContent = lang.startsWith('de')\n ? 'KI-generiert \\u2013 bitte \\u00fcberpr\\u00fcfen'\n : 'AI-generated \\u2013 please review';\n panel.appendChild(disclaimer);\n\n // Image preview\n const preview = document.createElement('img');\n preview.className = `${PANEL_CLASS}-preview`;\n preview.src = img.src;\n preview.alt = 'Preview of image being described';\n panel.appendChild(preview);\n\n // Check if already processed\n const existing = processedImages.get(img);\n if (existing && existing.generatedAlt) {\n renderEditableResult(panel, img, existing.generatedAlt, existing.applied);\n } else {\n generateAndShow(panel, img);\n }\n\n document.body.appendChild(panel);\n activePanelEl = panel;\n closeBtn.focus();\n\n // Close on Escape\n const handleEsc = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n removeActivePanel();\n document.removeEventListener('keydown', handleEsc);\n }\n };\n document.addEventListener('keydown', handleEsc);\n }\n\n async function generateAndShow(panel: HTMLDivElement, img: HTMLImageElement) {\n // Check for API service\n if (!aiService) {\n showPanelError(panel, lang.startsWith('de')\n ? 'Bitte konfigurieren Sie einen OpenRouter API-Schl\\u00fcssel, um KI-generierte Bildbeschreibungen zu nutzen.'\n : 'Please configure an OpenRouter API key to use AI alt-text generation.',\n );\n return;\n }\n\n // Spinner\n const spinner = document.createElement('div');\n spinner.className = `${PANEL_CLASS}-spinner`;\n spinner.textContent = lang.startsWith('de') ? 'Alt-Text wird erzeugt...' : 'Generating alt text...';\n panel.appendChild(spinner);\n\n try {\n // Gather context from surrounding elements\n const context = gatherImageContext(img);\n const altText = await aiService.generateAltText(img.src, context);\n\n spinner.remove();\n\n if (!altText) {\n showPanelError(panel, lang.startsWith('de')\n ? 'Keine Beschreibung erhalten. Bitte versuchen Sie es erneut.'\n : 'No description received. Please try again.',\n );\n return;\n }\n\n // Store in processed map\n processedImages.set(img, {\n element: img,\n generatedAlt: altText,\n applied: false,\n });\n\n renderEditableResult(panel, img, altText, false);\n } catch (err) {\n spinner.remove();\n if (err instanceof RateLimitError) {\n showPanelError(panel, lang.startsWith('de')\n ? 'Kostenlose API-Grenze erreicht. Bitte versuchen Sie es in einer Minute erneut oder hinterlegen Sie einen eigenen API-Schl\\u00fcssel.'\n : 'Free tier rate limit reached. Please try again in a minute or configure your own API key.',\n );\n } else {\n const msg = (err as Error)?.message || '';\n const is401 = msg.includes('401');\n showPanelError(panel, is401\n ? (lang.startsWith('de')\n ? 'OpenRouter API-Schlüssel fehlt oder ungültig. Erstellen Sie einen kostenlosen Key auf openrouter.ai und übergeben Sie ihn als openRouterKey in der Widget-Konfiguration.'\n : 'OpenRouter API key missing or invalid. Create a free key at openrouter.ai and pass it as openRouterKey in the widget config.')\n : (lang.startsWith('de')\n ? 'Fehler beim Erzeugen des Alt-Texts. Bitte versuchen Sie es erneut.'\n : 'Error generating alt text. Please try again.'),\n );\n }\n }\n }\n\n function renderEditableResult(\n panel: HTMLDivElement,\n img: HTMLImageElement,\n altText: string,\n alreadyApplied: boolean,\n ) {\n // Remove existing content/error areas (but keep header, disclaimer, preview)\n panel.querySelectorAll(\n `.${PANEL_CLASS}-textarea, .${PANEL_CLASS}-actions, .${PANEL_CLASS}-error, .${PANEL_CLASS}-spinner`,\n ).forEach((el) => el.remove());\n\n // Textarea for editing\n const textarea = document.createElement('textarea');\n textarea.className = `${PANEL_CLASS}-textarea`;\n textarea.value = altText;\n textarea.readOnly = true;\n textarea.setAttribute('aria-label', lang.startsWith('de') ? 'Alt-Text Beschreibung' : 'Alt text description');\n panel.appendChild(textarea);\n\n // Actions\n const actions = document.createElement('div');\n actions.className = `${PANEL_CLASS}-actions`;\n\n // Edit button\n const editBtn = document.createElement('button');\n editBtn.textContent = lang.startsWith('de') ? 'Bearbeiten' : 'Edit';\n editBtn.addEventListener('click', () => {\n textarea.readOnly = !textarea.readOnly;\n if (!textarea.readOnly) {\n textarea.focus();\n editBtn.textContent = lang.startsWith('de') ? 'Fertig' : 'Done';\n } else {\n editBtn.textContent = lang.startsWith('de') ? 'Bearbeiten' : 'Edit';\n // Update stored text\n const entry = processedImages.get(img);\n if (entry) entry.generatedAlt = textarea.value;\n }\n });\n\n // Regenerate button\n const regenBtn = document.createElement('button');\n regenBtn.textContent = lang.startsWith('de') ? 'Neu erzeugen' : 'Regenerate';\n regenBtn.addEventListener('click', () => {\n processedImages.delete(img);\n // Clear textarea and re-generate\n textarea.remove();\n actions.remove();\n generateAndShow(panel, img);\n });\n\n // Apply button\n const applyBtn = document.createElement('button');\n applyBtn.classList.add(alreadyApplied ? 'success' : 'primary');\n applyBtn.textContent = alreadyApplied\n ? (lang.startsWith('de') ? 'Angewendet' : 'Applied')\n : (lang.startsWith('de') ? '\\u00dcbernehmen' : 'Apply');\n applyBtn.addEventListener('click', () => {\n const finalText = textarea.value.trim();\n if (!finalText) return;\n\n // Apply to the DOM\n img.alt = finalText;\n img.setAttribute('alt', finalText);\n\n // Mark as processed\n const entry = processedImages.get(img) || {\n element: img,\n generatedAlt: finalText,\n applied: false,\n };\n entry.applied = true;\n entry.generatedAlt = finalText;\n processedImages.set(img, entry);\n\n // Update visual state\n img.classList.add('accessify-alt-processed');\n applyBtn.classList.remove('primary');\n applyBtn.classList.add('success');\n applyBtn.textContent = lang.startsWith('de') ? 'Angewendet!' : 'Applied!';\n\n updateBadge();\n\n // Auto-close after a beat\n setTimeout(() => removeActivePanel(), 1200);\n });\n\n actions.appendChild(editBtn);\n actions.appendChild(regenBtn);\n actions.appendChild(applyBtn);\n panel.appendChild(actions);\n }\n\n function showPanelError(panel: HTMLDivElement, message: string) {\n const errorEl = document.createElement('div');\n errorEl.className = `${PANEL_CLASS}-error`;\n errorEl.setAttribute('role', 'alert');\n errorEl.textContent = message;\n panel.appendChild(errorEl);\n }\n\n function removeActivePanel() {\n activePanelEl?.remove();\n activePanelEl = null;\n }\n\n // -------------------------------------------------------------------------\n // Gather context around an image\n // -------------------------------------------------------------------------\n\n function gatherImageContext(img: HTMLImageElement): string {\n const parts: string[] = [];\n\n // Title attribute\n if (img.title) parts.push(`Title: ${img.title}`);\n\n // Filename from src\n try {\n const url = new URL(img.src, window.location.href);\n const filename = url.pathname.split('/').pop();\n if (filename) parts.push(`Filename: ${filename}`);\n } catch {\n // ignore\n }\n\n // Surrounding figcaption\n const figure = img.closest('figure');\n if (figure) {\n const caption = figure.querySelector('figcaption');\n if (caption?.textContent?.trim()) {\n parts.push(`Caption: ${caption.textContent.trim()}`);\n }\n }\n\n // Nearby heading\n const parent = img.parentElement;\n if (parent) {\n const heading = parent.querySelector('h1, h2, h3, h4, h5, h6');\n if (heading?.textContent?.trim()) {\n parts.push(`Nearby heading: ${heading.textContent.trim()}`);\n }\n }\n\n // aria-describedby\n const describedBy = img.getAttribute('aria-describedby');\n if (describedBy) {\n const descEl = document.getElementById(describedBy);\n if (descEl?.textContent?.trim()) {\n parts.push(`Description: ${descEl.textContent.trim()}`);\n }\n }\n\n return parts.join('. ');\n }\n\n // -------------------------------------------------------------------------\n // Module lifecycle\n // -------------------------------------------------------------------------\n\n function activate() {\n if (enabled) return;\n enabled = true;\n\n injectStyles();\n highlightImages();\n }\n\n function deactivate() {\n enabled = false;\n\n removeActivePanel();\n removeHighlights();\n removeBadge();\n removeStyles();\n // Keep processedImages so re-activation doesn't lose already-generated text\n }\n\n // -------------------------------------------------------------------------\n // FeatureModule interface\n // -------------------------------------------------------------------------\n\n return {\n id: 'alt-text',\n name: () => (lang.startsWith('de') ? 'Alt-Text erzeugen' : 'Alt-Text Generator'),\n description: lang.startsWith('de')\n ? 'Bildbeschreibungen automatisch erstellen'\n : 'Auto-generate image descriptions',\n icon: 'alt-text',\n category: 'ai',\n activate,\n deactivate,\n getState: (): FeatureState => ({\n id: 'alt-text',\n enabled,\n value: {\n missingCount: missingAltImages.length,\n processedCount: processedImages.size,\n appliedCount: [...processedImages.values()].filter((p) => p.applied).length,\n },\n }),\n };\n}\n"],"names":[],"mappings":"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"}