accessify-widget 0.3.75 → 0.3.77

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.
@@ -4,13 +4,12 @@ function createAnimationStopModule() {
4
4
  const STORAGE_KEY = "accessify-animation-stop";
5
5
  let mutationObserver = null;
6
6
  let scanInterval = null;
7
- let scrollHandler = null;
8
7
  let originalVideoPlay = null;
9
8
  let originalAnimate = null;
10
- let pausedVideos = [];
11
9
  let gifOriginals = /* @__PURE__ */ new Map();
12
10
  let debounceTimer = null;
13
11
  let delayedTimers = [];
12
+ const WIDGET_SEL = ":not(#accessify-root):not(#accessify-root *)";
14
13
  function isWidgetElement(el) {
15
14
  try {
16
15
  let node = el;
@@ -24,93 +23,15 @@ function createAnimationStopModule() {
24
23
  }
25
24
  return false;
26
25
  }
27
- const VISUAL_ONLY_PROPS = /* @__PURE__ */ new Set([
28
- "transform",
29
- "scale",
30
- "rotate",
31
- "translate",
32
- "filter",
33
- "backdropFilter",
34
- "backdrop-filter",
35
- "webkitTransform",
36
- "webkitFilter",
37
- "-webkit-transform",
38
- "-webkit-filter",
39
- "offset",
40
- "easing",
41
- "composite"
42
- ]);
43
- function isVisualOnlyKeyframes(keyframes) {
44
- if (!keyframes) return false;
45
- try {
46
- if (Array.isArray(keyframes)) {
47
- for (const kf of keyframes) {
48
- if (!kf || typeof kf !== "object") return false;
49
- for (const key of Object.keys(kf)) {
50
- if (!VISUAL_ONLY_PROPS.has(key)) return false;
51
- }
52
- }
53
- return true;
54
- }
55
- for (const key of Object.keys(keyframes)) {
56
- if (!VISUAL_ONLY_PROPS.has(key)) return false;
57
- }
58
- return true;
59
- } catch {
60
- return false;
61
- }
62
- }
63
- function patchAnimate() {
64
- if (originalAnimate) return;
65
- originalAnimate = Element.prototype.animate;
66
- Element.prototype.animate = function(keyframes, options) {
67
- if (!enabled) {
68
- return originalAnimate.call(this, keyframes, options);
69
- }
70
- if (isWidgetElement(this)) {
71
- return originalAnimate.call(this, keyframes, options);
72
- }
73
- const visualOnly = isVisualOnlyKeyframes(keyframes);
74
- let opts;
75
- if (typeof options === "number") {
76
- opts = { duration: 1e-3, fill: "forwards" };
77
- } else {
78
- opts = {
79
- ...options || {},
80
- duration: 1e-3,
81
- delay: 0,
82
- iterations: 1,
83
- fill: "forwards"
84
- };
85
- delete opts.iterationStart;
86
- delete opts.endDelay;
87
- }
88
- const anim = originalAnimate.call(this, keyframes, opts);
89
- try {
90
- if (visualOnly) {
91
- anim.cancel();
92
- } else {
93
- anim.finish();
94
- }
95
- } catch {
96
- }
97
- return anim;
98
- };
99
- }
100
- function restoreAnimate() {
101
- if (originalAnimate) {
102
- Element.prototype.animate = originalAnimate;
103
- originalAnimate = null;
104
- }
105
- }
106
26
  function injectStyles() {
107
27
  if (document.getElementById(STYLE_ID)) return;
108
28
  const style = document.createElement("style");
109
29
  style.id = STYLE_ID;
110
30
  style.textContent = `
111
- *:not(#accessify-root):not(#accessify-root *),
112
- *:not(#accessify-root):not(#accessify-root *)::before,
113
- *:not(#accessify-root):not(#accessify-root *)::after {
31
+ /* --- Kill all CSS-driven animations & transitions --- */
32
+ *${WIDGET_SEL},
33
+ *${WIDGET_SEL}::before,
34
+ *${WIDGET_SEL}::after {
114
35
  animation-duration: 0.001ms !important;
115
36
  animation-iteration-count: 1 !important;
116
37
  animation-delay: 0s !important;
@@ -121,14 +42,36 @@ function createAnimationStopModule() {
121
42
  animation-timeline: auto !important;
122
43
  scroll-timeline: none !important;
123
44
  }
124
- /* Neutralize interaction transforms (hover zoom, click scale, focus
125
- lift). Without this, CSS like \`.card:hover { transform: scale(1.05) }\`
126
- still visibly changes the element just instantly. */
127
- *:not(#accessify-root):not(#accessify-root *):hover,
128
- *:not(#accessify-root):not(#accessify-root *):focus,
129
- *:not(#accessify-root):not(#accessify-root *):focus-within,
130
- *:not(#accessify-root):not(#accessify-root *):focus-visible,
131
- *:not(#accessify-root):not(#accessify-root *):active {
45
+
46
+ /* --- Force Framer appear-elements visible --- *
47
+ * Framer sets inline opacity:0.001 + transform:translateY(...)
48
+ * on elements waiting for scroll/appear animations. We override
49
+ * with !important which beats inline styles. When animation-stop
50
+ * is deactivated, removing this stylesheet instantly restores
51
+ * Framer's inline values — no cleanup needed. */
52
+ [data-framer-appear-id]${WIDGET_SEL} {
53
+ opacity: 1 !important;
54
+ transform: none !important;
55
+ }
56
+
57
+ /* --- Force any inline-hidden elements visible ---
58
+ * Some animation frameworks (AOS, wow.js, Framer scroll) hide
59
+ * elements via inline opacity:0 or visibility:hidden. */
60
+ [style*="opacity: 0"]${WIDGET_SEL},
61
+ [style*="opacity:0"]${WIDGET_SEL} {
62
+ opacity: 1 !important;
63
+ }
64
+ [style*="visibility: hidden"]${WIDGET_SEL},
65
+ [style*="visibility:hidden"]${WIDGET_SEL} {
66
+ visibility: visible !important;
67
+ }
68
+
69
+ /* --- Neutralize interaction transforms --- */
70
+ *${WIDGET_SEL}:hover,
71
+ *${WIDGET_SEL}:focus,
72
+ *${WIDGET_SEL}:focus-within,
73
+ *${WIDGET_SEL}:focus-visible,
74
+ *${WIDGET_SEL}:active {
132
75
  transform: none !important;
133
76
  scale: 1 !important;
134
77
  rotate: 0deg !important;
@@ -151,24 +94,53 @@ function createAnimationStopModule() {
151
94
  }
152
95
  return false;
153
96
  }
154
- function finishAllAnimations() {
97
+ function cancelAllAnimations() {
155
98
  try {
156
99
  const animations = document.getAnimations?.();
157
100
  if (!animations) return;
158
101
  for (const anim of animations) {
159
102
  if (isWidgetAnimation(anim)) continue;
160
103
  try {
161
- anim.finish();
104
+ anim.cancel();
162
105
  } catch {
163
- try {
164
- anim.cancel();
165
- } catch {
166
- }
167
106
  }
168
107
  }
169
108
  } catch {
170
109
  }
171
110
  }
111
+ function patchAnimate() {
112
+ if (originalAnimate) return;
113
+ originalAnimate = Element.prototype.animate;
114
+ Element.prototype.animate = function(keyframes, options) {
115
+ if (!enabled) {
116
+ return originalAnimate.call(this, keyframes, options);
117
+ }
118
+ if (isWidgetElement(this)) {
119
+ return originalAnimate.call(this, keyframes, options);
120
+ }
121
+ const opts = typeof options === "number" ? { duration: 1e-3, fill: "none" } : {
122
+ ...options || {},
123
+ duration: 1e-3,
124
+ delay: 0,
125
+ iterations: 1,
126
+ fill: "none"
127
+ };
128
+ delete opts.iterationStart;
129
+ delete opts.endDelay;
130
+ const anim = originalAnimate.call(this, keyframes, opts);
131
+ try {
132
+ anim.cancel();
133
+ } catch {
134
+ }
135
+ return anim;
136
+ };
137
+ }
138
+ function restoreAnimate() {
139
+ if (originalAnimate) {
140
+ Element.prototype.animate = originalAnimate;
141
+ originalAnimate = null;
142
+ }
143
+ }
172
144
  function patchGSAP() {
173
145
  const g = window.gsap;
174
146
  if (g?.globalTimeline) {
@@ -189,108 +161,11 @@ function createAnimationStopModule() {
189
161
  }
190
162
  }
191
163
  }
192
- function forceHiddenElementsVisible() {
193
- const viewH = window.innerHeight;
194
- const margin = viewH * 1.5;
195
- const candidates = document.querySelectorAll(
196
- '[style*="opacity"]:not(#accessify-root):not(#accessify-root *),[style*="transform"]:not(#accessify-root):not(#accessify-root *),[style*="visibility"]:not(#accessify-root):not(#accessify-root *),[style*="clip-path"]:not(#accessify-root):not(#accessify-root *),[data-framer-appear-id]:not(#accessify-root):not(#accessify-root *)'
197
- );
198
- for (const el of candidates) {
199
- if (el.tagName === "SCRIPT" || el.tagName === "STYLE" || el.tagName === "NOSCRIPT") continue;
200
- if (el.dataset.a11yOrigStyle) continue;
201
- const rect = el.getBoundingClientRect();
202
- if (rect.top > viewH + margin || rect.bottom < -margin) continue;
203
- const computed = window.getComputedStyle(el);
204
- if (computed.display === "none") continue;
205
- const inline = el.style;
206
- let needsFix = false;
207
- const originals = {};
208
- const opacityVal = parseFloat(computed.opacity);
209
- if (opacityVal < 0.1 && (inline.opacity !== "" || el.hasAttribute("data-framer-appear-id"))) {
210
- originals.opacity = inline.opacity || "";
211
- el.style.opacity = "1";
212
- needsFix = true;
213
- }
214
- if (inline.transform && /translate[XY3d]\([^0]/.test(inline.transform)) {
215
- originals.transform = inline.transform;
216
- el.style.transform = "none";
217
- needsFix = true;
218
- }
219
- if (inline.visibility === "hidden") {
220
- originals.visibility = inline.visibility;
221
- el.style.visibility = "visible";
222
- needsFix = true;
223
- }
224
- if (inline.clipPath && inline.clipPath !== "none") {
225
- originals.clipPath = inline.clipPath;
226
- el.style.clipPath = "none";
227
- needsFix = true;
228
- }
229
- if (needsFix) {
230
- el.dataset.a11yOrigStyle = JSON.stringify(originals);
231
- }
232
- }
233
- }
234
- function restoreHiddenElements() {
235
- document.querySelectorAll("[data-a11y-orig-style]").forEach((el) => {
236
- try {
237
- const orig = JSON.parse(el.dataset.a11yOrigStyle || "{}");
238
- for (const [prop, val] of Object.entries(orig)) {
239
- if (val === "") {
240
- el.style.removeProperty(prop);
241
- } else {
242
- el.style[prop] = val;
243
- }
244
- }
245
- } catch {
246
- }
247
- delete el.dataset.a11yOrigStyle;
248
- });
249
- }
250
- function freezeScrollAnimations() {
251
- document.querySelectorAll(
252
- '[style*="transform"]:not(#accessify-root):not(#accessify-root *),[style*="opacity"]:not(#accessify-root):not(#accessify-root *)'
253
- ).forEach((el) => {
254
- if (el.dataset.a11yFrozenStyle) return;
255
- const s = el.style;
256
- if (s.transform || s.opacity) {
257
- el.dataset.a11yFrozenStyle = JSON.stringify({
258
- transform: s.transform || "",
259
- opacity: s.opacity || ""
260
- });
261
- }
262
- });
263
- if (!scrollHandler) {
264
- scrollHandler = () => {
265
- if (!enabled) return;
266
- document.querySelectorAll("[data-a11y-frozen-style]").forEach((el) => {
267
- try {
268
- const frozen = JSON.parse(el.dataset.a11yFrozenStyle || "{}");
269
- if (frozen.transform && el.style.transform !== frozen.transform) {
270
- el.style.transform = frozen.transform;
271
- }
272
- } catch {
273
- }
274
- });
275
- };
276
- window.addEventListener("scroll", scrollHandler, { passive: true, capture: true });
277
- }
278
- }
279
- function unfreezeScrollAnimations() {
280
- if (scrollHandler) {
281
- window.removeEventListener("scroll", scrollHandler, true);
282
- scrollHandler = null;
283
- }
284
- document.querySelectorAll("[data-a11y-frozen-style]").forEach((el) => {
285
- delete el.dataset.a11yFrozenStyle;
286
- });
287
- }
288
164
  function pauseAllVideos() {
289
165
  document.querySelectorAll("video").forEach((v) => {
290
166
  if (!v.paused) {
291
167
  v.dataset.a11yPaused = "true";
292
168
  v.pause();
293
- pausedVideos.push(v);
294
169
  }
295
170
  });
296
171
  }
@@ -317,7 +192,6 @@ function createAnimationStopModule() {
317
192
  }
318
193
  delete v.dataset.a11yPaused;
319
194
  });
320
- pausedVideos = [];
321
195
  }
322
196
  function freezeGif(img) {
323
197
  const src = (img.currentSrc || img.src || "").toLowerCase();
@@ -390,12 +264,11 @@ function createAnimationStopModule() {
390
264
  if (debounceTimer) clearTimeout(debounceTimer);
391
265
  debounceTimer = setTimeout(() => {
392
266
  if (!enabled) return;
393
- finishAllAnimations();
394
- forceHiddenElementsVisible();
267
+ cancelAllAnimations();
395
268
  pauseAllVideos();
396
269
  freezeAllGifs();
397
270
  pauseSVG();
398
- }, 100);
271
+ }, 150);
399
272
  }
400
273
  function setupObserver() {
401
274
  if (mutationObserver) return;
@@ -426,7 +299,7 @@ function createAnimationStopModule() {
426
299
  if (scanInterval) return;
427
300
  scanInterval = setInterval(() => {
428
301
  if (!enabled) return;
429
- finishAllAnimations();
302
+ cancelAllAnimations();
430
303
  }, 2e3);
431
304
  }
432
305
  function stopPeriodicScan() {
@@ -440,10 +313,8 @@ function createAnimationStopModule() {
440
313
  enabled = true;
441
314
  patchAnimate();
442
315
  injectStyles();
443
- finishAllAnimations();
316
+ cancelAllAnimations();
444
317
  patchGSAP();
445
- forceHiddenElementsVisible();
446
- freezeScrollAnimations();
447
318
  pauseAllVideos();
448
319
  interceptVideoPlay();
449
320
  freezeAllGifs();
@@ -451,26 +322,19 @@ function createAnimationStopModule() {
451
322
  stopMarquees();
452
323
  setupObserver();
453
324
  startPeriodicScan();
454
- const scheduleIdle = typeof requestIdleCallback === "function" ? (fn, timeout) => {
455
- const id = setTimeout(() => {
456
- }, timeout + 500);
457
- requestIdleCallback(() => {
458
- clearTimeout(id);
459
- if (enabled) fn();
460
- }, { timeout });
461
- return id;
462
- } : (fn, timeout) => setTimeout(() => {
325
+ const schedule = (fn, ms) => setTimeout(() => {
463
326
  if (enabled) fn();
464
- }, timeout);
465
- const rescan = () => {
466
- finishAllAnimations();
467
- forceHiddenElementsVisible();
468
- freezeScrollAnimations();
327
+ }, ms);
328
+ delayedTimers.push(schedule(() => {
329
+ cancelAllAnimations();
469
330
  pauseAllVideos();
470
331
  freezeAllGifs();
471
- };
472
- delayedTimers.push(scheduleIdle(rescan, 300));
473
- delayedTimers.push(scheduleIdle(rescan, 2e3));
332
+ }, 500));
333
+ delayedTimers.push(schedule(() => {
334
+ cancelAllAnimations();
335
+ pauseAllVideos();
336
+ freezeAllGifs();
337
+ }, 2e3));
474
338
  localStorage.setItem(STORAGE_KEY, "true");
475
339
  }
476
340
  function deactivate() {
@@ -480,9 +344,7 @@ function createAnimationStopModule() {
480
344
  stopPeriodicScan();
481
345
  teardownObserver();
482
346
  restoreAnimate();
483
- unfreezeScrollAnimations();
484
347
  restoreGSAP();
485
- restoreHiddenElements();
486
348
  startMarquees();
487
349
  resumeSVG();
488
350
  restoreAllGifs();
@@ -508,4 +370,4 @@ function createAnimationStopModule() {
508
370
  export {
509
371
  createAnimationStopModule as default
510
372
  };
511
- //# sourceMappingURL=animation-stop-BU0qc2Qp.js.map
373
+ //# sourceMappingURL=animation-stop-_C-YdTs3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animation-stop-_C-YdTs3.js","sources":["../src/features/animation-stop.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\n\n/**\n * Animation Stop – V6 (Framer-Safe Rewrite)\n *\n * Layer 1: CSS injection — kill all CSS animations/transitions + force\n * Framer/WAAPI-hidden elements visible via CSS !important (no\n * inline-style manipulation that frameworks fight against).\n * Layer 2: Web Animations API — cancel all running WAAPI animations\n * Layer 3: Element.prototype.animate() intercept — kill new animations\n * Layer 4: GSAP globalTimeline freeze\n * Layer 5: Media — pause videos, freeze GIFs, stop SVG SMIL, marquees\n *\n * + MutationObserver for dynamic content (debounced)\n * + Periodic re-scan (every 2s) for frameworks that create new animations\n *\n * V6 key change: NO inline-style manipulation for visibility forcing.\n * Framer (and similar React-based frameworks) continuously re-set inline\n * styles via WAAPI and React renders. Overwriting inline styles creates an\n * infinite fight (our override → Framer re-sets → MutationObserver fires →\n * we override again → flicker). Instead, V6 uses CSS `!important` rules\n * in the injected stylesheet, which beat inline styles without triggering\n * a mutation loop.\n */\nexport default function createAnimationStopModule(): FeatureModule {\n let enabled = false;\n const STYLE_ID = 'a11y-stop-animations';\n const STORAGE_KEY = 'accessify-animation-stop';\n\n // --- State ---\n let mutationObserver: MutationObserver | null = null;\n let scanInterval: ReturnType<typeof setInterval> | null = null;\n let originalVideoPlay: typeof HTMLVideoElement.prototype.play | null = null;\n let originalAnimate: typeof Element.prototype.animate | null = null;\n let gifOriginals = new Map<HTMLImageElement, string>();\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let delayedTimers: ReturnType<typeof setTimeout>[] = [];\n\n // =========================================================================\n // Helpers\n // =========================================================================\n\n const WIDGET_SEL = ':not(#accessify-root):not(#accessify-root *)';\n\n function isWidgetElement(el: Element | Node | null): boolean {\n try {\n let node: Node | null = el;\n while (node) {\n if ((node as Element).id === 'accessify-root') return true;\n const parent: Node | null =\n (node as Node).parentNode ||\n ((node as unknown as ShadowRoot).host as Node | null) ||\n null;\n if (parent === node) break;\n node = parent;\n }\n } catch { /* ignore */ }\n return false;\n }\n\n // =========================================================================\n // Layer 1: CSS Injection\n //\n // One big stylesheet handles EVERYTHING CSS can do:\n // a) Kill all transitions + CSS animations (0.001ms)\n // b) Force Framer-appear elements visible (opacity, transform)\n // c) Neutralize hover/focus transforms\n //\n // Because CSS !important beats inline styles, Framer can re-set its\n // inline opacity:0.001 as much as it wants — our rule wins. No JS\n // inline-style manipulation needed, no MutationObserver feedback loop.\n // =========================================================================\n\n function injectStyles() {\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = `\n /* --- Kill all CSS-driven animations & transitions --- */\n *${WIDGET_SEL},\n *${WIDGET_SEL}::before,\n *${WIDGET_SEL}::after {\n animation-duration: 0.001ms !important;\n animation-iteration-count: 1 !important;\n animation-delay: 0s !important;\n animation-fill-mode: forwards !important;\n transition-duration: 0.001ms !important;\n transition-delay: 0s !important;\n scroll-behavior: auto !important;\n animation-timeline: auto !important;\n scroll-timeline: none !important;\n }\n\n /* --- Force Framer appear-elements visible --- *\n * Framer sets inline opacity:0.001 + transform:translateY(...)\n * on elements waiting for scroll/appear animations. We override\n * with !important which beats inline styles. When animation-stop\n * is deactivated, removing this stylesheet instantly restores\n * Framer's inline values — no cleanup needed. */\n [data-framer-appear-id]${WIDGET_SEL} {\n opacity: 1 !important;\n transform: none !important;\n }\n\n /* --- Force any inline-hidden elements visible ---\n * Some animation frameworks (AOS, wow.js, Framer scroll) hide\n * elements via inline opacity:0 or visibility:hidden. */\n [style*=\"opacity: 0\"]${WIDGET_SEL},\n [style*=\"opacity:0\"]${WIDGET_SEL} {\n opacity: 1 !important;\n }\n [style*=\"visibility: hidden\"]${WIDGET_SEL},\n [style*=\"visibility:hidden\"]${WIDGET_SEL} {\n visibility: visible !important;\n }\n\n /* --- Neutralize interaction transforms --- */\n *${WIDGET_SEL}:hover,\n *${WIDGET_SEL}:focus,\n *${WIDGET_SEL}:focus-within,\n *${WIDGET_SEL}:focus-visible,\n *${WIDGET_SEL}:active {\n transform: none !important;\n scale: 1 !important;\n rotate: 0deg !important;\n translate: 0 0 !important;\n filter: none !important;\n -webkit-filter: none !important;\n }\n `;\n document.head.appendChild(style);\n }\n\n function removeStyles() {\n document.getElementById(STYLE_ID)?.remove();\n }\n\n // =========================================================================\n // Layer 2: Web Animations API — cancel all WAAPI animations\n //\n // We cancel() instead of finish() to avoid leaving elements stuck in\n // animation end-states (e.g. scale(1.05) from a hover animation).\n // The CSS !important rules in Layer 1 handle visibility.\n // =========================================================================\n\n function isWidgetAnimation(anim: Animation): boolean {\n try {\n const target = (anim.effect as any)?.target as Element | null;\n if (!target) return false;\n return isWidgetElement(target);\n } catch { /* ignore */ }\n return false;\n }\n\n function cancelAllAnimations() {\n try {\n const animations = document.getAnimations?.();\n if (!animations) return;\n for (const anim of animations) {\n if (isWidgetAnimation(anim)) continue;\n try { anim.cancel(); } catch { /* ignore */ }\n }\n } catch { /* getAnimations not supported */ }\n }\n\n // =========================================================================\n // Layer 3: Element.prototype.animate() intercept\n //\n // Framer Motion creates new WAAPI animations constantly (hover, appear,\n // scroll). We intercept at creation time and cancel immediately.\n // =========================================================================\n\n function patchAnimate() {\n if (originalAnimate) return;\n originalAnimate = Element.prototype.animate;\n\n Element.prototype.animate = function (\n this: Element,\n keyframes: Keyframe[] | PropertyIndexedKeyframes | null,\n options?: number | KeyframeAnimationOptions,\n ): Animation {\n if (!enabled) {\n return originalAnimate!.call(this, keyframes, options);\n }\n\n // Never interfere with the widget's own animations.\n if (isWidgetElement(this)) {\n return originalAnimate!.call(this, keyframes, options);\n }\n\n // Create the animation with instant duration, then cancel it.\n // We must call originalAnimate (not skip it) because some frameworks\n // store the returned Animation object and call methods on it.\n const opts: KeyframeAnimationOptions = typeof options === 'number'\n ? { duration: 0.001, fill: 'none' as FillMode }\n : {\n ...(options || {}),\n duration: 0.001,\n delay: 0,\n iterations: 1,\n fill: 'none' as FillMode,\n };\n delete (opts as any).iterationStart;\n delete (opts as any).endDelay;\n\n const anim = originalAnimate!.call(this, keyframes, opts);\n try { anim.cancel(); } catch { /* ignore */ }\n return anim;\n };\n }\n\n function restoreAnimate() {\n if (originalAnimate) {\n Element.prototype.animate = originalAnimate;\n originalAnimate = null;\n }\n }\n\n // =========================================================================\n // Layer 4: GSAP\n // =========================================================================\n\n function patchGSAP() {\n const g = (window as any).gsap;\n if (g?.globalTimeline) {\n try {\n g.globalTimeline.timeScale(0);\n (window as any)._a11yGsapPatched = true;\n } catch { /* ignore */ }\n }\n }\n\n function restoreGSAP() {\n const g = (window as any).gsap;\n if ((window as any)._a11yGsapPatched && g?.globalTimeline) {\n try {\n g.globalTimeline.timeScale(1);\n delete (window as any)._a11yGsapPatched;\n } catch { /* ignore */ }\n }\n }\n\n // =========================================================================\n // Layer 5: Media\n // =========================================================================\n\n function pauseAllVideos() {\n document.querySelectorAll<HTMLVideoElement>('video').forEach(v => {\n if (!v.paused) {\n v.dataset.a11yPaused = 'true';\n v.pause();\n }\n });\n }\n\n function interceptVideoPlay() {\n if (originalVideoPlay) return;\n originalVideoPlay = HTMLVideoElement.prototype.play;\n HTMLVideoElement.prototype.play = function () {\n if (enabled) return Promise.resolve();\n return originalVideoPlay!.call(this);\n };\n }\n\n function restoreVideoPlay() {\n if (originalVideoPlay) {\n HTMLVideoElement.prototype.play = originalVideoPlay;\n originalVideoPlay = null;\n }\n }\n\n function resumeAllVideos() {\n restoreVideoPlay();\n document.querySelectorAll<HTMLVideoElement>('video[data-a11y-paused]').forEach(v => {\n try { v.play(); } catch { /* gone */ }\n delete v.dataset.a11yPaused;\n });\n }\n\n function freezeGif(img: HTMLImageElement) {\n const src = (img.currentSrc || img.src || '').toLowerCase();\n if (!src.match(/\\.gif(\\?|$)/i)) return;\n if (gifOriginals.has(img)) return;\n\n const doFreeze = () => {\n try {\n const w = img.naturalWidth || img.width;\n const h = img.naturalHeight || img.height;\n if (w === 0 || h === 0) return;\n const c = document.createElement('canvas');\n c.width = w; c.height = h;\n const ctx = c.getContext('2d');\n if (!ctx) return;\n ctx.drawImage(img, 0, 0);\n gifOriginals.set(img, img.src);\n img.src = c.toDataURL('image/png');\n } catch { /* CORS */ }\n };\n\n if (img.complete && img.naturalWidth > 0) doFreeze();\n else img.addEventListener('load', doFreeze, { once: true });\n }\n\n function freezeAllGifs() {\n document.querySelectorAll<HTMLImageElement>('img').forEach(freezeGif);\n }\n\n function restoreAllGifs() {\n for (const [img, src] of gifOriginals) {\n try { img.src = src; } catch { /* gone */ }\n }\n gifOriginals.clear();\n }\n\n function pauseSVG() {\n document.querySelectorAll('svg').forEach(s => {\n try { (s as any).pauseAnimations?.(); } catch { /* ignore */ }\n });\n }\n\n function resumeSVG() {\n document.querySelectorAll('svg').forEach(s => {\n try { (s as any).unpauseAnimations?.(); } catch { /* ignore */ }\n });\n }\n\n function stopMarquees() {\n document.querySelectorAll('marquee').forEach(m => {\n try { (m as any).stop?.(); } catch { /* ignore */ }\n });\n }\n\n function startMarquees() {\n document.querySelectorAll('marquee').forEach(m => {\n try { (m as any).start?.(); } catch { /* ignore */ }\n });\n }\n\n // =========================================================================\n // MutationObserver — dynamic content (debounced)\n //\n // V6: Only cancels WAAPI animations and pauses new media. Does NOT\n // manipulate inline styles (that's handled purely by CSS !important).\n // =========================================================================\n\n function debouncedRescan() {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n if (!enabled) return;\n cancelAllAnimations();\n pauseAllVideos();\n freezeAllGifs();\n pauseSVG();\n }, 150);\n }\n\n function setupObserver() {\n if (mutationObserver) return;\n mutationObserver = new MutationObserver(mutations => {\n if (!enabled) return;\n let hasNewContent = false;\n for (const m of mutations) {\n if (m.addedNodes.length > 0) { hasNewContent = true; break; }\n }\n if (hasNewContent) {\n debouncedRescan();\n }\n });\n mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n\n function teardownObserver() {\n mutationObserver?.disconnect();\n mutationObserver = null;\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n }\n\n // =========================================================================\n // Periodic re-scan — cancel animations frameworks keep creating\n // =========================================================================\n\n function startPeriodicScan() {\n if (scanInterval) return;\n scanInterval = setInterval(() => {\n if (!enabled) return;\n cancelAllAnimations();\n }, 2000);\n }\n\n function stopPeriodicScan() {\n if (scanInterval) {\n clearInterval(scanInterval);\n scanInterval = null;\n }\n }\n\n // =========================================================================\n // Lifecycle\n // =========================================================================\n\n function activate() {\n if (enabled) return;\n enabled = true;\n\n // Layer 3 first: intercept new WAAPI animations before anything else\n patchAnimate();\n\n // Layer 1: CSS — handles transitions, animations, AND visibility forcing\n injectStyles();\n\n // Layer 2: Cancel all currently running WAAPI animations\n cancelAllAnimations();\n\n // Layer 4: GSAP\n patchGSAP();\n\n // Layer 5: Media\n pauseAllVideos();\n interceptVideoPlay();\n freezeAllGifs();\n pauseSVG();\n stopMarquees();\n\n // Observers\n setupObserver();\n startPeriodicScan();\n\n // Two delayed re-scans for late-loading frameworks (reduced from 6).\n const schedule = (fn: () => void, ms: number) =>\n setTimeout(() => { if (enabled) fn(); }, ms);\n\n delayedTimers.push(schedule(() => {\n cancelAllAnimations();\n pauseAllVideos();\n freezeAllGifs();\n }, 500));\n\n delayedTimers.push(schedule(() => {\n cancelAllAnimations();\n pauseAllVideos();\n freezeAllGifs();\n }, 2000));\n\n localStorage.setItem(STORAGE_KEY, 'true');\n }\n\n function deactivate() {\n enabled = false;\n\n // Clear all timers\n for (const t of delayedTimers) clearTimeout(t);\n delayedTimers = [];\n\n stopPeriodicScan();\n teardownObserver();\n restoreAnimate();\n restoreGSAP();\n startMarquees();\n resumeSVG();\n restoreAllGifs();\n resumeAllVideos();\n\n // Remove CSS — this instantly restores all visibility, transitions,\n // and animations because the !important rules are gone.\n removeStyles();\n\n localStorage.removeItem(STORAGE_KEY);\n }\n\n return {\n id: 'animation-stop',\n name: () => 'Stop Animations',\n description: 'Pause all animations, transitions, and auto-playing videos (WCAG 2.3.1)',\n icon: 'animation-stop',\n category: 'visual',\n activate,\n deactivate,\n getState: (): FeatureState => ({ id: 'animation-stop', enabled }),\n setState: (state: { enabled: boolean }) => {\n if (state.enabled) activate();\n else deactivate();\n },\n };\n}\n"],"names":[],"mappings":"AAwBA,SAAwB,4BAA2C;AACjE,MAAI,UAAU;AACd,QAAM,WAAW;AACjB,QAAM,cAAc;AAGpB,MAAI,mBAA4C;AAChD,MAAI,eAAsD;AAC1D,MAAI,oBAAmE;AACvE,MAAI,kBAA2D;AAC/D,MAAI,mCAAmB,IAAA;AACvB,MAAI,gBAAsD;AAC1D,MAAI,gBAAiD,CAAA;AAMrD,QAAM,aAAa;AAEnB,WAAS,gBAAgB,IAAoC;AAC3D,QAAI;AACF,UAAI,OAAoB;AACxB,aAAO,MAAM;AACX,YAAK,KAAiB,OAAO,iBAAkB,QAAO;AACtD,cAAM,SACH,KAAc,cACb,KAA+B,QACjC;AACF,YAAI,WAAW,KAAM;AACrB,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAAe;AACvB,WAAO;AAAA,EACT;AAeA,WAAS,eAAe;AACtB,QAAI,SAAS,eAAe,QAAQ,EAAG;AACvC,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,KAAK;AACX,UAAM,cAAc;AAAA;AAAA,SAEf,UAAU;AAAA,SACV,UAAU;AAAA,SACV,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAkBY,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAQZ,UAAU;AAAA,4BACX,UAAU;AAAA;AAAA;AAAA,qCAGD,UAAU;AAAA,oCACX,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,SAKrC,UAAU;AAAA,SACV,UAAU;AAAA,SACV,UAAU;AAAA,SACV,UAAU;AAAA,SACV,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASf,aAAS,KAAK,YAAY,KAAK;AAAA,EACjC;AAEA,WAAS,eAAe;AACtB,aAAS,eAAe,QAAQ,GAAG,OAAA;AAAA,EACrC;AAUA,WAAS,kBAAkB,MAA0B;AACnD,QAAI;AACF,YAAM,SAAU,KAAK,QAAgB;AACrC,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,gBAAgB,MAAM;AAAA,IAC/B,QAAQ;AAAA,IAAe;AACvB,WAAO;AAAA,EACT;AAEA,WAAS,sBAAsB;AAC7B,QAAI;AACF,YAAM,aAAa,SAAS,gBAAA;AAC5B,UAAI,CAAC,WAAY;AACjB,iBAAW,QAAQ,YAAY;AAC7B,YAAI,kBAAkB,IAAI,EAAG;AAC7B,YAAI;AAAE,eAAK,OAAA;AAAA,QAAU,QAAQ;AAAA,QAAe;AAAA,MAC9C;AAAA,IACF,QAAQ;AAAA,IAAoC;AAAA,EAC9C;AASA,WAAS,eAAe;AACtB,QAAI,gBAAiB;AACrB,sBAAkB,QAAQ,UAAU;AAEpC,YAAQ,UAAU,UAAU,SAE1B,WACA,SACW;AACX,UAAI,CAAC,SAAS;AACZ,eAAO,gBAAiB,KAAK,MAAM,WAAW,OAAO;AAAA,MACvD;AAGA,UAAI,gBAAgB,IAAI,GAAG;AACzB,eAAO,gBAAiB,KAAK,MAAM,WAAW,OAAO;AAAA,MACvD;AAKA,YAAM,OAAiC,OAAO,YAAY,WACtD,EAAE,UAAU,MAAO,MAAM,WACzB;AAAA,QACE,GAAI,WAAW,CAAA;AAAA,QACf,UAAU;AAAA,QACV,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,MAAM;AAAA,MAAA;AAEZ,aAAQ,KAAa;AACrB,aAAQ,KAAa;AAErB,YAAM,OAAO,gBAAiB,KAAK,MAAM,WAAW,IAAI;AACxD,UAAI;AAAE,aAAK,OAAA;AAAA,MAAU,QAAQ;AAAA,MAAe;AAC5C,aAAO;AAAA,IACT;AAAA,EACF;AAEA,WAAS,iBAAiB;AACxB,QAAI,iBAAiB;AACnB,cAAQ,UAAU,UAAU;AAC5B,wBAAkB;AAAA,IACpB;AAAA,EACF;AAMA,WAAS,YAAY;AACnB,UAAM,IAAK,OAAe;AAC1B,QAAI,GAAG,gBAAgB;AACrB,UAAI;AACF,UAAE,eAAe,UAAU,CAAC;AAC3B,eAAe,mBAAmB;AAAA,MACrC,QAAQ;AAAA,MAAe;AAAA,IACzB;AAAA,EACF;AAEA,WAAS,cAAc;AACrB,UAAM,IAAK,OAAe;AAC1B,QAAK,OAAe,oBAAoB,GAAG,gBAAgB;AACzD,UAAI;AACF,UAAE,eAAe,UAAU,CAAC;AAC5B,eAAQ,OAAe;AAAA,MACzB,QAAQ;AAAA,MAAe;AAAA,IACzB;AAAA,EACF;AAMA,WAAS,iBAAiB;AACxB,aAAS,iBAAmC,OAAO,EAAE,QAAQ,CAAA,MAAK;AAChE,UAAI,CAAC,EAAE,QAAQ;AACb,UAAE,QAAQ,aAAa;AACvB,UAAE,MAAA;AAAA,MACJ;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,qBAAqB;AAC5B,QAAI,kBAAmB;AACvB,wBAAoB,iBAAiB,UAAU;AAC/C,qBAAiB,UAAU,OAAO,WAAY;AAC5C,UAAI,QAAS,QAAO,QAAQ,QAAA;AAC5B,aAAO,kBAAmB,KAAK,IAAI;AAAA,IACrC;AAAA,EACF;AAEA,WAAS,mBAAmB;AAC1B,QAAI,mBAAmB;AACrB,uBAAiB,UAAU,OAAO;AAClC,0BAAoB;AAAA,IACtB;AAAA,EACF;AAEA,WAAS,kBAAkB;AACzB,qBAAA;AACA,aAAS,iBAAmC,yBAAyB,EAAE,QAAQ,CAAA,MAAK;AAClF,UAAI;AAAE,UAAE,KAAA;AAAA,MAAQ,QAAQ;AAAA,MAAa;AACrC,aAAO,EAAE,QAAQ;AAAA,IACnB,CAAC;AAAA,EACH;AAEA,WAAS,UAAU,KAAuB;AACxC,UAAM,OAAO,IAAI,cAAc,IAAI,OAAO,IAAI,YAAA;AAC9C,QAAI,CAAC,IAAI,MAAM,cAAc,EAAG;AAChC,QAAI,aAAa,IAAI,GAAG,EAAG;AAE3B,UAAM,WAAW,MAAM;AACrB,UAAI;AACF,cAAM,IAAI,IAAI,gBAAgB,IAAI;AAClC,cAAM,IAAI,IAAI,iBAAiB,IAAI;AACnC,YAAI,MAAM,KAAK,MAAM,EAAG;AACxB,cAAM,IAAI,SAAS,cAAc,QAAQ;AACzC,UAAE,QAAQ;AAAG,UAAE,SAAS;AACxB,cAAM,MAAM,EAAE,WAAW,IAAI;AAC7B,YAAI,CAAC,IAAK;AACV,YAAI,UAAU,KAAK,GAAG,CAAC;AACvB,qBAAa,IAAI,KAAK,IAAI,GAAG;AAC7B,YAAI,MAAM,EAAE,UAAU,WAAW;AAAA,MACnC,QAAQ;AAAA,MAAa;AAAA,IACvB;AAEA,QAAI,IAAI,YAAY,IAAI,eAAe,EAAG,UAAA;AAAA,aACjC,iBAAiB,QAAQ,UAAU,EAAE,MAAM,MAAM;AAAA,EAC5D;AAEA,WAAS,gBAAgB;AACvB,aAAS,iBAAmC,KAAK,EAAE,QAAQ,SAAS;AAAA,EACtE;AAEA,WAAS,iBAAiB;AACxB,eAAW,CAAC,KAAK,GAAG,KAAK,cAAc;AACrC,UAAI;AAAE,YAAI,MAAM;AAAA,MAAK,QAAQ;AAAA,MAAa;AAAA,IAC5C;AACA,iBAAa,MAAA;AAAA,EACf;AAEA,WAAS,WAAW;AAClB,aAAS,iBAAiB,KAAK,EAAE,QAAQ,CAAA,MAAK;AAC5C,UAAI;AAAG,UAAU,kBAAA;AAAA,MAAqB,QAAQ;AAAA,MAAe;AAAA,IAC/D,CAAC;AAAA,EACH;AAEA,WAAS,YAAY;AACnB,aAAS,iBAAiB,KAAK,EAAE,QAAQ,CAAA,MAAK;AAC5C,UAAI;AAAG,UAAU,oBAAA;AAAA,MAAuB,QAAQ;AAAA,MAAe;AAAA,IACjE,CAAC;AAAA,EACH;AAEA,WAAS,eAAe;AACtB,aAAS,iBAAiB,SAAS,EAAE,QAAQ,CAAA,MAAK;AAChD,UAAI;AAAG,UAAU,OAAA;AAAA,MAAU,QAAQ;AAAA,MAAe;AAAA,IACpD,CAAC;AAAA,EACH;AAEA,WAAS,gBAAgB;AACvB,aAAS,iBAAiB,SAAS,EAAE,QAAQ,CAAA,MAAK;AAChD,UAAI;AAAG,UAAU,QAAA;AAAA,MAAW,QAAQ;AAAA,MAAe;AAAA,IACrD,CAAC;AAAA,EACH;AASA,WAAS,kBAAkB;AACzB,QAAI,4BAA4B,aAAa;AAC7C,oBAAgB,WAAW,MAAM;AAC/B,UAAI,CAAC,QAAS;AACd,0BAAA;AACA,qBAAA;AACA,oBAAA;AACA,eAAA;AAAA,IACF,GAAG,GAAG;AAAA,EACR;AAEA,WAAS,gBAAgB;AACvB,QAAI,iBAAkB;AACtB,uBAAmB,IAAI,iBAAiB,CAAA,cAAa;AACnD,UAAI,CAAC,QAAS;AACd,UAAI,gBAAgB;AACpB,iBAAW,KAAK,WAAW;AACzB,YAAI,EAAE,WAAW,SAAS,GAAG;AAAE,0BAAgB;AAAM;AAAA,QAAO;AAAA,MAC9D;AACA,UAAI,eAAe;AACjB,wBAAA;AAAA,MACF;AAAA,IACF,CAAC;AACD,qBAAiB,QAAQ,SAAS,MAAM,EAAE,WAAW,MAAM,SAAS,MAAM;AAAA,EAC5E;AAEA,WAAS,mBAAmB;AAC1B,sBAAkB,WAAA;AAClB,uBAAmB;AACnB,QAAI,eAAe;AACjB,mBAAa,aAAa;AAC1B,sBAAgB;AAAA,IAClB;AAAA,EACF;AAMA,WAAS,oBAAoB;AAC3B,QAAI,aAAc;AAClB,mBAAe,YAAY,MAAM;AAC/B,UAAI,CAAC,QAAS;AACd,0BAAA;AAAA,IACF,GAAG,GAAI;AAAA,EACT;AAEA,WAAS,mBAAmB;AAC1B,QAAI,cAAc;AAChB,oBAAc,YAAY;AAC1B,qBAAe;AAAA,IACjB;AAAA,EACF;AAMA,WAAS,WAAW;AAClB,QAAI,QAAS;AACb,cAAU;AAGV,iBAAA;AAGA,iBAAA;AAGA,wBAAA;AAGA,cAAA;AAGA,mBAAA;AACA,uBAAA;AACA,kBAAA;AACA,aAAA;AACA,iBAAA;AAGA,kBAAA;AACA,sBAAA;AAGA,UAAM,WAAW,CAAC,IAAgB,OAChC,WAAW,MAAM;AAAE,UAAI,QAAS,IAAA;AAAA,IAAM,GAAG,EAAE;AAE7C,kBAAc,KAAK,SAAS,MAAM;AAChC,0BAAA;AACA,qBAAA;AACA,oBAAA;AAAA,IACF,GAAG,GAAG,CAAC;AAEP,kBAAc,KAAK,SAAS,MAAM;AAChC,0BAAA;AACA,qBAAA;AACA,oBAAA;AAAA,IACF,GAAG,GAAI,CAAC;AAER,iBAAa,QAAQ,aAAa,MAAM;AAAA,EAC1C;AAEA,WAAS,aAAa;AACpB,cAAU;AAGV,eAAW,KAAK,cAAe,cAAa,CAAC;AAC7C,oBAAgB,CAAA;AAEhB,qBAAA;AACA,qBAAA;AACA,mBAAA;AACA,gBAAA;AACA,kBAAA;AACA,cAAA;AACA,mBAAA;AACA,oBAAA;AAIA,iBAAA;AAEA,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,kBAAkB,QAAA;AAAA,IACvD,UAAU,CAAC,UAAgC;AACzC,UAAI,MAAM,QAAS,UAAA;AAAA,UACd,YAAA;AAAA,IACP;AAAA,EAAA;AAEJ;"}
@@ -6644,17 +6644,17 @@ function FeatureGrid($$anchor, $$props) {
6644
6644
  const FEATURE_LOADERS = {
6645
6645
  contrast: () => import("./contrast-CqsOs6Uo.js"),
6646
6646
  "text-size": () => import("./text-size-m_mHNPWo.js"),
6647
- "keyboard-nav": () => import("./keyboard-nav-fmtN9gtf.js"),
6647
+ "keyboard-nav": () => import("./keyboard-nav-Bykt8VwT.js"),
6648
6648
  "link-highlight": () => import("./link-highlight-D9gxFmiG.js"),
6649
6649
  "reading-guide": () => import("./reading-guide-C_jxzorm.js"),
6650
6650
  "reading-mask": () => import("./reading-mask-B_NxbhTN.js"),
6651
- "animation-stop": () => import("./animation-stop-BU0qc2Qp.js"),
6651
+ "animation-stop": () => import("./animation-stop-_C-YdTs3.js"),
6652
6652
  "hide-images": () => import("./hide-images-B_LeCBcd.js"),
6653
6653
  "big-cursor": () => import("./big-cursor-B2UKu9dQ.js"),
6654
- "page-structure": () => import("./page-structure-C6PEQh5j.js"),
6654
+ "page-structure": () => import("./page-structure-CuafhkRb.js"),
6655
6655
  tts: () => import("./tts-zrXtEd07.js"),
6656
- "text-simplify": () => import("./text-simplify-BIFpqadq.js"),
6657
- "alt-text": () => import("./alt-text-BzVlRLRb.js")
6656
+ "text-simplify": () => import("./text-simplify-H4aHr88a.js"),
6657
+ "alt-text": () => import("./alt-text-BDEUXN9O.js")
6658
6658
  };
6659
6659
  let contrastMode = /* @__PURE__ */ state(proxy(readStoredContrastMode()));
6660
6660
  let textSize = /* @__PURE__ */ state(proxy(readStoredTextSize()));
@@ -8325,7 +8325,7 @@ async function init(userConfig = {}) {
8325
8325
  }
8326
8326
  });
8327
8327
  document.body.appendChild(containerEl);
8328
- import("./alt-text-BzVlRLRb.js").then((m) => m.autoApplyCachedAltTexts(config)).catch(() => {
8328
+ import("./alt-text-BDEUXN9O.js").then((m) => m.autoApplyCachedAltTexts(config)).catch(() => {
8329
8329
  });
8330
8330
  config.onReady?.();
8331
8331
  }
@@ -8368,4 +8368,4 @@ export {
8368
8368
  init as i,
8369
8369
  t
8370
8370
  };
8371
- //# sourceMappingURL=index-CJw0r5jG.js.map
8371
+ //# sourceMappingURL=index-yV-nq3Z-.js.map