poltrgeist 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,622 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __spreadValues = (a, b) => {
11
+ for (var prop in b || (b = {}))
12
+ if (__hasOwnProp.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ if (__getOwnPropSymbols)
15
+ for (var prop of __getOwnPropSymbols(b)) {
16
+ if (__propIsEnum.call(b, prop))
17
+ __defNormalProp(a, prop, b[prop]);
18
+ }
19
+ return a;
20
+ };
21
+
22
+ // src/utils/random.ts
23
+ function roll(probability) {
24
+ return Math.random() < probability;
25
+ }
26
+ function pickRandom(arr) {
27
+ return arr[Math.floor(Math.random() * arr.length)];
28
+ }
29
+ function randomBetween(min, max) {
30
+ return Math.random() * (max - min) + min;
31
+ }
32
+ function randomIntBetween(min, max) {
33
+ return Math.floor(randomBetween(min, max + 1));
34
+ }
35
+
36
+ // src/utils/dom.ts
37
+ var STYLE_ID = "poltrgeist-styles";
38
+ function injectStyle(css) {
39
+ let tag = document.getElementById(STYLE_ID);
40
+ if (!tag) {
41
+ tag = document.createElement("style");
42
+ tag.id = STYLE_ID;
43
+ document.head.appendChild(tag);
44
+ }
45
+ const marker = `/* block-${Math.random().toString(36).slice(2)} */`;
46
+ tag.textContent += `
47
+ ${marker}
48
+ ${css}`;
49
+ return () => {
50
+ var _a;
51
+ if (!tag) return;
52
+ const text = (_a = tag.textContent) != null ? _a : "";
53
+ const idx = text.indexOf(marker);
54
+ if (idx === -1) return;
55
+ const end = text.indexOf("/* block-", idx + marker.length);
56
+ tag.textContent = text.slice(0, idx) + (end === -1 ? "" : text.slice(end));
57
+ };
58
+ }
59
+ function cloneAtPosition(el) {
60
+ const rect = el.getBoundingClientRect();
61
+ const clone = el.cloneNode(true);
62
+ clone.style.cssText = `
63
+ position: fixed;
64
+ top: ${rect.top}px;
65
+ left: ${rect.left}px;
66
+ width: ${rect.width}px;
67
+ height: ${rect.height}px;
68
+ margin: 0;
69
+ pointer-events: none;
70
+ z-index: 9999;
71
+ `;
72
+ document.body.appendChild(clone);
73
+ return clone;
74
+ }
75
+ function wrapChars(el) {
76
+ var _a;
77
+ const original = el.innerHTML;
78
+ const text = (_a = el.textContent) != null ? _a : "";
79
+ const spans = [];
80
+ el.innerHTML = "";
81
+ for (const ch of text) {
82
+ const span = document.createElement("span");
83
+ span.style.display = "inline-block";
84
+ span.textContent = ch === " " ? "\xA0" : ch;
85
+ spans.push(span);
86
+ el.appendChild(span);
87
+ }
88
+ return {
89
+ spans,
90
+ unwrap: () => {
91
+ el.innerHTML = original;
92
+ }
93
+ };
94
+ }
95
+ function prefersReducedMotion() {
96
+ var _a, _b;
97
+ return (_b = (_a = window.matchMedia) == null ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)").matches) != null ? _b : false;
98
+ }
99
+
100
+ // src/engine.ts
101
+ var DEFAULT_PROBABILITY = 0.15;
102
+ var DEFAULT_INTERVAL = { min: 8e3, max: 3e4 };
103
+ var Engine = class {
104
+ constructor() {
105
+ this.cleanups = [];
106
+ this.effects = /* @__PURE__ */ new Map();
107
+ }
108
+ register(effect) {
109
+ this.effects.set(effect.name, effect);
110
+ }
111
+ haunt(options = {}) {
112
+ var _a, _b;
113
+ if (options.respectReducedMotion !== false && prefersReducedMotion()) return;
114
+ const allowedEffects = options.effects ? options.effects.map((n) => this.effects.get(n)).filter(Boolean) : [...this.effects.values()];
115
+ const images = (_a = options.images) != null ? _a : [];
116
+ const interval = __spreadValues(__spreadValues({}, DEFAULT_INTERVAL), (_b = options.interval) != null ? _b : {});
117
+ const getProbability = (name) => {
118
+ var _a2, _b2;
119
+ if (typeof options.probability === "number") return options.probability;
120
+ return (_b2 = (_a2 = options.probability) == null ? void 0 : _a2[name]) != null ? _b2 : DEFAULT_PROBABILITY;
121
+ };
122
+ for (const effect of allowedEffects) {
123
+ if (effect.trigger === "timer" || effect.trigger === "dom-text") {
124
+ this.scheduleEffect(effect, interval, images);
125
+ continue;
126
+ }
127
+ for (const selector of effect.defaultTargets) {
128
+ document.querySelectorAll(selector).forEach((el) => {
129
+ if (!roll(getProbability(effect.name))) return;
130
+ const cleanup = effect.attach(el, { images });
131
+ this.cleanups.push(cleanup);
132
+ });
133
+ }
134
+ }
135
+ this.applyDataAttributes(options, images);
136
+ }
137
+ apply(target, effectName, opts = {}) {
138
+ const effect = this.effects.get(effectName);
139
+ if (!effect) throw new Error(`Unknown effect: "${effectName}"`);
140
+ const elements = typeof target === "string" ? [...document.querySelectorAll(target)] : target instanceof NodeList ? [...target] : [target];
141
+ for (const el of elements) {
142
+ const cleanup = effect.attach(el, opts);
143
+ this.cleanups.push(cleanup);
144
+ }
145
+ }
146
+ release() {
147
+ for (const fn of this.cleanups) fn();
148
+ this.cleanups = [];
149
+ }
150
+ applyDataAttributes(options, images) {
151
+ document.querySelectorAll("[data-poltrgeist]").forEach((el) => {
152
+ var _a;
153
+ const names = ((_a = el.dataset.poltrgeist) != null ? _a : "").split(/\s+/).filter(Boolean);
154
+ for (const name of names) {
155
+ const effect = this.effects.get(name);
156
+ if (!effect) continue;
157
+ if (options.effects && !options.effects.includes(name)) continue;
158
+ const cleanup = effect.attach(el, { images });
159
+ this.cleanups.push(cleanup);
160
+ }
161
+ });
162
+ }
163
+ scheduleEffect(effect, interval, images) {
164
+ let timer;
165
+ const run = () => {
166
+ const candidates = [];
167
+ for (const selector of effect.defaultTargets) {
168
+ document.querySelectorAll(selector).forEach((el) => candidates.push(el));
169
+ }
170
+ if (candidates.length > 0) {
171
+ const el = pickRandom(candidates);
172
+ const cleanup = effect.attach(el, { images });
173
+ this.cleanups.push(cleanup);
174
+ }
175
+ schedule();
176
+ };
177
+ const schedule = () => {
178
+ const delay = Math.random() * (interval.max - interval.min) + interval.min;
179
+ timer = setTimeout(run, delay);
180
+ };
181
+ schedule();
182
+ this.cleanups.push(() => clearTimeout(timer));
183
+ }
184
+ };
185
+
186
+ // src/effects/explode.ts
187
+ var CSS = `
188
+ @keyframes pg-particle {
189
+ 0% { transform: translate(0,0) scale(1); opacity: 1; }
190
+ 100% { transform: translate(var(--pg-dx), var(--pg-dy)) scale(0); opacity: 0; }
191
+ }
192
+ .pg-particle {
193
+ position: fixed;
194
+ border-radius: 50%;
195
+ pointer-events: none;
196
+ z-index: 9999;
197
+ animation: pg-particle 0.6s ease-out forwards;
198
+ }
199
+ `;
200
+ var COLORS = ["#ff6b6b", "#ffd93d", "#6bcb77", "#4d96ff", "#ff922b", "#da77f2"];
201
+ var explode = {
202
+ name: "explode",
203
+ defaultTargets: ["button", '[role="button"]', 'input[type="submit"]', 'input[type="button"]'],
204
+ trigger: "click",
205
+ attach(el) {
206
+ const removeStyle = injectStyle(CSS);
207
+ const handler = (e) => {
208
+ const count = randomIntBetween(8, 16);
209
+ const x = e.clientX;
210
+ const y = e.clientY;
211
+ for (let i = 0; i < count; i++) {
212
+ const particle = document.createElement("div");
213
+ particle.className = "pg-particle";
214
+ const size = randomBetween(4, 10);
215
+ const angle = Math.PI * 2 * i / count + randomBetween(-0.3, 0.3);
216
+ const dist = randomBetween(40, 100);
217
+ particle.style.cssText = `
218
+ left: ${x - size / 2}px;
219
+ top: ${y - size / 2}px;
220
+ width: ${size}px;
221
+ height: ${size}px;
222
+ background: ${COLORS[i % COLORS.length]};
223
+ --pg-dx: ${Math.cos(angle) * dist}px;
224
+ --pg-dy: ${Math.sin(angle) * dist}px;
225
+ `;
226
+ document.body.appendChild(particle);
227
+ particle.addEventListener("animationend", () => particle.remove(), { once: true });
228
+ }
229
+ };
230
+ el.addEventListener("click", handler);
231
+ return () => {
232
+ el.removeEventListener("click", handler);
233
+ removeStyle();
234
+ };
235
+ }
236
+ };
237
+
238
+ // src/effects/shy.ts
239
+ var CSS2 = `
240
+ .pg-shy {
241
+ transition: transform 0.15s ease-out !important;
242
+ }
243
+ `;
244
+ var MAX_DRIFT = 14;
245
+ var shy = {
246
+ name: "shy",
247
+ defaultTargets: ["button", '[role="button"]', "a"],
248
+ trigger: "hover",
249
+ attach(el) {
250
+ const removeStyle = injectStyle(CSS2);
251
+ el.classList.add("pg-shy");
252
+ const onMove = (e) => {
253
+ const rect = el.getBoundingClientRect();
254
+ const cx = rect.left + rect.width / 2;
255
+ const cy = rect.top + rect.height / 2;
256
+ const dx = e.clientX - cx;
257
+ const dy = e.clientY - cy;
258
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
259
+ const factor = Math.min(MAX_DRIFT / dist, 1) * MAX_DRIFT;
260
+ el.style.transform = `translate(${-dx / dist * factor}px, ${-dy / dist * factor}px)`;
261
+ };
262
+ const onLeave = () => {
263
+ el.style.transform = "";
264
+ };
265
+ el.addEventListener("mousemove", onMove);
266
+ el.addEventListener("mouseleave", onLeave);
267
+ return () => {
268
+ el.removeEventListener("mousemove", onMove);
269
+ el.removeEventListener("mouseleave", onLeave);
270
+ el.classList.remove("pg-shy");
271
+ el.style.transform = "";
272
+ removeStyle();
273
+ };
274
+ }
275
+ };
276
+
277
+ // src/effects/yeet.ts
278
+ var CSS3 = `
279
+ @keyframes pg-yeet {
280
+ 0% { transform: translate(0,0) rotate(0deg) scale(1); opacity: 1; }
281
+ 100% { transform: translate(var(--pg-ydx), var(--pg-ydy)) rotate(var(--pg-yrot)) scale(0.2); opacity: 0; }
282
+ }
283
+ .pg-yeet-clone {
284
+ animation: pg-yeet 0.7s cubic-bezier(0.2, 0, 0.8, 1) forwards;
285
+ }
286
+ `;
287
+ var yeet = {
288
+ name: "yeet",
289
+ defaultTargets: ["button", '[role="button"]'],
290
+ trigger: "click",
291
+ attach(el) {
292
+ const removeStyle = injectStyle(CSS3);
293
+ const handler = () => {
294
+ const clone = cloneAtPosition(el);
295
+ const angle = randomBetween(-Math.PI / 3, -Math.PI * 2 / 3);
296
+ const dist = randomBetween(400, 900);
297
+ clone.style.setProperty("--pg-ydx", `${Math.cos(angle) * dist}px`);
298
+ clone.style.setProperty("--pg-ydy", `${Math.sin(angle) * dist}px`);
299
+ clone.style.setProperty("--pg-yrot", `${randomBetween(-720, 720)}deg`);
300
+ clone.classList.add("pg-yeet-clone");
301
+ el.style.visibility = "hidden";
302
+ clone.addEventListener("animationend", () => {
303
+ clone.remove();
304
+ el.style.visibility = "";
305
+ }, { once: true });
306
+ };
307
+ el.addEventListener("click", handler);
308
+ return () => {
309
+ el.removeEventListener("click", handler);
310
+ el.style.visibility = "";
311
+ removeStyle();
312
+ };
313
+ }
314
+ };
315
+
316
+ // src/effects/wobble.ts
317
+ var CSS4 = `
318
+ @keyframes pg-wobble-char {
319
+ 0%, 100% { transform: translateY(0); }
320
+ 50% { transform: translateY(var(--pg-wobble-y)); }
321
+ }
322
+ .pg-wobble-char {
323
+ animation: pg-wobble-char 0.4s ease-in-out forwards;
324
+ animation-delay: var(--pg-wobble-delay);
325
+ }
326
+ `;
327
+ var wobble = {
328
+ name: "wobble",
329
+ defaultTargets: ["h1", "h2", "h3", "h4", "label", "p"],
330
+ trigger: "timer",
331
+ attach(el) {
332
+ const removeStyle = injectStyle(CSS4);
333
+ let cancelled = false;
334
+ if (el._pgWobbling) {
335
+ removeStyle();
336
+ return removeStyle;
337
+ }
338
+ el._pgWobbling = true;
339
+ const { spans, unwrap } = wrapChars(el);
340
+ spans.forEach((span, i) => {
341
+ span.style.setProperty("--pg-wobble-y", `${randomBetween(-5, 5)}px`);
342
+ span.style.setProperty("--pg-wobble-delay", `${i * 30}ms`);
343
+ span.classList.add("pg-wobble-char");
344
+ });
345
+ const duration = 400 + spans.length * 30 + 200;
346
+ let done = false;
347
+ const restore = () => {
348
+ if (done) return;
349
+ done = true;
350
+ unwrap();
351
+ el._pgWobbling = false;
352
+ };
353
+ const timer = setTimeout(() => {
354
+ if (!cancelled) restore();
355
+ }, duration);
356
+ return () => {
357
+ cancelled = true;
358
+ clearTimeout(timer);
359
+ restore();
360
+ removeStyle();
361
+ };
362
+ }
363
+ };
364
+
365
+ // src/effects/ghost-image.ts
366
+ var CSS5 = `
367
+ @keyframes pg-ghost-drift {
368
+ 0% { opacity: 0; }
369
+ 20% { opacity: var(--pg-ghost-opacity); }
370
+ 80% { opacity: var(--pg-ghost-opacity); }
371
+ 100% { opacity: 0; }
372
+ }
373
+ .pg-ghost-image {
374
+ position: fixed;
375
+ pointer-events: none;
376
+ z-index: 1;
377
+ animation: pg-ghost-drift var(--pg-ghost-dur) ease-in-out forwards;
378
+ filter: blur(1px) grayscale(0.4);
379
+ max-width: 220px;
380
+ max-height: 220px;
381
+ object-fit: contain;
382
+ }
383
+ `;
384
+ var EDGES = ["top", "bottom", "left", "right"];
385
+ var FALLBACK_IMAGES = [
386
+ 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 80"><path d="M32 0 C14 0 4 14 4 32 L4 56 L12 48 L20 56 L28 48 L36 56 L44 48 L52 56 L60 48 L60 32 C60 14 50 0 32 0Z" fill="%23888"/><circle cx="22" cy="28" r="5" fill="%23fff"/><circle cx="42" cy="28" r="5" fill="%23fff"/></svg>'
387
+ ];
388
+ var ghostImage = {
389
+ name: "ghost-image",
390
+ defaultTargets: ["body"],
391
+ trigger: "timer",
392
+ attach(_el, opts) {
393
+ var _a;
394
+ const removeStyle = injectStyle(CSS5);
395
+ const images = ((_a = opts == null ? void 0 : opts.images) == null ? void 0 : _a.length) ? opts.images : FALLBACK_IMAGES;
396
+ const src = pickRandom(images);
397
+ const edge = pickRandom(EDGES);
398
+ const img = document.createElement("img");
399
+ img.className = "pg-ghost-image";
400
+ img.src = src;
401
+ img.alt = "";
402
+ img.setAttribute("aria-hidden", "true");
403
+ const vw = window.innerWidth;
404
+ const vh = window.innerHeight;
405
+ const dur = randomBetween(4, 8);
406
+ const opacity = randomBetween(0.06, 0.14);
407
+ const size = randomBetween(80, 200);
408
+ img.style.setProperty("--pg-ghost-opacity", String(opacity));
409
+ img.style.setProperty("--pg-ghost-dur", `${dur}s`);
410
+ img.style.width = `${size}px`;
411
+ const pos = randomBetween(0.1, 0.9);
412
+ const drift = randomBetween(30, 70);
413
+ if (edge === "top") {
414
+ img.style.left = `${vw * pos}px`;
415
+ img.style.top = `-${size}px`;
416
+ img.style.transform = `translateY(${drift}px)`;
417
+ } else if (edge === "bottom") {
418
+ img.style.left = `${vw * pos}px`;
419
+ img.style.bottom = `-${size}px`;
420
+ img.style.transform = `translateY(-${drift}px)`;
421
+ } else if (edge === "left") {
422
+ img.style.top = `${vh * pos}px`;
423
+ img.style.left = `-${size}px`;
424
+ img.style.transform = `translateX(${drift}px)`;
425
+ } else {
426
+ img.style.top = `${vh * pos}px`;
427
+ img.style.right = `-${size}px`;
428
+ img.style.transform = `translateX(-${drift}px)`;
429
+ }
430
+ document.body.appendChild(img);
431
+ img.addEventListener("animationend", () => img.remove(), { once: true });
432
+ return () => {
433
+ img.remove();
434
+ removeStyle();
435
+ };
436
+ }
437
+ };
438
+
439
+ // src/effects/heartbeat.ts
440
+ var CSS6 = `
441
+ @keyframes pg-heartbeat {
442
+ 0% { box-shadow: inset 0 0 0px 0px rgba(180,0,0,0); }
443
+ 15% { box-shadow: inset 0 0 60px 20px rgba(180,0,0,0.18); }
444
+ 30% { box-shadow: inset 0 0 20px 5px rgba(180,0,0,0.08); }
445
+ 50% { box-shadow: inset 0 0 80px 30px rgba(180,0,0,0.22); }
446
+ 100% { box-shadow: inset 0 0 0px 0px rgba(180,0,0,0); }
447
+ }
448
+ .pg-heartbeat-overlay {
449
+ position: fixed;
450
+ inset: 0;
451
+ pointer-events: none;
452
+ z-index: 9998;
453
+ animation: pg-heartbeat var(--pg-hb-dur) ease-in-out forwards;
454
+ }
455
+ `;
456
+ var heartbeat = {
457
+ name: "heartbeat",
458
+ defaultTargets: ["body"],
459
+ trigger: "timer",
460
+ attach() {
461
+ const removeStyle = injectStyle(CSS6);
462
+ const overlay = document.createElement("div");
463
+ overlay.className = "pg-heartbeat-overlay";
464
+ const dur = randomBetween(1.5, 3);
465
+ overlay.style.setProperty("--pg-hb-dur", `${dur}s`);
466
+ document.body.appendChild(overlay);
467
+ overlay.addEventListener("animationend", () => overlay.remove(), { once: true });
468
+ return () => {
469
+ overlay.remove();
470
+ removeStyle();
471
+ };
472
+ }
473
+ };
474
+
475
+ // src/effects/cthulhu.ts
476
+ var WORDS = [
477
+ "Ph'nglui",
478
+ "mglw'nafh",
479
+ "Cthulhu",
480
+ "R'lyeh",
481
+ "wgah'nagl",
482
+ "fhtagn",
483
+ "Azathoth",
484
+ "Nyarlathotep",
485
+ "Shoggoth",
486
+ "Yogg-Sothoth",
487
+ "Dagon",
488
+ "Ia",
489
+ "I\xE4",
490
+ "Nug",
491
+ "Yeb",
492
+ "Tsathoggua",
493
+ "Yog-Sothoth",
494
+ "Hastur",
495
+ "Shub-Niggurath",
496
+ "cyclopean",
497
+ "eldritch",
498
+ "gibbous",
499
+ "ululating",
500
+ "nameless",
501
+ "nigh"
502
+ ];
503
+ function getTextNodes(el) {
504
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, {
505
+ acceptNode: (node) => {
506
+ var _a, _b;
507
+ return ((_b = (_a = node.textContent) == null ? void 0 : _a.trim().length) != null ? _b : 0) > 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
508
+ }
509
+ });
510
+ const nodes = [];
511
+ let n;
512
+ while (n = walker.nextNode()) nodes.push(n);
513
+ return nodes;
514
+ }
515
+ var cthulhu = {
516
+ name: "cthulhu",
517
+ defaultTargets: ["p", "li", "h1", "h2", "h3", "span", "label"],
518
+ trigger: "dom-text",
519
+ attach(el) {
520
+ var _a;
521
+ const nodes = getTextNodes(el);
522
+ if (!nodes.length) return () => {
523
+ };
524
+ const node = pickRandom(nodes);
525
+ const original = (_a = node.textContent) != null ? _a : "";
526
+ const words = original.split(/(\s+)/);
527
+ const realWords = words.map((w, i) => ({ i, isWord: /\S/.test(w) })).filter((x) => x.isWord);
528
+ if (!realWords.length) return () => {
529
+ };
530
+ const target = pickRandom(realWords);
531
+ const replacement = pickRandom(WORDS);
532
+ words[target.i] = replacement;
533
+ node.textContent = words.join("");
534
+ const dur = randomBetween(4e3, 8e3);
535
+ const timer = setTimeout(() => {
536
+ node.textContent = original;
537
+ }, dur);
538
+ return () => {
539
+ clearTimeout(timer);
540
+ node.textContent = original;
541
+ };
542
+ }
543
+ };
544
+
545
+ // src/effects/mirror.ts
546
+ function getTextNodes2(el) {
547
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, {
548
+ acceptNode: (node) => {
549
+ var _a, _b;
550
+ return ((_b = (_a = node.textContent) == null ? void 0 : _a.trim().length) != null ? _b : 0) > 3 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
551
+ }
552
+ });
553
+ const nodes = [];
554
+ let n;
555
+ while (n = walker.nextNode()) nodes.push(n);
556
+ return nodes;
557
+ }
558
+ var mirror = {
559
+ name: "mirror",
560
+ defaultTargets: ["p", "li", "h1", "h2", "h3", "span", "label"],
561
+ trigger: "dom-text",
562
+ attach(el) {
563
+ var _a;
564
+ const nodes = getTextNodes2(el);
565
+ if (!nodes.length) return () => {
566
+ };
567
+ const node = pickRandom(nodes);
568
+ const original = (_a = node.textContent) != null ? _a : "";
569
+ const words = original.split(/(\s+)/);
570
+ const realWords = words.map((w, i) => ({ i, isWord: /\S{4,}/.test(w) })).filter((x) => x.isWord);
571
+ if (!realWords.length) return () => {
572
+ };
573
+ const target = pickRandom(realWords);
574
+ const word = words[target.i];
575
+ const span = document.createElement("span");
576
+ span.style.cssText = "display:inline-block; transform:scaleX(-1);";
577
+ span.textContent = word;
578
+ const parts = original.split(word);
579
+ const before = document.createTextNode(parts[0]);
580
+ const after = document.createTextNode(parts.slice(1).join(word));
581
+ node.replaceWith(before, span, after);
582
+ const dur = randomBetween(3e3, 6e3);
583
+ const timer = setTimeout(() => {
584
+ const restored = document.createTextNode(original);
585
+ before.replaceWith(restored);
586
+ span.remove();
587
+ after.remove();
588
+ }, dur);
589
+ return () => {
590
+ clearTimeout(timer);
591
+ try {
592
+ const restored = document.createTextNode(original);
593
+ before.replaceWith(restored);
594
+ span.remove();
595
+ after.remove();
596
+ } catch (e) {
597
+ }
598
+ };
599
+ }
600
+ };
601
+
602
+ // src/index.ts
603
+ var engine = new Engine();
604
+ engine.register(explode);
605
+ engine.register(shy);
606
+ engine.register(yeet);
607
+ engine.register(wobble);
608
+ engine.register(ghostImage);
609
+ engine.register(heartbeat);
610
+ engine.register(cthulhu);
611
+ engine.register(mirror);
612
+ var poltrgeist = {
613
+ haunt: (options) => engine.haunt(options),
614
+ apply: (target, effect, opts) => engine.apply(target, effect, opts),
615
+ release: () => engine.release()
616
+ };
617
+ var index_default = poltrgeist;
618
+
619
+ exports.default = index_default;
620
+ exports.poltrgeist = poltrgeist;
621
+ //# sourceMappingURL=index.cjs.map
622
+ //# sourceMappingURL=index.cjs.map