agex 0.2.6 → 0.2.7

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.
Files changed (48) hide show
  1. package/dist/cli.js +588 -19
  2. package/dist/index.js +16 -6
  3. package/package.json +10 -5
  4. package/assets/assets/assets/cursor.js +0 -192
  5. package/assets/assets/assets/effects.js +0 -1586
  6. package/assets/scripts/scripts/ab-close-disabled.sh +0 -5
  7. package/assets/scripts/scripts/ab-close.sh +0 -130
  8. package/assets/scripts/scripts/ab-fx.sh +0 -3
  9. package/assets/scripts/scripts/ab-move.sh +0 -19
  10. package/assets/scripts/scripts/ab-not-found.sh +0 -33
  11. package/assets/scripts/scripts/ab-open.sh +0 -17
  12. package/assets/scripts/scripts/ab-proof.sh +0 -126
  13. package/assets/scripts/scripts/ab-record-disabled.sh +0 -3
  14. package/assets/scripts/scripts/ab-record-start-disabled.sh +0 -2
  15. package/assets/scripts/scripts/ab-record-start.sh +0 -18
  16. package/assets/scripts/scripts/ab-record-stop-disabled.sh +0 -2
  17. package/assets/scripts/scripts/ab-record-stop.sh +0 -5
  18. package/assets/scripts/scripts/ab-record.sh +0 -43
  19. package/assets/scripts/scripts/ab-screenshot.sh +0 -9
  20. package/assets/scripts/scripts/agent-browser-wrapper.sh +0 -6
  21. package/assets/scripts/scripts/fx-annotation.sh +0 -13
  22. package/assets/scripts/scripts/fx-arrow.sh +0 -12
  23. package/assets/scripts/scripts/fx-circle.sh +0 -13
  24. package/assets/scripts/scripts/fx-clear.sh +0 -3
  25. package/assets/scripts/scripts/fx-highlight.sh +0 -32
  26. package/assets/scripts/scripts/fx-spotlight.sh +0 -13
  27. package/assets/scripts/scripts/fx-title.sh +0 -11
  28. package/assets/scripts/scripts/fx-wait.sh +0 -3
  29. package/dist/cli.d.ts +0 -6
  30. package/dist/cli.d.ts.map +0 -1
  31. package/dist/cli.js.map +0 -1
  32. package/dist/commands/browse.d.ts.map +0 -1
  33. package/dist/commands/browse.js +0 -112
  34. package/dist/commands/browse.js.map +0 -1
  35. package/dist/commands/prove-pr.d.ts.map +0 -1
  36. package/dist/commands/prove-pr.js +0 -120
  37. package/dist/commands/prove-pr.js.map +0 -1
  38. package/dist/commands/prove.d.ts.map +0 -1
  39. package/dist/commands/prove.js +0 -138
  40. package/dist/commands/prove.js.map +0 -1
  41. package/dist/commands/review.d.ts.map +0 -1
  42. package/dist/commands/review.js +0 -82
  43. package/dist/commands/review.js.map +0 -1
  44. package/dist/commands/run.d.ts.map +0 -1
  45. package/dist/commands/run.js +0 -132
  46. package/dist/commands/run.js.map +0 -1
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/index.js.map +0 -1
@@ -1,1586 +0,0 @@
1
- (() => {
2
- if (window.__agexEffects) return;
3
-
4
- const EFFECTS_ID = 'agex-effects';
5
-
6
- // Utility functions
7
- const utils = {
8
- getElement(selector) {
9
- return document.querySelector(selector);
10
- },
11
-
12
- getElements(selector) {
13
- return Array.from(document.querySelectorAll(selector));
14
- },
15
-
16
- getRect(selector) {
17
- const el = this.getElement(selector);
18
- return el ? el.getBoundingClientRect() : null;
19
- },
20
-
21
- createElement(tag, styles, id) {
22
- const el = document.createElement(tag);
23
- if (id) el.id = id;
24
- if (styles) Object.assign(el.style, styles);
25
- el.classList.add(EFFECTS_ID);
26
- return el;
27
- },
28
-
29
- removeElements(selector) {
30
- document.querySelectorAll(selector).forEach(el => el.remove());
31
- },
32
-
33
- easing: {
34
- linear: t => t,
35
- easeIn: t => t * t,
36
- easeOut: t => t * (2 - t),
37
- easeInOut: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
38
- bounce: t => {
39
- if (t < 1/2.75) return 7.5625 * t * t;
40
- if (t < 2/2.75) return 7.5625 * (t -= 1.5/2.75) * t + 0.75;
41
- if (t < 2.5/2.75) return 7.5625 * (t -= 2.25/2.75) * t + 0.9375;
42
- return 7.5625 * (t -= 2.625/2.75) * t + 0.984375;
43
- }
44
- },
45
-
46
- animate(duration, callback, easingName = 'easeInOut') {
47
- return new Promise(resolve => {
48
- const start = performance.now();
49
- const easingFn = this.easing[easingName] || this.easing.easeInOut;
50
-
51
- const step = (now) => {
52
- const elapsed = now - start;
53
- const progress = Math.min(elapsed / duration, 1);
54
- callback(easingFn(progress));
55
- if (progress < 1) {
56
- requestAnimationFrame(step);
57
- } else {
58
- resolve();
59
- }
60
- };
61
- requestAnimationFrame(step);
62
- });
63
- },
64
-
65
- wait(ms) {
66
- return new Promise(resolve => setTimeout(resolve, ms));
67
- }
68
- };
69
-
70
- // Inject base styles
71
- const injectStyles = () => {
72
- if (document.getElementById(`${EFFECTS_ID}-styles`)) return;
73
-
74
- const style = document.createElement('style');
75
- style.id = `${EFFECTS_ID}-styles`;
76
- style.textContent = `
77
- .${EFFECTS_ID} {
78
- pointer-events: none;
79
- z-index: 2147483640;
80
- }
81
- .${EFFECTS_ID}-highlight {
82
- position: absolute;
83
- box-sizing: border-box;
84
- transition: all 0.3s ease;
85
- }
86
- .${EFFECTS_ID}-highlight.border {
87
- border: 4px solid var(--color, #ff4444);
88
- box-shadow: 0 0 0 2px rgba(0,0,0,0.8), 0 0 12px var(--color, #ff4444);
89
- }
90
- .${EFFECTS_ID}-highlight.glow {
91
- box-shadow: 0 0 20px var(--color, #ff4444), inset 0 0 20px var(--color, #ff4444);
92
- animation: ${EFFECTS_ID}-glow 1s ease-in-out 3;
93
- }
94
- @keyframes ${EFFECTS_ID}-glow {
95
- 0%, 100% { box-shadow: 0 0 20px var(--color, #ff4444), inset 0 0 20px var(--color, #ff4444); }
96
- 50% { box-shadow: 0 0 35px var(--color, #ff4444), inset 0 0 35px var(--color, #ff4444); }
97
- }
98
- .${EFFECTS_ID}-highlight.pulse {
99
- animation: ${EFFECTS_ID}-pulse 1s ease-in-out infinite;
100
- border: 3px solid var(--color, #ff4444);
101
- }
102
- @keyframes ${EFFECTS_ID}-pulse {
103
- 0%, 100% { opacity: 1; transform: scale(1); }
104
- 50% { opacity: 0.7; transform: scale(1.02); }
105
- }
106
- .${EFFECTS_ID}-annotation {
107
- position: absolute;
108
- background: rgba(0, 0, 0, 0.9);
109
- color: white;
110
- padding: 8px 14px;
111
- border-radius: 4px;
112
- border: 3px solid var(--color, #ff4444);
113
- font-family: system-ui, -apple-system, sans-serif;
114
- font-size: 14px;
115
- font-weight: 600;
116
- letter-spacing: 0.5px;
117
- white-space: nowrap;
118
- box-shadow: 0 0 0 2px rgba(0,0,0,0.5), 0 4px 12px rgba(0,0,0,0.4);
119
- text-transform: uppercase;
120
- }
121
- .${EFFECTS_ID}-annotation::after {
122
- content: '';
123
- position: absolute;
124
- border: 6px solid transparent;
125
- }
126
- .${EFFECTS_ID}-annotation.arrow-bottom::after {
127
- bottom: -12px; left: 50%; transform: translateX(-50%);
128
- border-top-color: var(--color, #ff4444);
129
- }
130
- .${EFFECTS_ID}-annotation.arrow-top::after {
131
- top: -12px; left: 50%; transform: translateX(-50%);
132
- border-bottom-color: var(--color, #ff4444);
133
- }
134
- .${EFFECTS_ID}-annotation.arrow-left::after {
135
- left: -12px; top: 50%; transform: translateY(-50%);
136
- border-right-color: var(--color, #ff4444);
137
- }
138
- .${EFFECTS_ID}-annotation.arrow-right::after {
139
- right: -12px; top: 50%; transform: translateY(-50%);
140
- border-left-color: var(--color, #ff4444);
141
- }
142
- .${EFFECTS_ID}-spotlight-overlay {
143
- position: fixed;
144
- top: 0; left: 0; right: 0; bottom: 0;
145
- pointer-events: none;
146
- transition: opacity 0.3s ease;
147
- }
148
- .${EFFECTS_ID}-overlay {
149
- position: fixed;
150
- font-family: system-ui, sans-serif;
151
- transition: all 0.3s ease;
152
- }
153
- .${EFFECTS_ID}-subtitle {
154
- position: fixed;
155
- left: 50%;
156
- transform: translateX(-50%);
157
- background: rgba(0, 0, 0, 0.8);
158
- color: white;
159
- padding: 12px 24px;
160
- border-radius: 8px;
161
- font-size: 18px;
162
- max-width: 80%;
163
- text-align: center;
164
- }
165
- .${EFFECTS_ID}-drawing-canvas {
166
- position: fixed;
167
- top: 0; left: 0;
168
- width: 100%; height: 100%;
169
- pointer-events: none;
170
- }
171
- .${EFFECTS_ID}-transition {
172
- position: fixed;
173
- top: 0; left: 0; right: 0; bottom: 0;
174
- z-index: 2147483645;
175
- }
176
- `;
177
- document.head.appendChild(style);
178
- };
179
-
180
- // Effect handlers
181
- const effects = {
182
- // HIGHLIGHT
183
- highlight: {
184
- add(selector, { color = '#ff4444', style = 'border', duration } = {}) {
185
- const el = utils.getElement(selector);
186
- if (!el) return false;
187
-
188
- const rect = el.getBoundingClientRect();
189
- const highlight = utils.createElement('div', {
190
- position: 'absolute',
191
- left: `${rect.left + window.scrollX - 4}px`,
192
- top: `${rect.top + window.scrollY - 4}px`,
193
- width: `${rect.width + 8}px`,
194
- height: `${rect.height + 8}px`,
195
- borderRadius: '4px',
196
- });
197
- highlight.classList.add(`${EFFECTS_ID}-highlight`, style);
198
- highlight.style.setProperty('--color', color);
199
- highlight.dataset.selector = selector;
200
- document.body.appendChild(highlight);
201
-
202
- if (duration) {
203
- setTimeout(() => highlight.remove(), duration);
204
- }
205
- return true;
206
- },
207
-
208
- clear() {
209
- utils.removeElements(`.${EFFECTS_ID}-highlight`);
210
- }
211
- },
212
-
213
- // ANNOTATION
214
- annotation: {
215
- add(selector, { text, position = 'top', color = '#333', arrow = true } = {}) {
216
- const el = utils.getElement(selector);
217
- if (!el) return false;
218
-
219
- const rect = el.getBoundingClientRect();
220
- const annotation = utils.createElement('div');
221
- annotation.classList.add(`${EFFECTS_ID}-annotation`);
222
- if (arrow) annotation.classList.add(`arrow-${position === 'top' ? 'bottom' : position === 'bottom' ? 'top' : position === 'left' ? 'right' : 'left'}`);
223
- annotation.style.setProperty('--color', color);
224
- annotation.textContent = text;
225
- document.body.appendChild(annotation);
226
-
227
- const aRect = annotation.getBoundingClientRect();
228
- const positions = {
229
- top: { left: rect.left + rect.width/2 - aRect.width/2, top: rect.top - aRect.height - 16 },
230
- bottom: { left: rect.left + rect.width/2 - aRect.width/2, top: rect.bottom + 16 },
231
- left: { left: rect.left - aRect.width - 16, top: rect.top + rect.height/2 - aRect.height/2 },
232
- right: { left: rect.right + 16, top: rect.top + rect.height/2 - aRect.height/2 }
233
- };
234
- const pos = positions[position];
235
- annotation.style.left = `${pos.left + window.scrollX}px`;
236
- annotation.style.top = `${pos.top + window.scrollY}px`;
237
- annotation.style.position = 'absolute';
238
-
239
- return true;
240
- },
241
-
242
- clear() {
243
- utils.removeElements(`.${EFFECTS_ID}-annotation`);
244
- }
245
- },
246
-
247
- // SPOTLIGHT
248
- spotlight: {
249
- show(selector, { opacity = 0.7, label = '' } = {}) {
250
- const el = utils.getElement(selector);
251
- if (!el) return false;
252
-
253
- this.clear();
254
- const rect = el.getBoundingClientRect();
255
-
256
- const overlay = utils.createElement('div', {}, `${EFFECTS_ID}-spotlight`);
257
- overlay.classList.add(`${EFFECTS_ID}-spotlight-overlay`);
258
- overlay.style.background = `radial-gradient(ellipse at ${rect.left + rect.width/2}px ${rect.top + rect.height/2}px, transparent ${Math.max(rect.width, rect.height)/2 + 20}px, rgba(0,0,0,${opacity}) ${Math.max(rect.width, rect.height)/2 + 80}px)`;
259
- document.body.appendChild(overlay);
260
-
261
- // Add label if provided
262
- if (label) {
263
- const labelEl = utils.createElement('div', {
264
- position: 'absolute',
265
- left: `${rect.left + window.scrollX + rect.width/2}px`,
266
- top: `${rect.bottom + window.scrollY + 16}px`,
267
- transform: 'translateX(-50%)',
268
- background: 'rgba(255,255,255,0.95)',
269
- color: '#333',
270
- padding: '8px 16px',
271
- borderRadius: '6px',
272
- fontFamily: 'system-ui, sans-serif',
273
- fontSize: '14px',
274
- fontWeight: '600',
275
- boxShadow: '0 2px 12px rgba(0,0,0,0.3)',
276
- whiteSpace: 'nowrap',
277
- zIndex: '2147483641'
278
- }, `${EFFECTS_ID}-spotlight-label`);
279
- labelEl.textContent = label;
280
- document.body.appendChild(labelEl);
281
- }
282
-
283
- return true;
284
- },
285
-
286
- blur(amount = 8, excludeSelector) {
287
- const exclude = excludeSelector ? utils.getElement(excludeSelector) : null;
288
- document.body.style.filter = `blur(${amount}px)`;
289
- if (exclude) {
290
- exclude.style.filter = 'none';
291
- exclude.style.position = 'relative';
292
- exclude.style.zIndex = '2147483646';
293
- }
294
- },
295
-
296
- clearBlur() {
297
- document.body.style.filter = '';
298
- },
299
-
300
- clear() {
301
- utils.removeElements(`#${EFFECTS_ID}-spotlight`);
302
- utils.removeElements(`#${EFFECTS_ID}-spotlight-label`);
303
- }
304
- },
305
-
306
- // ZOOM
307
- zoom: {
308
- currentScale: 1,
309
-
310
- async toElement(selector, scale = 1.5, duration = 500) {
311
- const el = utils.getElement(selector);
312
- if (!el) return false;
313
-
314
- const rect = el.getBoundingClientRect();
315
- const centerX = rect.left + rect.width/2;
316
- const centerY = rect.top + rect.height/2;
317
-
318
- await utils.animate(duration, (p) => {
319
- const currentScale = 1 + (scale - 1) * p;
320
- document.body.style.transform = `scale(${currentScale})`;
321
- document.body.style.transformOrigin = `${centerX}px ${centerY}px`;
322
- });
323
-
324
- this.currentScale = scale;
325
- return true;
326
- },
327
-
328
- async toArea(x, y, scale = 1.5, duration = 500) {
329
- const centerX = (x / 100) * window.innerWidth;
330
- const centerY = (y / 100) * window.innerHeight;
331
-
332
- await utils.animate(duration, (p) => {
333
- const currentScale = 1 + (scale - 1) * p;
334
- document.body.style.transform = `scale(${currentScale})`;
335
- document.body.style.transformOrigin = `${centerX}px ${centerY}px`;
336
- });
337
-
338
- this.currentScale = scale;
339
- },
340
-
341
- async reset(duration = 500) {
342
- const startScale = this.currentScale;
343
- await utils.animate(duration, (p) => {
344
- const scale = startScale - (startScale - 1) * p;
345
- document.body.style.transform = `scale(${scale})`;
346
- });
347
- document.body.style.transform = '';
348
- this.currentScale = 1;
349
- }
350
- },
351
-
352
- // CURSOR
353
- cursor: {
354
- configure({ size = 'large', clickRipple = true, trail = false } = {}) {
355
- const cursor = document.getElementById('agex-cursor');
356
- if (!cursor) return;
357
-
358
- const sizes = { normal: 16, large: 24, 'extra-large': 32 };
359
- const s = sizes[size] || 24;
360
- cursor.style.width = `${s}px`;
361
- cursor.style.height = `${s}px`;
362
-
363
- cursor.dataset.clickRipple = clickRipple;
364
- cursor.dataset.trail = trail;
365
- },
366
-
367
- async moveTo(x, y, duration = 500, easing = 'easeInOut') {
368
- const cursor = document.getElementById('agex-cursor');
369
- if (!cursor) return;
370
-
371
- const startX = parseFloat(cursor.style.left) || 0;
372
- const startY = parseFloat(cursor.style.top) || 0;
373
-
374
- await utils.animate(duration, (p) => {
375
- cursor.style.left = `${startX + (x - startX) * p}px`;
376
- cursor.style.top = `${startY + (y - startY) * p}px`;
377
- cursor.style.opacity = '1';
378
- }, easing);
379
- },
380
-
381
- async moveToElement(selector, duration = 500, easing = 'easeInOut', offsetX = 0, offsetY = 0) {
382
- const el = utils.getElement(selector);
383
- if (!el) return false;
384
-
385
- const rect = el.getBoundingClientRect();
386
- const x = rect.left + rect.width/2 + offsetX;
387
- const y = rect.top + rect.height/2 + offsetY;
388
-
389
- await this.moveTo(x, y, duration, easing);
390
- return true;
391
- },
392
-
393
- showClick(x, y, color = '#ff4444') {
394
- const ripple = utils.createElement('div', {
395
- position: 'fixed',
396
- left: `${x}px`,
397
- top: `${y}px`,
398
- width: '0',
399
- height: '0',
400
- borderRadius: '50%',
401
- background: color,
402
- opacity: '0.5',
403
- transform: 'translate(-50%, -50%)',
404
- transition: 'all 0.4s ease-out'
405
- });
406
- document.body.appendChild(ripple);
407
-
408
- requestAnimationFrame(() => {
409
- ripple.style.width = '60px';
410
- ripple.style.height = '60px';
411
- ripple.style.opacity = '0';
412
- });
413
-
414
- setTimeout(() => ripple.remove(), 400);
415
- },
416
-
417
- async wiggle(selector, intensity = 5, duration = 500) {
418
- const el = utils.getElement(selector);
419
- if (!el) return false;
420
-
421
- await this.moveToElement(selector, duration/2);
422
-
423
- const cursor = document.getElementById('agex-cursor');
424
- if (!cursor) return false;
425
-
426
- const baseX = parseFloat(cursor.style.left);
427
- const baseY = parseFloat(cursor.style.top);
428
-
429
- for (let i = 0; i < 3; i++) {
430
- await utils.animate(100, (p) => {
431
- cursor.style.left = `${baseX + Math.sin(p * Math.PI * 2) * intensity}px`;
432
- });
433
- }
434
- cursor.style.left = `${baseX}px`;
435
- return true;
436
- },
437
-
438
- async drag(fromSelector, toSelector, duration = 800) {
439
- const from = utils.getElement(fromSelector);
440
- const to = utils.getElement(toSelector);
441
- if (!from || !to) return false;
442
-
443
- await this.moveToElement(fromSelector, duration/3);
444
- this.showClick(parseFloat(document.getElementById('agex-cursor')?.style.left || '0'),
445
- parseFloat(document.getElementById('agex-cursor')?.style.top || '0'));
446
- await utils.wait(100);
447
- await this.moveToElement(toSelector, duration/2);
448
-
449
- return true;
450
- },
451
-
452
- async followPath(points, duration = 1000) {
453
- if (!points || points.length < 2) return false;
454
-
455
- const segmentDuration = duration / (points.length - 1);
456
- for (let i = 0; i < points.length - 1; i++) {
457
- await this.moveTo(points[i + 1].x, points[i + 1].y, segmentDuration);
458
- }
459
- return true;
460
- }
461
- },
462
-
463
- // OVERLAY
464
- overlay: {
465
- showStep(step, total, text) {
466
- this.clearStep();
467
- const badge = utils.createElement('div', {
468
- position: 'fixed',
469
- top: '20px',
470
- right: '20px',
471
- background: '#333',
472
- color: 'white',
473
- padding: '8px 16px',
474
- borderRadius: '20px',
475
- fontFamily: 'system-ui, sans-serif',
476
- fontSize: '14px'
477
- }, `${EFFECTS_ID}-step`);
478
- badge.classList.add(`${EFFECTS_ID}-overlay`);
479
- badge.textContent = text || `Step ${step} of ${total}`;
480
- document.body.appendChild(badge);
481
- },
482
-
483
- clearStep() {
484
- utils.removeElements(`#${EFFECTS_ID}-step`);
485
- },
486
-
487
- showTitle(title, subtitle, duration = 3000, bgColor = '#1a1a2e', textColor = 'white') {
488
- const card = utils.createElement('div', {
489
- position: 'fixed',
490
- top: '0', left: '0', right: '0', bottom: '0',
491
- background: bgColor,
492
- display: 'flex',
493
- flexDirection: 'column',
494
- alignItems: 'center',
495
- justifyContent: 'center',
496
- opacity: '0',
497
- transition: 'opacity 0.5s ease'
498
- }, `${EFFECTS_ID}-title`);
499
- card.classList.add(`${EFFECTS_ID}-transition`);
500
-
501
- const h1 = document.createElement('h1');
502
- h1.style.cssText = `color: ${textColor}; font-size: 48px; margin: 0; font-family: system-ui, sans-serif;`;
503
- h1.textContent = title;
504
- card.appendChild(h1);
505
-
506
- if (subtitle) {
507
- const h2 = document.createElement('h2');
508
- h2.style.cssText = `color: ${textColor}; font-size: 24px; margin-top: 16px; opacity: 0.8; font-family: system-ui, sans-serif;`;
509
- h2.textContent = subtitle;
510
- card.appendChild(h2);
511
- }
512
-
513
- document.body.appendChild(card);
514
- requestAnimationFrame(() => card.style.opacity = '1');
515
-
516
- setTimeout(() => {
517
- card.style.opacity = '0';
518
- setTimeout(() => card.remove(), 500);
519
- }, duration);
520
- },
521
-
522
- showLowerThird(title, subtitle, duration = 5000, color = '#0066ff') {
523
- this.clearLowerThird();
524
- const lower = utils.createElement('div', {
525
- position: 'fixed',
526
- bottom: '60px',
527
- left: '40px',
528
- background: `linear-gradient(90deg, ${color}, transparent)`,
529
- padding: '16px 40px 16px 20px',
530
- borderLeft: `4px solid ${color}`,
531
- transform: 'translateX(-100%)',
532
- transition: 'transform 0.5s ease'
533
- }, `${EFFECTS_ID}-lower-third`);
534
- lower.classList.add(`${EFFECTS_ID}-overlay`);
535
-
536
- const t = document.createElement('div');
537
- t.style.cssText = 'color: white; font-size: 20px; font-weight: bold; font-family: system-ui, sans-serif;';
538
- t.textContent = title;
539
- lower.appendChild(t);
540
-
541
- if (subtitle) {
542
- const s = document.createElement('div');
543
- s.style.cssText = 'color: white; font-size: 14px; opacity: 0.9; margin-top: 4px; font-family: system-ui, sans-serif;';
544
- s.textContent = subtitle;
545
- lower.appendChild(s);
546
- }
547
-
548
- document.body.appendChild(lower);
549
- requestAnimationFrame(() => lower.style.transform = 'translateX(0)');
550
-
551
- if (duration) {
552
- setTimeout(() => {
553
- lower.style.transform = 'translateX(-100%)';
554
- setTimeout(() => lower.remove(), 500);
555
- }, duration);
556
- }
557
- },
558
-
559
- clearLowerThird() {
560
- utils.removeElements(`#${EFFECTS_ID}-lower-third`);
561
- },
562
-
563
- showWatermark(text, position = 'bottom-right', logoUrl) {
564
- this.clearWatermark();
565
- const positions = {
566
- 'top-left': { top: '20px', left: '20px' },
567
- 'top-right': { top: '20px', right: '20px' },
568
- 'bottom-left': { bottom: '20px', left: '20px' },
569
- 'bottom-right': { bottom: '20px', right: '20px' }
570
- };
571
-
572
- const watermark = utils.createElement('div', {
573
- position: 'fixed',
574
- ...positions[position],
575
- color: 'rgba(255,255,255,0.5)',
576
- fontSize: '14px',
577
- fontFamily: 'system-ui, sans-serif'
578
- }, `${EFFECTS_ID}-watermark`);
579
- watermark.classList.add(`${EFFECTS_ID}-overlay`);
580
- watermark.textContent = text;
581
- document.body.appendChild(watermark);
582
- },
583
-
584
- clearWatermark() {
585
- utils.removeElements(`#${EFFECTS_ID}-watermark`);
586
- },
587
-
588
- showKeystroke(keys, duration = 1500) {
589
- const kbd = utils.createElement('div', {
590
- position: 'fixed',
591
- bottom: '40px',
592
- left: '50%',
593
- transform: 'translateX(-50%)',
594
- display: 'flex',
595
- gap: '8px',
596
- background: 'rgba(0,0,0,0.8)',
597
- padding: '12px 20px',
598
- borderRadius: '8px'
599
- }, `${EFFECTS_ID}-keystroke`);
600
- kbd.classList.add(`${EFFECTS_ID}-overlay`);
601
-
602
- keys.forEach((key, i) => {
603
- if (i > 0) {
604
- const plus = document.createElement('span');
605
- plus.textContent = '+';
606
- plus.style.cssText = 'color: rgba(255,255,255,0.5); font-size: 18px;';
607
- kbd.appendChild(plus);
608
- }
609
- const k = document.createElement('span');
610
- k.style.cssText = 'background: #444; color: white; padding: 8px 12px; border-radius: 4px; font-family: system-ui, sans-serif; font-size: 16px; border: 1px solid #666;';
611
- k.textContent = key;
612
- kbd.appendChild(k);
613
- });
614
-
615
- document.body.appendChild(kbd);
616
- setTimeout(() => kbd.remove(), duration);
617
- },
618
-
619
- showProgress(percent) {
620
- let bar = document.getElementById(`${EFFECTS_ID}-progress`);
621
- if (!bar) {
622
- bar = utils.createElement('div', {
623
- position: 'fixed',
624
- top: '0',
625
- left: '0',
626
- height: '4px',
627
- background: '#0066ff',
628
- width: '0',
629
- transition: 'width 0.3s ease'
630
- }, `${EFFECTS_ID}-progress`);
631
- bar.classList.add(`${EFFECTS_ID}-overlay`);
632
- document.body.appendChild(bar);
633
- }
634
- bar.style.width = `${percent}%`;
635
- },
636
-
637
- clearProgress() {
638
- utils.removeElements(`#${EFFECTS_ID}-progress`);
639
- },
640
-
641
- async countdown(seconds = 3) {
642
- for (let i = seconds; i > 0; i--) {
643
- const num = utils.createElement('div', {
644
- position: 'fixed',
645
- top: '50%',
646
- left: '50%',
647
- transform: 'translate(-50%, -50%) scale(0)',
648
- fontSize: '120px',
649
- fontWeight: 'bold',
650
- color: 'white',
651
- textShadow: '0 0 40px rgba(0,0,0,0.5)',
652
- transition: 'transform 0.3s ease, opacity 0.3s ease',
653
- fontFamily: 'system-ui, sans-serif'
654
- });
655
- num.classList.add(`${EFFECTS_ID}-overlay`);
656
- num.textContent = i;
657
- document.body.appendChild(num);
658
-
659
- requestAnimationFrame(() => num.style.transform = 'translate(-50%, -50%) scale(1)');
660
- await utils.wait(700);
661
- num.style.opacity = '0';
662
- num.style.transform = 'translate(-50%, -50%) scale(1.5)';
663
- await utils.wait(300);
664
- num.remove();
665
- }
666
- }
667
- },
668
-
669
- // DRAWING
670
- drawing: {
671
- getCanvas() {
672
- let canvas = document.getElementById(`${EFFECTS_ID}-canvas`);
673
- if (!canvas) {
674
- canvas = document.createElement('canvas');
675
- canvas.id = `${EFFECTS_ID}-canvas`;
676
- canvas.classList.add(`${EFFECTS_ID}-drawing-canvas`, EFFECTS_ID);
677
- canvas.width = window.innerWidth;
678
- canvas.height = window.innerHeight;
679
- canvas.style.cssText = 'position: fixed; top: 0; left: 0; pointer-events: none; z-index: 2147483641;';
680
- document.body.appendChild(canvas);
681
- }
682
- return canvas;
683
- },
684
-
685
- async circle(selector, color = '#ff4444', label = '', strokeWidth = 3, duration = 800) {
686
- const el = utils.getElement(selector);
687
- if (!el) return false;
688
-
689
- const rect = el.getBoundingClientRect();
690
- const canvas = this.getCanvas();
691
- const ctx = canvas.getContext('2d');
692
-
693
- const cx = rect.left + rect.width/2;
694
- const cy = rect.top + rect.height/2;
695
- const radius = Math.max(rect.width, rect.height)/2 + 10;
696
-
697
- await utils.animate(duration, (p) => {
698
- ctx.clearRect(cx - radius - 10, cy - radius - 10, radius * 2 + 20, radius * 2 + 20);
699
- ctx.beginPath();
700
- ctx.arc(cx, cy, radius, -Math.PI/2, -Math.PI/2 + Math.PI * 2 * p);
701
- ctx.strokeStyle = color;
702
- ctx.lineWidth = strokeWidth;
703
- ctx.stroke();
704
- });
705
-
706
- // Add label if provided
707
- if (label) {
708
- const labelEl = utils.createElement('div', {
709
- position: 'absolute',
710
- left: `${cx + window.scrollX}px`,
711
- top: `${cy + window.scrollY + radius + 12}px`,
712
- transform: 'translateX(-50%)',
713
- background: color,
714
- color: 'white',
715
- padding: '6px 12px',
716
- borderRadius: '4px',
717
- fontFamily: 'system-ui, sans-serif',
718
- fontSize: '14px',
719
- fontWeight: '600',
720
- boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
721
- whiteSpace: 'nowrap',
722
- zIndex: '2147483642'
723
- }, `${EFFECTS_ID}-circle-label-${Date.now()}`);
724
- labelEl.classList.add(`${EFFECTS_ID}-circle-label`);
725
- labelEl.textContent = label;
726
- document.body.appendChild(labelEl);
727
- }
728
-
729
- return true;
730
- },
731
-
732
- async arrow(fromX, fromY, toX, toY, color = '#ff4444', strokeWidth = 3, duration = 600) {
733
- const canvas = this.getCanvas();
734
- const ctx = canvas.getContext('2d');
735
-
736
- await utils.animate(duration, (p) => {
737
- const currentX = fromX + (toX - fromX) * p;
738
- const currentY = fromY + (toY - fromY) * p;
739
-
740
- ctx.beginPath();
741
- ctx.moveTo(fromX, fromY);
742
- ctx.lineTo(currentX, currentY);
743
- ctx.strokeStyle = color;
744
- ctx.lineWidth = strokeWidth;
745
- ctx.stroke();
746
-
747
- if (p === 1) {
748
- const angle = Math.atan2(toY - fromY, toX - fromX);
749
- const headLen = 15;
750
- ctx.beginPath();
751
- ctx.moveTo(toX, toY);
752
- ctx.lineTo(toX - headLen * Math.cos(angle - Math.PI/6), toY - headLen * Math.sin(angle - Math.PI/6));
753
- ctx.moveTo(toX, toY);
754
- ctx.lineTo(toX - headLen * Math.cos(angle + Math.PI/6), toY - headLen * Math.sin(angle + Math.PI/6));
755
- ctx.stroke();
756
- }
757
- });
758
- },
759
-
760
- async arrowBetween(fromSelector, toSelector, color = '#ff4444', strokeWidth = 3, duration = 600) {
761
- const from = utils.getElement(fromSelector);
762
- const to = utils.getElement(toSelector);
763
- if (!from || !to) return false;
764
-
765
- const fromRect = from.getBoundingClientRect();
766
- const toRect = to.getBoundingClientRect();
767
-
768
- await this.arrow(
769
- fromRect.left + fromRect.width/2,
770
- fromRect.top + fromRect.height/2,
771
- toRect.left + toRect.width/2,
772
- toRect.top + toRect.height/2,
773
- color, strokeWidth, duration
774
- );
775
- return true;
776
- },
777
-
778
- freehand(points, color = '#ff4444', strokeWidth = 3) {
779
- if (!points || points.length < 2) return;
780
-
781
- const canvas = this.getCanvas();
782
- const ctx = canvas.getContext('2d');
783
-
784
- ctx.beginPath();
785
- ctx.moveTo(points[0].x, points[0].y);
786
- for (let i = 1; i < points.length; i++) {
787
- ctx.lineTo(points[i].x, points[i].y);
788
- }
789
- ctx.strokeStyle = color;
790
- ctx.lineWidth = strokeWidth;
791
- ctx.lineCap = 'round';
792
- ctx.lineJoin = 'round';
793
- ctx.stroke();
794
- },
795
-
796
- clear() {
797
- const canvas = document.getElementById(`${EFFECTS_ID}-canvas`);
798
- if (canvas) {
799
- const ctx = canvas.getContext('2d');
800
- ctx.clearRect(0, 0, canvas.width, canvas.height);
801
- }
802
- utils.removeElements(`.${EFFECTS_ID}-circle-label`);
803
- }
804
- },
805
-
806
- // TRANSITIONS
807
- transitions: {
808
- async fade(color = '#000', duration = 500, hold = 1000) {
809
- const overlay = utils.createElement('div', {
810
- position: 'fixed',
811
- top: '0', left: '0', right: '0', bottom: '0',
812
- background: color,
813
- opacity: '0',
814
- transition: `opacity ${duration}ms ease`
815
- }, `${EFFECTS_ID}-fade`);
816
- overlay.classList.add(`${EFFECTS_ID}-transition`);
817
- document.body.appendChild(overlay);
818
-
819
- requestAnimationFrame(() => overlay.style.opacity = '1');
820
- await utils.wait(duration + hold);
821
- overlay.style.opacity = '0';
822
- await utils.wait(duration);
823
- overlay.remove();
824
- },
825
-
826
- async wipe(direction = 'left', color = '#000', duration = 800) {
827
- const overlay = utils.createElement('div', {
828
- position: 'fixed',
829
- top: '0', left: '0', right: '0', bottom: '0',
830
- background: color,
831
- transition: `transform ${duration/2}ms ease`
832
- }, `${EFFECTS_ID}-wipe`);
833
- overlay.classList.add(`${EFFECTS_ID}-transition`);
834
-
835
- const transforms = {
836
- left: ['translateX(-100%)', 'translateX(0)', 'translateX(100%)'],
837
- right: ['translateX(100%)', 'translateX(0)', 'translateX(-100%)'],
838
- up: ['translateY(100%)', 'translateY(0)', 'translateY(-100%)'],
839
- down: ['translateY(-100%)', 'translateY(0)', 'translateY(100%)']
840
- };
841
-
842
- overlay.style.transform = transforms[direction][0];
843
- document.body.appendChild(overlay);
844
-
845
- await utils.wait(50);
846
- overlay.style.transform = transforms[direction][1];
847
- await utils.wait(duration/2);
848
- overlay.style.transform = transforms[direction][2];
849
- await utils.wait(duration/2);
850
- overlay.remove();
851
- },
852
-
853
- async chapter(num, title, duration = 3000, bgColor = '#1a1a2e', textColor = 'white') {
854
- effects.overlay.showTitle(`Chapter ${num}`, title, duration, bgColor, textColor);
855
- await utils.wait(duration + 500);
856
- },
857
-
858
- slowMotion(show) {
859
- if (show) {
860
- const indicator = utils.createElement('div', {
861
- position: 'fixed',
862
- top: '20px',
863
- left: '50%',
864
- transform: 'translateX(-50%)',
865
- background: 'rgba(255,0,0,0.8)',
866
- color: 'white',
867
- padding: '8px 16px',
868
- borderRadius: '4px',
869
- fontSize: '14px',
870
- fontFamily: 'system-ui, sans-serif'
871
- }, `${EFFECTS_ID}-slow-motion`);
872
- indicator.classList.add(`${EFFECTS_ID}-overlay`);
873
- indicator.textContent = '◉ SLOW MOTION';
874
- document.body.appendChild(indicator);
875
- } else {
876
- utils.removeElements(`#${EFFECTS_ID}-slow-motion`);
877
- }
878
- }
879
- },
880
-
881
- // ATTENTION
882
- attention: {
883
- magnify(selector, scale = 2, size = 150) {
884
- const el = utils.getElement(selector);
885
- if (!el) return false;
886
-
887
- this.clearMagnify();
888
- const rect = el.getBoundingClientRect();
889
-
890
- const magnifier = utils.createElement('div', {
891
- position: 'absolute',
892
- left: `${rect.right + window.scrollX + 20}px`,
893
- top: `${rect.top + window.scrollY}px`,
894
- width: `${size}px`,
895
- height: `${size}px`,
896
- borderRadius: '50%',
897
- border: '3px solid #333',
898
- overflow: 'hidden',
899
- boxShadow: '0 4px 20px rgba(0,0,0,0.3)'
900
- }, `${EFFECTS_ID}-magnifier`);
901
- magnifier.classList.add(`${EFFECTS_ID}-overlay`);
902
-
903
- const inner = document.createElement('div');
904
- inner.style.cssText = `transform: scale(${scale}); transform-origin: center;`;
905
- inner.innerHTML = el.outerHTML;
906
- magnifier.appendChild(inner);
907
-
908
- document.body.appendChild(magnifier);
909
- return true;
910
- },
911
-
912
- clearMagnify() {
913
- utils.removeElements(`#${EFFECTS_ID}-magnifier`);
914
- },
915
-
916
- numberSteps(selectors, startNum = 1) {
917
- let found = false;
918
- selectors.forEach((sel, i) => {
919
- const el = utils.getElement(sel);
920
- if (!el) return;
921
- found = true;
922
-
923
- const rect = el.getBoundingClientRect();
924
- const badge = utils.createElement('div', {
925
- position: 'absolute',
926
- left: `${rect.left + window.scrollX - 12}px`,
927
- top: `${rect.top + window.scrollY - 12}px`,
928
- width: '24px',
929
- height: '24px',
930
- borderRadius: '50%',
931
- background: '#0066ff',
932
- color: 'white',
933
- display: 'flex',
934
- alignItems: 'center',
935
- justifyContent: 'center',
936
- fontSize: '14px',
937
- fontWeight: 'bold',
938
- fontFamily: 'system-ui, sans-serif'
939
- });
940
- badge.classList.add(`${EFFECTS_ID}-number-step`);
941
- badge.textContent = startNum + i;
942
- document.body.appendChild(badge);
943
- });
944
- return found;
945
- },
946
-
947
- clearNumberSteps() {
948
- utils.removeElements(`.${EFFECTS_ID}-number-step`);
949
- },
950
-
951
- comparison(beforeSel, afterSel, orientation = 'horizontal') {
952
- // Simplified: just highlight both with labels
953
- const before = utils.getElement(beforeSel);
954
- const after = utils.getElement(afterSel);
955
- if (!before || !after) return false;
956
-
957
- effects.annotation.add(beforeSel, { text: 'BEFORE', position: 'top', color: '#cc0000' });
958
- effects.annotation.add(afterSel, { text: 'AFTER', position: 'top', color: '#00cc00' });
959
- return true;
960
- },
961
-
962
- clearComparison() {
963
- effects.annotation.clear();
964
- }
965
- },
966
-
967
- // INFO
968
- info: {
969
- showTooltip(selector, text, position = 'top') {
970
- return effects.annotation.add(selector, { text, position, color: '#333' });
971
- },
972
-
973
- clearTooltip() {
974
- effects.annotation.clear();
975
- },
976
-
977
- showCodeSnippet(code, language, title) {
978
- this.clearCodeSnippet();
979
- const snippet = utils.createElement('div', {
980
- position: 'fixed',
981
- top: '50%',
982
- left: '50%',
983
- transform: 'translate(-50%, -50%)',
984
- background: '#1e1e1e',
985
- borderRadius: '8px',
986
- padding: '16px',
987
- maxWidth: '80%',
988
- maxHeight: '60%',
989
- overflow: 'auto',
990
- boxShadow: '0 4px 30px rgba(0,0,0,0.4)'
991
- }, `${EFFECTS_ID}-code-snippet`);
992
- snippet.classList.add(`${EFFECTS_ID}-overlay`);
993
-
994
- if (title) {
995
- const t = document.createElement('div');
996
- t.style.cssText = 'color: #888; font-size: 12px; margin-bottom: 8px; font-family: system-ui, sans-serif;';
997
- t.textContent = title + (language ? ` (${language})` : '');
998
- snippet.appendChild(t);
999
- }
1000
-
1001
- const pre = document.createElement('pre');
1002
- pre.style.cssText = 'margin: 0; color: #d4d4d4; font-family: monospace; font-size: 14px; white-space: pre-wrap;';
1003
- pre.textContent = code;
1004
- snippet.appendChild(pre);
1005
-
1006
- document.body.appendChild(snippet);
1007
- },
1008
-
1009
- clearCodeSnippet() {
1010
- utils.removeElements(`#${EFFECTS_ID}-code-snippet`);
1011
- },
1012
-
1013
- async showStatsCounter(value, label, duration = 1500, prefix = '', suffix = '') {
1014
- this.clearStatsCounter();
1015
- const counter = utils.createElement('div', {
1016
- position: 'fixed',
1017
- top: '50%',
1018
- left: '50%',
1019
- transform: 'translate(-50%, -50%)',
1020
- textAlign: 'center'
1021
- }, `${EFFECTS_ID}-stats-counter`);
1022
- counter.classList.add(`${EFFECTS_ID}-overlay`);
1023
-
1024
- const num = document.createElement('div');
1025
- num.style.cssText = 'font-size: 72px; font-weight: bold; color: #333; font-family: system-ui, sans-serif;';
1026
- counter.appendChild(num);
1027
-
1028
- if (label) {
1029
- const l = document.createElement('div');
1030
- l.style.cssText = 'font-size: 18px; color: #666; margin-top: 8px; font-family: system-ui, sans-serif;';
1031
- l.textContent = label;
1032
- counter.appendChild(l);
1033
- }
1034
-
1035
- document.body.appendChild(counter);
1036
-
1037
- await utils.animate(duration, (p) => {
1038
- num.textContent = prefix + Math.round(value * p).toLocaleString() + suffix;
1039
- });
1040
- },
1041
-
1042
- clearStatsCounter() {
1043
- utils.removeElements(`#${EFFECTS_ID}-stats-counter`);
1044
- },
1045
-
1046
- showChecklist(items) {
1047
- this.clearChecklist();
1048
- const list = utils.createElement('div', {
1049
- position: 'fixed',
1050
- top: '20px',
1051
- right: '20px',
1052
- background: 'white',
1053
- borderRadius: '8px',
1054
- padding: '16px',
1055
- boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
1056
- minWidth: '200px'
1057
- }, `${EFFECTS_ID}-checklist`);
1058
- list.classList.add(`${EFFECTS_ID}-overlay`);
1059
-
1060
- items.forEach((item, i) => {
1061
- const row = document.createElement('div');
1062
- row.style.cssText = 'display: flex; align-items: center; gap: 8px; padding: 8px 0; font-family: system-ui, sans-serif;';
1063
- row.dataset.index = i;
1064
-
1065
- const check = document.createElement('span');
1066
- check.style.cssText = `width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; ${item.checked ? 'background: #00cc00; color: white;' : 'border: 2px solid #ccc;'}`;
1067
- check.textContent = item.checked ? '✓' : '';
1068
- row.appendChild(check);
1069
-
1070
- const text = document.createElement('span');
1071
- text.style.cssText = item.checked ? 'text-decoration: line-through; color: #888;' : '';
1072
- text.textContent = item.text;
1073
- row.appendChild(text);
1074
-
1075
- list.appendChild(row);
1076
- });
1077
-
1078
- document.body.appendChild(list);
1079
- },
1080
-
1081
- updateChecklistItem(index, checked) {
1082
- const list = document.getElementById(`${EFFECTS_ID}-checklist`);
1083
- if (!list) return false;
1084
-
1085
- const row = list.querySelector(`[data-index="${index}"]`);
1086
- if (!row) return false;
1087
-
1088
- const check = row.querySelector('span');
1089
- const text = row.querySelectorAll('span')[1];
1090
-
1091
- if (checked) {
1092
- check.style.background = '#00cc00';
1093
- check.style.color = 'white';
1094
- check.style.border = 'none';
1095
- check.textContent = '✓';
1096
- text.style.textDecoration = 'line-through';
1097
- text.style.color = '#888';
1098
- } else {
1099
- check.style.background = '';
1100
- check.style.border = '2px solid #ccc';
1101
- check.textContent = '';
1102
- text.style.textDecoration = '';
1103
- text.style.color = '';
1104
- }
1105
- return true;
1106
- },
1107
-
1108
- clearChecklist() {
1109
- utils.removeElements(`#${EFFECTS_ID}-checklist`);
1110
- }
1111
- },
1112
-
1113
- // BROADCAST
1114
- broadcast: {
1115
- showFacecam(position = 'bottom-right', size = 200, borderColor = '#0066ff') {
1116
- this.clearFacecam();
1117
- const positions = {
1118
- 'top-left': { top: '20px', left: '20px' },
1119
- 'top-right': { top: '20px', right: '20px' },
1120
- 'bottom-left': { bottom: '20px', left: '20px' },
1121
- 'bottom-right': { bottom: '20px', right: '20px' }
1122
- };
1123
-
1124
- const frame = utils.createElement('div', {
1125
- position: 'fixed',
1126
- ...positions[position],
1127
- width: `${size}px`,
1128
- height: `${size}px`,
1129
- borderRadius: '8px',
1130
- border: `3px solid ${borderColor || '#0066ff'}`,
1131
- background: '#1a1a1a',
1132
- display: 'flex',
1133
- alignItems: 'center',
1134
- justifyContent: 'center',
1135
- color: '#666',
1136
- fontSize: '14px',
1137
- fontFamily: 'system-ui, sans-serif'
1138
- }, `${EFFECTS_ID}-facecam`);
1139
- frame.classList.add(`${EFFECTS_ID}-overlay`);
1140
- frame.textContent = '📷 Facecam';
1141
- document.body.appendChild(frame);
1142
- },
1143
-
1144
- clearFacecam() {
1145
- utils.removeElements(`#${EFFECTS_ID}-facecam`);
1146
- },
1147
-
1148
- showChapterMarker(chapter, title, position = 'top-left') {
1149
- this.clearChapterMarker();
1150
- const positions = {
1151
- 'top-left': { top: '20px', left: '20px' },
1152
- 'top-right': { top: '20px', right: '20px' },
1153
- 'bottom-left': { bottom: '20px', left: '20px' },
1154
- 'bottom-right': { bottom: '20px', right: '20px' }
1155
- };
1156
-
1157
- const marker = utils.createElement('div', {
1158
- position: 'fixed',
1159
- ...positions[position],
1160
- background: 'rgba(0,0,0,0.8)',
1161
- color: 'white',
1162
- padding: '8px 16px',
1163
- borderRadius: '4px',
1164
- fontFamily: 'system-ui, sans-serif'
1165
- }, `${EFFECTS_ID}-chapter-marker`);
1166
- marker.classList.add(`${EFFECTS_ID}-overlay`);
1167
- marker.innerHTML = `<span style="opacity:0.6">Chapter ${chapter}</span> ${title}`;
1168
- document.body.appendChild(marker);
1169
- },
1170
-
1171
- clearChapterMarker() {
1172
- utils.removeElements(`#${EFFECTS_ID}-chapter-marker`);
1173
- },
1174
-
1175
- showCallout(selector, text, position = 'right') {
1176
- return effects.annotation.add(selector, { text, position, color: '#0066ff' });
1177
- },
1178
-
1179
- clearCallout() {
1180
- effects.annotation.clear();
1181
- },
1182
-
1183
- showHotkeyHint(keys, description, position = 'bottom-right') {
1184
- this.clearHotkeyHint();
1185
- const positions = {
1186
- 'top-left': { top: '20px', left: '20px' },
1187
- 'top-right': { top: '20px', right: '20px' },
1188
- 'bottom-left': { bottom: '20px', left: '20px' },
1189
- 'bottom-right': { bottom: '20px', right: '20px' }
1190
- };
1191
-
1192
- const hint = utils.createElement('div', {
1193
- position: 'fixed',
1194
- ...positions[position],
1195
- background: 'rgba(0,0,0,0.8)',
1196
- padding: '8px 12px',
1197
- borderRadius: '6px',
1198
- display: 'flex',
1199
- alignItems: 'center',
1200
- gap: '8px'
1201
- }, `${EFFECTS_ID}-hotkey-hint`);
1202
- hint.classList.add(`${EFFECTS_ID}-overlay`);
1203
-
1204
- keys.forEach((key, i) => {
1205
- if (i > 0) {
1206
- const plus = document.createElement('span');
1207
- plus.textContent = '+';
1208
- plus.style.color = 'rgba(255,255,255,0.5)';
1209
- hint.appendChild(plus);
1210
- }
1211
- const k = document.createElement('span');
1212
- k.style.cssText = 'background: #444; color: white; padding: 4px 8px; border-radius: 3px; font-size: 12px; font-family: system-ui, sans-serif;';
1213
- k.textContent = key;
1214
- hint.appendChild(k);
1215
- });
1216
-
1217
- const desc = document.createElement('span');
1218
- desc.style.cssText = 'color: rgba(255,255,255,0.8); font-size: 12px; margin-left: 8px; font-family: system-ui, sans-serif;';
1219
- desc.textContent = description;
1220
- hint.appendChild(desc);
1221
-
1222
- document.body.appendChild(hint);
1223
- },
1224
-
1225
- clearHotkeyHint() {
1226
- utils.removeElements(`#${EFFECTS_ID}-hotkey-hint`);
1227
- },
1228
-
1229
- showAlert(text, type = 'info', duration = 3000) {
1230
- const colors = {
1231
- info: '#0066ff',
1232
- success: '#00cc00',
1233
- warning: '#ff9900',
1234
- error: '#cc0000'
1235
- };
1236
-
1237
- const alert = utils.createElement('div', {
1238
- position: 'fixed',
1239
- top: '20px',
1240
- left: '50%',
1241
- transform: 'translateX(-50%) translateY(-100px)',
1242
- background: colors[type] || colors.info,
1243
- color: 'white',
1244
- padding: '12px 24px',
1245
- borderRadius: '8px',
1246
- fontSize: '16px',
1247
- fontFamily: 'system-ui, sans-serif',
1248
- boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
1249
- transition: 'transform 0.3s ease'
1250
- });
1251
- alert.classList.add(`${EFFECTS_ID}-overlay`, `${EFFECTS_ID}-alert`);
1252
- alert.textContent = text;
1253
- document.body.appendChild(alert);
1254
-
1255
- requestAnimationFrame(() => alert.style.transform = 'translateX(-50%) translateY(0)');
1256
-
1257
- setTimeout(() => {
1258
- alert.style.transform = 'translateX(-50%) translateY(-100px)';
1259
- setTimeout(() => alert.remove(), 300);
1260
- }, duration);
1261
- }
1262
- },
1263
-
1264
- // NARRATION
1265
- narration: {
1266
- async speak(text, rate = 1, pitch = 1, position = 'bottom') {
1267
- return new Promise(resolve => {
1268
- this.showSubtitle(text, 0, position); // 0 = no auto-clear
1269
-
1270
- if ('speechSynthesis' in window) {
1271
- const utterance = new SpeechSynthesisUtterance(text);
1272
- utterance.rate = rate;
1273
- utterance.pitch = pitch;
1274
- utterance.onend = () => {
1275
- this.clear();
1276
- resolve(true);
1277
- };
1278
- utterance.onerror = () => {
1279
- this.clear();
1280
- resolve(false);
1281
- };
1282
- speechSynthesis.speak(utterance);
1283
- } else {
1284
- setTimeout(() => {
1285
- this.clear();
1286
- resolve(false);
1287
- }, text.length * 60); // Fallback timing
1288
- }
1289
- });
1290
- },
1291
-
1292
- showSubtitle(text, duration = 3000, position = 'bottom') {
1293
- this.clear();
1294
- const subtitle = utils.createElement('div', {
1295
- [position]: '40px'
1296
- }, `${EFFECTS_ID}-subtitle`);
1297
- subtitle.classList.add(`${EFFECTS_ID}-subtitle`);
1298
- subtitle.textContent = text;
1299
- document.body.appendChild(subtitle);
1300
-
1301
- if (duration > 0) {
1302
- setTimeout(() => subtitle.remove(), duration);
1303
- }
1304
- },
1305
-
1306
- clear() {
1307
- utils.removeElements(`#${EFFECTS_ID}-subtitle`);
1308
- if ('speechSynthesis' in window) {
1309
- speechSynthesis.cancel();
1310
- }
1311
- }
1312
- },
1313
-
1314
- // TIMING
1315
- timing: {
1316
- async pause(duration = 1000) {
1317
- await utils.wait(duration);
1318
- },
1319
-
1320
- async panToElement(selector, duration = 500) {
1321
- const el = utils.getElement(selector);
1322
- if (!el) return false;
1323
-
1324
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
1325
- await utils.wait(duration);
1326
- return true;
1327
- },
1328
-
1329
- async typeText(selector, text, speed = 50) {
1330
- const el = utils.getElement(selector);
1331
- if (!el || !('value' in el)) return false;
1332
-
1333
- el.focus();
1334
- for (const char of text) {
1335
- el.value += char;
1336
- el.dispatchEvent(new Event('input', { bubbles: true }));
1337
- await utils.wait(speed + Math.random() * 30);
1338
- }
1339
- return true;
1340
- }
1341
- }
1342
- };
1343
-
1344
- // Unified apply function
1345
- function apply(effect, action, params = {}) {
1346
- const handler = effects[effect];
1347
- if (!handler) {
1348
- return { success: false, error: `Unknown effect: ${effect}` };
1349
- }
1350
-
1351
- const method = handler[action];
1352
- if (!method) {
1353
- return { success: false, error: `Unknown action: ${action} for effect: ${effect}` };
1354
- }
1355
-
1356
- try {
1357
- const result = method.call(handler, ...Object.values(params));
1358
- if (result instanceof Promise) {
1359
- return result.then(r => ({ success: r !== false, result: r }));
1360
- }
1361
- return { success: result !== false, result };
1362
- } catch (err) {
1363
- return { success: false, error: err.message };
1364
- }
1365
- }
1366
-
1367
- // Banner (persistent top-of-page notification)
1368
- const banner = {
1369
- show(text, subtitle) {
1370
- this.clear();
1371
- const el = utils.createElement('div', {
1372
- position: 'fixed',
1373
- top: '20px',
1374
- left: '50%',
1375
- transform: 'translateX(-50%)',
1376
- background: 'rgba(0,0,0,0.85)',
1377
- color: 'white',
1378
- padding: '12px 24px',
1379
- borderRadius: '8px',
1380
- zIndex: '99999',
1381
- fontFamily: 'system-ui, sans-serif',
1382
- fontSize: '18px',
1383
- fontWeight: '600',
1384
- boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
1385
- textAlign: 'center',
1386
- pointerEvents: 'none'
1387
- }, `${EFFECTS_ID}-banner`);
1388
- let html = text;
1389
- if (subtitle) {
1390
- html += '<br><span style="font-size:14px;opacity:0.8;font-weight:400">' + subtitle + '</span>';
1391
- }
1392
- el.innerHTML = html;
1393
- document.body.appendChild(el);
1394
- },
1395
- clear() {
1396
- utils.removeElements(`#${EFFECTS_ID}-banner`);
1397
- const old = document.getElementById('__agex_title_overlay');
1398
- if (old) old.remove();
1399
- }
1400
- };
1401
-
1402
- // Smart label positioning (shared by proof and highlight)
1403
- function computeLabelPosition(box, labelHeight, gap) {
1404
- labelHeight = labelHeight || 40;
1405
- gap = gap || 16;
1406
- var viewportBottom = window.innerHeight;
1407
- var elBottom = box.y + box.height;
1408
- var elTop = box.y;
1409
- var belowY = elBottom + gap;
1410
- var aboveY = elTop - gap - labelHeight;
1411
- var placeAbove = false;
1412
- var existingLabels = document.querySelectorAll('.agex-fx-label, .agex-proof-highlight');
1413
- var hasCollisionBelow = false;
1414
- var labelCenterX = box.x + box.width / 2;
1415
- existingLabels.forEach(function(el) {
1416
- var r = el.getBoundingClientRect();
1417
- if (r.top >= elBottom && r.top <= elBottom + labelHeight + gap * 2) {
1418
- if (Math.abs(r.left + r.width / 2 - labelCenterX) < 150) hasCollisionBelow = true;
1419
- }
1420
- });
1421
- if ((belowY + labelHeight > viewportBottom || hasCollisionBelow) && aboveY >= 0) {
1422
- placeAbove = true;
1423
- }
1424
- return placeAbove
1425
- ? (elTop + window.scrollY - gap - labelHeight)
1426
- : (elBottom + window.scrollY + gap);
1427
- }
1428
-
1429
- // Proof / highlight helpers exposed on the API
1430
- const proof = {
1431
- highlightBox(box, label, color) {
1432
- color = color || '#00ff00';
1433
- if (!box || box.x === undefined) return 'invalid box';
1434
-
1435
- var hl = document.createElement('div');
1436
- hl.className = 'agex-effects agex-fx-highlight';
1437
- hl.style.cssText = 'position:absolute;left:' + (box.x + window.scrollX - 4) + 'px;top:' + (box.y + window.scrollY - 4) + 'px;width:' + (box.width + 8) + 'px;height:' + (box.height + 8) + 'px;border:4px solid ' + color + ';border-radius:4px;pointer-events:none;z-index:2147483640;box-sizing:border-box;box-shadow:0 0 0 2px rgba(0,0,0,0.8),0 0 12px ' + color + ';';
1438
- document.body.appendChild(hl);
1439
-
1440
- if (label) {
1441
- var labelEl = document.createElement('div');
1442
- labelEl.className = 'agex-effects agex-fx-label';
1443
- labelEl.textContent = label;
1444
- var labelY = computeLabelPosition(box);
1445
- labelEl.style.cssText = 'position:absolute;left:' + (box.x + window.scrollX + box.width / 2) + 'px;top:' + labelY + 'px;transform:translateX(-50%);background:' + color + ';color:white;padding:8px 16px;border-radius:6px;font-family:system-ui,sans-serif;font-size:14px;font-weight:600;white-space:nowrap;box-shadow:0 2px 12px rgba(0,0,0,0.4);z-index:2147483647;pointer-events:none;';
1446
- document.body.appendChild(labelEl);
1447
- }
1448
- return 'highlighted';
1449
- },
1450
-
1451
- highlightProof(box, label, color) {
1452
- color = color || '#00ff00';
1453
- this.clearProof();
1454
- if (!box || box.x === undefined) return 'invalid box';
1455
-
1456
- var hl = document.createElement('div');
1457
- hl.className = 'agex-effects agex-proof-highlight';
1458
- hl.style.cssText = 'position:absolute;left:' + (box.x + window.scrollX - 4) + 'px;top:' + (box.y + window.scrollY - 4) + 'px;width:' + (box.width + 8) + 'px;height:' + (box.height + 8) + 'px;border:4px solid ' + color + ';border-radius:4px;pointer-events:none;z-index:2147483640;box-sizing:border-box;box-shadow:0 0 0 2px rgba(0,0,0,0.8),0 0 12px ' + color + ';';
1459
- document.body.appendChild(hl);
1460
-
1461
- if (label) {
1462
- var labelEl = document.createElement('div');
1463
- labelEl.className = 'agex-effects agex-proof-highlight';
1464
- labelEl.textContent = label;
1465
- var labelY = computeLabelPosition(box);
1466
- labelEl.style.cssText = 'position:absolute;left:' + (box.x + window.scrollX + box.width / 2) + 'px;top:' + labelY + 'px;transform:translateX(-50%);background:' + color + ';color:white;padding:8px 16px;border-radius:6px;font-family:system-ui,sans-serif;font-size:14px;font-weight:600;white-space:nowrap;box-shadow:0 2px 12px rgba(0,0,0,0.4);z-index:2147483647;pointer-events:none;';
1467
- document.body.appendChild(labelEl);
1468
- }
1469
- return 'highlighted';
1470
- },
1471
-
1472
- highlightSelector(selector, label, color) {
1473
- color = color || '#2e7d32';
1474
- var el = document.querySelector(selector);
1475
- if (!el) return 'element not found: ' + selector;
1476
- effects.highlight.add(selector, { color: color, style: 'border' });
1477
- var rect = el.getBoundingClientRect();
1478
- var box = { x: rect.left, y: rect.top, width: rect.width, height: rect.height };
1479
- if (label) {
1480
- var labelEl = document.createElement('div');
1481
- labelEl.className = 'agex-effects agex-fx-label';
1482
- labelEl.textContent = label;
1483
- var labelY = computeLabelPosition(box);
1484
- labelEl.style.cssText = 'position:absolute;left:' + (rect.left + window.scrollX + rect.width / 2) + 'px;top:' + labelY + 'px;transform:translateX(-50%);background:' + color + ';color:white;padding:8px 16px;border-radius:6px;font-family:system-ui,sans-serif;font-size:14px;font-weight:600;white-space:nowrap;box-shadow:0 2px 12px rgba(0,0,0,0.4);z-index:2147483647;pointer-events:none;';
1485
- document.body.appendChild(labelEl);
1486
- }
1487
- return 'highlighted';
1488
- },
1489
-
1490
- getBoundingBox(selector) {
1491
- var el = document.querySelector(selector);
1492
- if (!el) return null;
1493
- var r = el.getBoundingClientRect();
1494
- return { x: r.left, y: r.top, width: r.width, height: r.height };
1495
- },
1496
-
1497
- scrollToSelector(selector) {
1498
- var el = document.querySelector(selector);
1499
- if (el) el.scrollIntoView({ block: 'center', behavior: 'instant' });
1500
- },
1501
-
1502
- scrollToCoords(y, viewportHeight, elHeight) {
1503
- window.scrollTo({
1504
- top: window.scrollY + y - (viewportHeight / 2) + (elHeight / 2),
1505
- behavior: 'instant'
1506
- });
1507
- },
1508
-
1509
- notFound(term, color) {
1510
- color = color || '#ff4444';
1511
- this.clearNotFound();
1512
-
1513
- var border = document.createElement('div');
1514
- border.className = 'agex-effects agex-not-found-overlay';
1515
- border.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;border:8px solid ' + color + ';pointer-events:none;z-index:2147483640;box-sizing:border-box;';
1516
- document.body.appendChild(border);
1517
-
1518
- var bannerEl = document.createElement('div');
1519
- bannerEl.className = 'agex-effects agex-not-found-overlay';
1520
- bannerEl.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:' + color + ';color:white;padding:16px 32px;border-radius:8px;z-index:2147483647;font-family:system-ui,sans-serif;text-align:center;box-shadow:0 4px 20px rgba(0,0,0,0.4);';
1521
- bannerEl.innerHTML = '<div style="font-size:24px;font-weight:700;margin-bottom:8px;">NOT FOUND</div><div style="font-size:16px;opacity:0.9;">' + term + '</div>';
1522
- document.body.appendChild(bannerEl);
1523
-
1524
- var xmark = document.createElement('div');
1525
- xmark.className = 'agex-effects agex-not-found-overlay';
1526
- xmark.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:120px;height:120px;border:6px solid ' + color + ';border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:72px;color:' + color + ';font-weight:bold;opacity:0.3;z-index:2147483641;pointer-events:none;';
1527
- xmark.textContent = '\u2715';
1528
- document.body.appendChild(xmark);
1529
-
1530
- return 'not-found overlay added';
1531
- },
1532
-
1533
- clearProof() {
1534
- document.querySelectorAll('.agex-proof-highlight').forEach(function(e) { e.remove(); });
1535
- },
1536
-
1537
- clearNotFound() {
1538
- document.querySelectorAll('.agex-not-found-overlay').forEach(function(e) { e.remove(); });
1539
- },
1540
-
1541
- getScrollPosition() {
1542
- return { x: window.scrollX, y: window.scrollY };
1543
- }
1544
- };
1545
-
1546
- // Clear all effects
1547
- function clearAll() {
1548
- effects.highlight.clear();
1549
- effects.annotation.clear();
1550
- effects.spotlight.clear();
1551
- effects.spotlight.clearBlur();
1552
- effects.zoom.reset();
1553
- effects.overlay.clearStep();
1554
- effects.overlay.clearLowerThird();
1555
- effects.overlay.clearWatermark();
1556
- effects.overlay.clearProgress();
1557
- effects.drawing.clear();
1558
- effects.attention.clearMagnify();
1559
- effects.attention.clearNumberSteps();
1560
- effects.info.clearTooltip();
1561
- effects.info.clearCodeSnippet();
1562
- effects.info.clearStatsCounter();
1563
- effects.info.clearChecklist();
1564
- effects.broadcast.clearFacecam();
1565
- effects.broadcast.clearChapterMarker();
1566
- effects.broadcast.clearHotkeyHint();
1567
- effects.narration.clear();
1568
- banner.clear();
1569
- proof.clearProof();
1570
- proof.clearNotFound();
1571
- utils.removeElements(`.${EFFECTS_ID}`);
1572
- }
1573
-
1574
- // Initialize
1575
- injectStyles();
1576
-
1577
- // Expose API
1578
- window.__agexEffects = {
1579
- apply,
1580
- clearAll,
1581
- banner,
1582
- proof,
1583
- ...effects,
1584
- utils
1585
- };
1586
- })();