animot-presenter 0.2.8 → 0.5.4
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/AnimotPresenter.svelte +1654 -1529
- package/dist/FlowMarkers.svelte +111 -0
- package/dist/FlowMarkers.svelte.d.ts +10 -0
- package/dist/cdn/animot-presenter.css +1 -1
- package/dist/cdn/animot-presenter.esm.js +4998 -4620
- package/dist/cdn/animot-presenter.min.js +9 -9
- package/dist/renderers/IconRenderer.svelte +19 -1
- package/dist/styles/presenter.css +53 -0
- package/dist/types.d.ts +29 -1
- package/dist/utils/arrow-clip-draw.d.ts +28 -0
- package/dist/utils/arrow-clip-draw.js +177 -0
- package/dist/utils/arrow-path.d.ts +15 -0
- package/dist/utils/arrow-path.js +73 -0
- package/dist/utils/trace-svg-paths.d.ts +28 -0
- package/dist/utils/trace-svg-paths.js +247 -0
- package/package.json +1 -1
|
@@ -1,1529 +1,1654 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { onMount, onDestroy } from 'svelte';
|
|
3
|
-
import { tween } from '@animotion/motion';
|
|
4
|
-
import { highlightCode } from './highlight/highlighter';
|
|
5
|
-
import CodeMorph from './highlight/CodeMorph.svelte';
|
|
6
|
-
import ParticlesBackground from './effects/ParticlesBackground.svelte';
|
|
7
|
-
import ConfettiEffect from './effects/ConfettiEffect.svelte';
|
|
8
|
-
import CounterRenderer from './renderers/CounterRenderer.svelte';
|
|
9
|
-
import ChartRenderer from './renderers/ChartRenderer.svelte';
|
|
10
|
-
import IconRenderer from './renderers/IconRenderer.svelte';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let
|
|
158
|
-
let
|
|
159
|
-
let
|
|
160
|
-
let
|
|
161
|
-
let
|
|
162
|
-
let
|
|
163
|
-
let
|
|
164
|
-
let
|
|
165
|
-
let
|
|
166
|
-
|
|
167
|
-
let
|
|
168
|
-
let
|
|
169
|
-
|
|
170
|
-
let
|
|
171
|
-
let
|
|
172
|
-
let
|
|
173
|
-
let
|
|
174
|
-
let
|
|
175
|
-
let
|
|
176
|
-
let
|
|
177
|
-
let
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
textTypewriterState
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (!
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
svg!.style.clipPath =
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const progress = Math.min(
|
|
308
|
-
const eased = 1 - Math.pow(1 - progress, 3);
|
|
309
|
-
const inset = 100 * eased;
|
|
310
|
-
svg!.style.clipPath = goesLeftToRight ? `inset(0
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if (animated
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
if (!
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
if (
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
if (
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
if (
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
{
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
{@const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
.animot-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
.animot-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
.animot-canvas
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
.animot-canvas
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
.animot-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
.animot-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
.animot-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1508
|
-
.animot-
|
|
1509
|
-
.animot-
|
|
1510
|
-
.animot-
|
|
1511
|
-
.animot-
|
|
1512
|
-
.animot-
|
|
1513
|
-
.animot-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
.animot-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
}
|
|
1521
|
-
.animot-
|
|
1522
|
-
.animot-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
.animot-
|
|
1526
|
-
.animot-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
import { tween } from '@animotion/motion';
|
|
4
|
+
import { highlightCode } from './highlight/highlighter';
|
|
5
|
+
import CodeMorph from './highlight/CodeMorph.svelte';
|
|
6
|
+
import ParticlesBackground from './effects/ParticlesBackground.svelte';
|
|
7
|
+
import ConfettiEffect from './effects/ConfettiEffect.svelte';
|
|
8
|
+
import CounterRenderer from './renderers/CounterRenderer.svelte';
|
|
9
|
+
import ChartRenderer from './renderers/ChartRenderer.svelte';
|
|
10
|
+
import IconRenderer from './renderers/IconRenderer.svelte';
|
|
11
|
+
import FlowMarkers from './FlowMarkers.svelte';
|
|
12
|
+
import { traceSvgPaths } from './utils/trace-svg-paths';
|
|
13
|
+
import { arrowClipDraw } from './utils/arrow-clip-draw';
|
|
14
|
+
import { easeInOutCubic, getEasingFn, getBackgroundStyle, hashFraction, getFloatAnimName, computeFloatAmp, computeFloatSpeed } from './engine/utils';
|
|
15
|
+
import type {
|
|
16
|
+
AnimotProject, AnimotPresenterProps, CanvasElement, CodeElement, TextElement,
|
|
17
|
+
ArrowElement, ImageElement, ShapeElement, CounterElement, ChartElement, IconElement,
|
|
18
|
+
SvgElement, MotionPathElement, PathPoint,
|
|
19
|
+
Slide, CodeAnimationMode, AnimatableProperty
|
|
20
|
+
} from './types';
|
|
21
|
+
import './styles/presenter.css';
|
|
22
|
+
|
|
23
|
+
type TweenValue = ReturnType<typeof tween<number>>;
|
|
24
|
+
|
|
25
|
+
interface AnimatedElement {
|
|
26
|
+
x: TweenValue; y: TweenValue; width: TweenValue; height: TweenValue;
|
|
27
|
+
rotation: TweenValue; skewX: TweenValue; skewY: TweenValue;
|
|
28
|
+
tiltX: TweenValue; tiltY: TweenValue; perspective: TweenValue;
|
|
29
|
+
opacity: TweenValue; borderRadius: TweenValue;
|
|
30
|
+
fontSize: TweenValue | null;
|
|
31
|
+
fillColor: ReturnType<typeof tween<string>> | null;
|
|
32
|
+
strokeColor: ReturnType<typeof tween<string>> | null;
|
|
33
|
+
strokeWidth: TweenValue | null;
|
|
34
|
+
shapeMorph: TweenValue | null;
|
|
35
|
+
motionPathProgress: TweenValue | null;
|
|
36
|
+
blur: TweenValue;
|
|
37
|
+
brightness: TweenValue;
|
|
38
|
+
contrast: TweenValue;
|
|
39
|
+
saturate: TweenValue;
|
|
40
|
+
grayscale: TweenValue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ShapeMorphState { fromType: string; toType: string; }
|
|
44
|
+
|
|
45
|
+
// Active motion path loop cancellation tokens
|
|
46
|
+
let motionPathLoopAbort: AbortController | null = null;
|
|
47
|
+
function cancelMotionPathLoops() {
|
|
48
|
+
if (motionPathLoopAbort) { motionPathLoopAbort.abort(); motionPathLoopAbort = null; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --- Motion Path Utilities ---
|
|
52
|
+
function buildPresenterPathD(points: PathPoint[], closed: boolean): string {
|
|
53
|
+
if (points.length < 2) return '';
|
|
54
|
+
let d = `M ${points[0].x} ${points[0].y}`;
|
|
55
|
+
for (let i = 1; i < points.length; i++) {
|
|
56
|
+
const prev = points[i - 1], curr = points[i];
|
|
57
|
+
const cp1x = prev.x + (prev.handleOut?.x ?? 0), cp1y = prev.y + (prev.handleOut?.y ?? 0);
|
|
58
|
+
const cp2x = curr.x + (curr.handleIn?.x ?? 0), cp2y = curr.y + (curr.handleIn?.y ?? 0);
|
|
59
|
+
if (prev.handleOut || curr.handleIn) d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${curr.x} ${curr.y}`;
|
|
60
|
+
else d += ` L ${curr.x} ${curr.y}`;
|
|
61
|
+
}
|
|
62
|
+
if (closed && points.length > 2) {
|
|
63
|
+
const last = points[points.length - 1], first = points[0];
|
|
64
|
+
const cp1x = last.x + (last.handleOut?.x ?? 0), cp1y = last.y + (last.handleOut?.y ?? 0);
|
|
65
|
+
const cp2x = first.x + (first.handleIn?.x ?? 0), cp2y = first.y + (first.handleIn?.y ?? 0);
|
|
66
|
+
if (last.handleOut || first.handleIn) d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${first.x} ${first.y}`;
|
|
67
|
+
else d += ` Z`;
|
|
68
|
+
}
|
|
69
|
+
return d;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function cubicBez(p0: number, p1: number, p2: number, p3: number, t: number): number {
|
|
73
|
+
const mt = 1 - t;
|
|
74
|
+
return mt * mt * mt * p0 + 3 * mt * mt * t * p1 + 3 * mt * t * t * p2 + t * t * t * p3;
|
|
75
|
+
}
|
|
76
|
+
function cubicBezDeriv(p0: number, p1: number, p2: number, p3: number, t: number): number {
|
|
77
|
+
const mt = 1 - t;
|
|
78
|
+
return 3 * mt * mt * (p1 - p0) + 6 * mt * t * (p2 - p1) + 3 * t * t * (p3 - p2);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getPresenterPointOnPath(points: PathPoint[], closed: boolean, progress: number): { x: number; y: number; angle: number } {
|
|
82
|
+
if (points.length < 2) return { x: points[0]?.x ?? 0, y: points[0]?.y ?? 0, angle: 0 };
|
|
83
|
+
const segs: { p0x: number; p0y: number; p1x: number; p1y: number; p2x: number; p2y: number; p3x: number; p3y: number; length: number }[] = [];
|
|
84
|
+
const segCount = closed ? points.length : points.length - 1;
|
|
85
|
+
for (let i = 0; i < segCount; i++) {
|
|
86
|
+
const curr = points[i], next = points[(i + 1) % points.length];
|
|
87
|
+
const p0x = curr.x, p0y = curr.y;
|
|
88
|
+
const p1x = curr.x + (curr.handleOut?.x ?? 0), p1y = curr.y + (curr.handleOut?.y ?? 0);
|
|
89
|
+
const p2x = next.x + (next.handleIn?.x ?? 0), p2y = next.y + (next.handleIn?.y ?? 0);
|
|
90
|
+
const p3x = next.x, p3y = next.y;
|
|
91
|
+
let length = 0, prevPx = p0x, prevPy = p0y;
|
|
92
|
+
for (let s = 1; s <= 20; s++) {
|
|
93
|
+
const t = s / 20;
|
|
94
|
+
const px = cubicBez(p0x, p1x, p2x, p3x, t), py = cubicBez(p0y, p1y, p2y, p3y, t);
|
|
95
|
+
length += Math.sqrt((px - prevPx) ** 2 + (py - prevPy) ** 2);
|
|
96
|
+
prevPx = px; prevPy = py;
|
|
97
|
+
}
|
|
98
|
+
segs.push({ p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, length });
|
|
99
|
+
}
|
|
100
|
+
const totalLength = segs.reduce((sum, s) => sum + s.length, 0);
|
|
101
|
+
const targetLength = progress * totalLength;
|
|
102
|
+
let accum = 0;
|
|
103
|
+
for (const seg of segs) {
|
|
104
|
+
if (accum + seg.length >= targetLength || seg === segs[segs.length - 1]) {
|
|
105
|
+
const t = Math.max(0, Math.min(1, seg.length > 0 ? (targetLength - accum) / seg.length : 0));
|
|
106
|
+
let dx = cubicBezDeriv(seg.p0x, seg.p1x, seg.p2x, seg.p3x, t);
|
|
107
|
+
let dy = cubicBezDeriv(seg.p0y, seg.p1y, seg.p2y, seg.p3y, t);
|
|
108
|
+
// Degenerate tangent at endpoints (no Bezier handles) — sample nearby
|
|
109
|
+
if (Math.abs(dx) < 0.001 && Math.abs(dy) < 0.001) {
|
|
110
|
+
const epsilon = t < 0.5 ? 0.01 : -0.01;
|
|
111
|
+
dx = cubicBezDeriv(seg.p0x, seg.p1x, seg.p2x, seg.p3x, t + epsilon);
|
|
112
|
+
dy = cubicBezDeriv(seg.p0y, seg.p1y, seg.p2y, seg.p3y, t + epsilon);
|
|
113
|
+
}
|
|
114
|
+
// Still zero (fully degenerate segment) — use chord direction
|
|
115
|
+
if (Math.abs(dx) < 0.001 && Math.abs(dy) < 0.001) {
|
|
116
|
+
dx = seg.p3x - seg.p0x;
|
|
117
|
+
dy = seg.p3y - seg.p0y;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
x: cubicBez(seg.p0x, seg.p1x, seg.p2x, seg.p3x, t),
|
|
121
|
+
y: cubicBez(seg.p0y, seg.p1y, seg.p2y, seg.p3y, t),
|
|
122
|
+
angle: Math.atan2(dy, dx) * (180 / Math.PI)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
accum += seg.length;
|
|
126
|
+
}
|
|
127
|
+
return { x: points[0].x, y: points[0].y, angle: 0 };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function computeMotionPathPosition(
|
|
131
|
+
mpPoint: { x: number; y: number; angle: number },
|
|
132
|
+
startPoint: { x: number; y: number; angle: number },
|
|
133
|
+
animX: number, animY: number, animW: number, animH: number,
|
|
134
|
+
closed: boolean
|
|
135
|
+
): { x: number; y: number } {
|
|
136
|
+
if (!closed) {
|
|
137
|
+
return { x: mpPoint.x - animW / 2, y: mpPoint.y - animH / 2 };
|
|
138
|
+
}
|
|
139
|
+
const offsetX = (animX + animW / 2) - startPoint.x;
|
|
140
|
+
const offsetY = (animY + animH / 2) - startPoint.y;
|
|
141
|
+
const angleDelta = (mpPoint.angle - startPoint.angle) * Math.PI / 180;
|
|
142
|
+
const cos = Math.cos(angleDelta);
|
|
143
|
+
const sin = Math.sin(angleDelta);
|
|
144
|
+
return {
|
|
145
|
+
x: mpPoint.x + offsetX * cos - offsetY * sin - animW / 2,
|
|
146
|
+
y: mpPoint.y + offsetX * sin + offsetY * cos - animH / 2
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let {
|
|
151
|
+
src, data, autoplay = false, loop = false, controls = true, arrows = false,
|
|
152
|
+
progress: showProgress = true, keyboard = true, duration: durationOverride,
|
|
153
|
+
startSlide = 0, class: className = '', onslidechange, oncomplete
|
|
154
|
+
}: AnimotPresenterProps = $props();
|
|
155
|
+
|
|
156
|
+
// State
|
|
157
|
+
let project = $state<AnimotProject | null>(null);
|
|
158
|
+
let loading = $state(true);
|
|
159
|
+
let error = $state<string | null>(null);
|
|
160
|
+
let currentSlideIndex = $state(0);
|
|
161
|
+
let isTransitioning = $state(false);
|
|
162
|
+
let isAutoplay = $state(false);
|
|
163
|
+
let transitionClass = $state('');
|
|
164
|
+
let transitionDirection = $state<'forward' | 'backward'>('forward');
|
|
165
|
+
let transitionDurationMs = $state(500);
|
|
166
|
+
let containerEl: HTMLElement;
|
|
167
|
+
let containerWidth = $state(0);
|
|
168
|
+
let containerHeight = $state(0);
|
|
169
|
+
|
|
170
|
+
let animatedElements = $state<Map<string, AnimatedElement>>(new Map());
|
|
171
|
+
let codeHighlights = $state<Map<string, string>>(new Map());
|
|
172
|
+
let elementContent = $state<Map<string, CanvasElement>>(new Map());
|
|
173
|
+
let previousCodeContent = $state<Map<string, string>>(new Map());
|
|
174
|
+
let codeMorphState = $state<Map<string, {oldCode: string, newCode: string, mode: CodeAnimationMode, speed: number, highlightColor: string}>>(new Map());
|
|
175
|
+
let textTypewriterState = $state<Map<string, {fullText: string, displayedChars: number, isAnimating: boolean}>>(new Map());
|
|
176
|
+
let typewriterIntervals = new Map<string, ReturnType<typeof setInterval>>();
|
|
177
|
+
let shapeMorphStates = $state<Map<string, ShapeMorphState>>(new Map());
|
|
178
|
+
let autoplayTimer: ReturnType<typeof setTimeout> | null = null;
|
|
179
|
+
let menuVisible = $state(true);
|
|
180
|
+
let mouseIdleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
181
|
+
|
|
182
|
+
const slides = $derived(project?.slides ?? []);
|
|
183
|
+
const currentSlide = $derived(slides[currentSlideIndex]);
|
|
184
|
+
const canvasWidth = $derived(currentSlide?.canvas.width ?? 800);
|
|
185
|
+
const canvasHeight = $derived(currentSlide?.canvas.height ?? 600);
|
|
186
|
+
|
|
187
|
+
const presentationScale = $derived.by(() => {
|
|
188
|
+
if (!containerWidth || !containerHeight) return 1;
|
|
189
|
+
const scaleX = containerWidth / canvasWidth;
|
|
190
|
+
const scaleY = containerHeight / canvasHeight;
|
|
191
|
+
return Math.min(scaleX, scaleY);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const backgroundStyle = $derived.by(() => {
|
|
195
|
+
if (!currentSlide) return 'background: transparent';
|
|
196
|
+
return getBackgroundStyle(currentSlide.canvas.background);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const allElementIds = $derived.by(() => {
|
|
200
|
+
const ids = new Set<string>();
|
|
201
|
+
slides.forEach(slide => slide.canvas.elements.forEach(el => ids.add(el.id)));
|
|
202
|
+
return ids;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const sortedElementIds = $derived.by(() => {
|
|
206
|
+
const elements: Array<{id: string, zIndex: number}> = [];
|
|
207
|
+
for (const id of allElementIds) {
|
|
208
|
+
const el = elementContent.get(id);
|
|
209
|
+
if (el) elements.push({ id, zIndex: el.zIndex ?? 0 });
|
|
210
|
+
}
|
|
211
|
+
elements.sort((a, b) => a.zIndex - b.zIndex);
|
|
212
|
+
return elements.map(e => e.id);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
function getElementInSlide(slide: Slide | null, elementId: string): CanvasElement | undefined {
|
|
216
|
+
return slide?.canvas.elements.find(el => el.id === elementId);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Typewriter
|
|
220
|
+
function startTypewriterAnimation(elementId: string, fullText: string, speed: number) {
|
|
221
|
+
const existing = typewriterIntervals.get(elementId);
|
|
222
|
+
if (existing) { clearInterval(existing); typewriterIntervals.delete(elementId); }
|
|
223
|
+
textTypewriterState.set(elementId, { fullText, displayedChars: 0, isAnimating: true });
|
|
224
|
+
textTypewriterState = new Map(textTypewriterState);
|
|
225
|
+
const intervalMs = 1000 / speed;
|
|
226
|
+
const interval = setInterval(() => {
|
|
227
|
+
const state = textTypewriterState.get(elementId);
|
|
228
|
+
if (state && state.isAnimating) {
|
|
229
|
+
if (state.displayedChars < state.fullText.length) {
|
|
230
|
+
textTypewriterState.set(elementId, { ...state, displayedChars: state.displayedChars + 1 });
|
|
231
|
+
textTypewriterState = new Map(textTypewriterState);
|
|
232
|
+
} else {
|
|
233
|
+
clearInterval(interval); typewriterIntervals.delete(elementId);
|
|
234
|
+
textTypewriterState.set(elementId, { ...state, isAnimating: false });
|
|
235
|
+
textTypewriterState = new Map(textTypewriterState);
|
|
236
|
+
}
|
|
237
|
+
} else { clearInterval(interval); typewriterIntervals.delete(elementId); }
|
|
238
|
+
}, intervalMs);
|
|
239
|
+
typewriterIntervals.set(elementId, interval);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function clearAllTypewriterAnimations() {
|
|
243
|
+
for (const [, interval] of typewriterIntervals) clearInterval(interval);
|
|
244
|
+
typewriterIntervals.clear();
|
|
245
|
+
textTypewriterState.clear();
|
|
246
|
+
textTypewriterState = new Map(textTypewriterState);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Build SVG path for 3+ control points using Catmull-Rom spline
|
|
250
|
+
function buildCatmullRomPath(start: {x:number,y:number}, cps: {x:number,y:number}[], end: {x:number,y:number}): string {
|
|
251
|
+
const pts = [start, ...cps, end];
|
|
252
|
+
let d = `M ${pts[0].x} ${pts[0].y}`;
|
|
253
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
254
|
+
const p0 = pts[i === 0 ? 0 : i - 1];
|
|
255
|
+
const p1 = pts[i];
|
|
256
|
+
const p2 = pts[i + 1];
|
|
257
|
+
const p3 = pts[i + 2 < pts.length ? i + 2 : pts.length - 1];
|
|
258
|
+
const c1x = p1.x + (p2.x - p0.x) / 6;
|
|
259
|
+
const c1y = p1.y + (p2.y - p0.y) / 6;
|
|
260
|
+
const c2x = p2.x - (p3.x - p1.x) / 6;
|
|
261
|
+
const c2y = p2.y - (p3.y - p1.y) / 6;
|
|
262
|
+
d += ` C ${c1x} ${c1y} ${c2x} ${c2y} ${p2.x} ${p2.y}`;
|
|
263
|
+
}
|
|
264
|
+
return d;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Arrow draw/undraw/draw-undraw animation action
|
|
268
|
+
function animateStyledArrowDraw(node: SVGPathElement, params: { enabled: boolean; mode: string; duration: number; dashPattern: string; startX: number; endX: number; slideIndex: number; loop?: boolean; reverse?: boolean }) {
|
|
269
|
+
let lastSlideIndex = params.slideIndex;
|
|
270
|
+
let animationId: number | null = null;
|
|
271
|
+
function runAnimation() {
|
|
272
|
+
if (!params.enabled) return;
|
|
273
|
+
if (animationId) cancelAnimationFrame(animationId);
|
|
274
|
+
const svg = node.closest('svg') as SVGSVGElement | null;
|
|
275
|
+
if (!svg) return;
|
|
276
|
+
const baseLeftToRight = params.endX >= params.startX;
|
|
277
|
+
const goesLeftToRight = params.reverse ? !baseLeftToRight : baseLeftToRight;
|
|
278
|
+
const mode = params.mode;
|
|
279
|
+
const dur = params.duration;
|
|
280
|
+
const startTime = performance.now();
|
|
281
|
+
if (mode === 'draw' || mode === 'draw-undraw') {
|
|
282
|
+
svg.style.clipPath = goesLeftToRight ? 'inset(0 100% 0 0)' : 'inset(0 0 0 100%)';
|
|
283
|
+
} else if (mode === 'undraw') {
|
|
284
|
+
svg.style.clipPath = 'none';
|
|
285
|
+
}
|
|
286
|
+
function animate(currentTime: number) {
|
|
287
|
+
const elapsed = currentTime - startTime;
|
|
288
|
+
if (mode === 'draw') {
|
|
289
|
+
const progress = Math.min(elapsed / dur, 1);
|
|
290
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
291
|
+
const inset = 100 * (1 - eased);
|
|
292
|
+
svg!.style.clipPath = goesLeftToRight ? `inset(0 ${inset}% 0 0)` : `inset(0 0 0 ${inset}%)`;
|
|
293
|
+
if (progress < 1) { animationId = requestAnimationFrame(animate); }
|
|
294
|
+
else if (params.loop) { runAnimation(); }
|
|
295
|
+
else { svg!.style.clipPath = 'none'; animationId = null; }
|
|
296
|
+
} else if (mode === 'undraw') {
|
|
297
|
+
const progress = Math.min(elapsed / dur, 1);
|
|
298
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
299
|
+
const inset = 100 * eased;
|
|
300
|
+
svg!.style.clipPath = goesLeftToRight ? `inset(0 0 0 ${inset}%)` : `inset(0 ${inset}% 0 0)`;
|
|
301
|
+
if (progress < 1) { animationId = requestAnimationFrame(animate); }
|
|
302
|
+
else if (params.loop) { runAnimation(); }
|
|
303
|
+
else { svg!.style.clipPath = 'inset(0 0 0 100%)'; animationId = null; }
|
|
304
|
+
} else if (mode === 'draw-undraw') {
|
|
305
|
+
const halfDur = dur / 2;
|
|
306
|
+
if (elapsed < halfDur) {
|
|
307
|
+
const progress = Math.min(elapsed / halfDur, 1);
|
|
308
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
309
|
+
const inset = 100 * (1 - eased);
|
|
310
|
+
svg!.style.clipPath = goesLeftToRight ? `inset(0 ${inset}% 0 0)` : `inset(0 0 0 ${inset}%)`;
|
|
311
|
+
animationId = requestAnimationFrame(animate);
|
|
312
|
+
} else {
|
|
313
|
+
const progress = Math.min((elapsed - halfDur) / halfDur, 1);
|
|
314
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
315
|
+
const inset = 100 * eased;
|
|
316
|
+
svg!.style.clipPath = goesLeftToRight ? `inset(0 0 0 ${inset}%)` : `inset(0 ${inset}% 0 0)`;
|
|
317
|
+
if (progress < 1) { animationId = requestAnimationFrame(animate); }
|
|
318
|
+
else if (params.loop) { runAnimation(); }
|
|
319
|
+
else { svg!.style.clipPath = 'inset(0 0 0 100%)'; animationId = null; }
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
animationId = requestAnimationFrame(animate);
|
|
324
|
+
}
|
|
325
|
+
runAnimation();
|
|
326
|
+
return {
|
|
327
|
+
update(newParams: typeof params) {
|
|
328
|
+
const slideChanged = newParams.slideIndex !== lastSlideIndex;
|
|
329
|
+
params = newParams;
|
|
330
|
+
if (!params.enabled) {
|
|
331
|
+
if (animationId) { cancelAnimationFrame(animationId); animationId = null; }
|
|
332
|
+
const svg = node.closest('svg') as SVGSVGElement | null;
|
|
333
|
+
if (svg) svg.style.clipPath = '';
|
|
334
|
+
lastSlideIndex = newParams.slideIndex;
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (slideChanged) { lastSlideIndex = newParams.slideIndex; runAnimation(); }
|
|
338
|
+
},
|
|
339
|
+
destroy() { if (animationId) cancelAnimationFrame(animationId); }
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Init animated elements
|
|
344
|
+
function initAllAnimatedElements() {
|
|
345
|
+
const firstSlide = slides[0];
|
|
346
|
+
if (firstSlide) {
|
|
347
|
+
for (const element of firstSlide.canvas.elements) {
|
|
348
|
+
if (element.type === 'code') previousCodeContent.set(element.id, (element as CodeElement).code);
|
|
349
|
+
if (element.type === 'text') {
|
|
350
|
+
const textEl = element as TextElement;
|
|
351
|
+
if (textEl.animation?.mode === 'typewriter') startTypewriterAnimation(element.id, textEl.content, textEl.animation.typewriterSpeed || 50);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
for (const slide of slides) {
|
|
356
|
+
for (const element of slide.canvas.elements) {
|
|
357
|
+
if (!animatedElements.has(element.id)) {
|
|
358
|
+
const inCurrent = getElementInSlide(currentSlide, element.id);
|
|
359
|
+
const startOpacity = inCurrent ? ((inCurrent as any).opacity ?? 1) : 0;
|
|
360
|
+
const br = (element as any).borderRadius ?? 0;
|
|
361
|
+
const isShape = element.type === 'shape';
|
|
362
|
+
const shapeEl = isShape ? element as ShapeElement : null;
|
|
363
|
+
const isText = element.type === 'text';
|
|
364
|
+
const textEl = isText ? element as TextElement : null;
|
|
365
|
+
animatedElements.set(element.id, {
|
|
366
|
+
x: tween(element.position.x, { duration: 500 }),
|
|
367
|
+
y: tween(element.position.y, { duration: 500 }),
|
|
368
|
+
width: tween(element.size.width, { duration: 500 }),
|
|
369
|
+
height: tween(element.size.height, { duration: 500 }),
|
|
370
|
+
rotation: tween(element.rotation, { duration: 500 }),
|
|
371
|
+
skewX: tween(element.skewX ?? 0, { duration: 500 }),
|
|
372
|
+
skewY: tween(element.skewY ?? 0, { duration: 500 }),
|
|
373
|
+
tiltX: tween(element.tiltX ?? 0, { duration: 500 }),
|
|
374
|
+
tiltY: tween(element.tiltY ?? 0, { duration: 500 }),
|
|
375
|
+
perspective: tween(element.perspective ?? 1000, { duration: 500 }),
|
|
376
|
+
opacity: tween(startOpacity, { duration: 300 }),
|
|
377
|
+
borderRadius: tween(br, { duration: 500 }),
|
|
378
|
+
fontSize: textEl ? tween(textEl.fontSize, { duration: 500 }) : null,
|
|
379
|
+
fillColor: shapeEl ? tween(shapeEl.fillColor, { duration: 500 }) : null,
|
|
380
|
+
strokeColor: shapeEl ? tween(shapeEl.strokeColor, { duration: 500 }) : null,
|
|
381
|
+
strokeWidth: shapeEl ? tween(shapeEl.strokeWidth, { duration: 500 }) : null,
|
|
382
|
+
shapeMorph: shapeEl ? tween(1, { duration: 500 }) : null,
|
|
383
|
+
motionPathProgress: element.motionPathConfig ? tween(0, { duration: 500 }) : null,
|
|
384
|
+
blur: tween(element.blur ?? 0, { duration: 500 }),
|
|
385
|
+
brightness: tween(element.brightness ?? 100, { duration: 500 }),
|
|
386
|
+
contrast: tween(element.contrast ?? 100, { duration: 500 }),
|
|
387
|
+
saturate: tween(element.saturate ?? 100, { duration: 500 }),
|
|
388
|
+
grayscale: tween(element.grayscale ?? 0, { duration: 500 })
|
|
389
|
+
});
|
|
390
|
+
const currentSlideEl = getElementInSlide(currentSlide, element.id);
|
|
391
|
+
elementContent.set(element.id, JSON.parse(JSON.stringify(currentSlideEl || element)));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
animatedElements = new Map(animatedElements);
|
|
396
|
+
elementContent = new Map(elementContent);
|
|
397
|
+
previousCodeContent = new Map(previousCodeContent);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function animateMotionPaths(slide: Slide) {
|
|
401
|
+
cancelMotionPathLoops();
|
|
402
|
+
motionPathLoopAbort = new AbortController();
|
|
403
|
+
const signal = motionPathLoopAbort.signal;
|
|
404
|
+
|
|
405
|
+
const resets: Promise<void>[] = [];
|
|
406
|
+
for (const element of slide.canvas.elements) {
|
|
407
|
+
if (element.motionPathConfig) {
|
|
408
|
+
const animated = animatedElements.get(element.id);
|
|
409
|
+
if (animated?.motionPathProgress) {
|
|
410
|
+
resets.push(animated.motionPathProgress.to(0, { duration: 0 }));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
await Promise.all(resets);
|
|
415
|
+
for (const element of slide.canvas.elements) {
|
|
416
|
+
if (element.motionPathConfig) {
|
|
417
|
+
const animated = animatedElements.get(element.id);
|
|
418
|
+
if (animated?.motionPathProgress) {
|
|
419
|
+
const config = element.animationConfig;
|
|
420
|
+
const duration = config?.duration ?? 2000;
|
|
421
|
+
const easing = getEasingFn(config?.easing);
|
|
422
|
+
const shouldLoop = element.motionPathConfig.loop;
|
|
423
|
+
|
|
424
|
+
if (shouldLoop) {
|
|
425
|
+
const laps = element.motionPathConfig.laps ?? 0;
|
|
426
|
+
(async () => {
|
|
427
|
+
let lap = 0;
|
|
428
|
+
while (!signal.aborted && (laps === 0 || lap < laps)) {
|
|
429
|
+
await animated.motionPathProgress!.to(0, { duration: 0 });
|
|
430
|
+
await animated.motionPathProgress!.to(1, { duration, easing });
|
|
431
|
+
lap++;
|
|
432
|
+
if (!signal.aborted && (laps === 0 || lap < laps)) await new Promise(r => setTimeout(r, 50));
|
|
433
|
+
}
|
|
434
|
+
})();
|
|
435
|
+
} else {
|
|
436
|
+
animated.motionPathProgress.to(1, { duration, easing });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Reset presentation to first slide (snap all elements back to initial state)
|
|
444
|
+
async function resetToFirstSlide() {
|
|
445
|
+
if (isTransitioning) return;
|
|
446
|
+
isTransitioning = true;
|
|
447
|
+
clearAllTypewriterAnimations();
|
|
448
|
+
cancelMotionPathLoops();
|
|
449
|
+
const firstSlide = slides[0];
|
|
450
|
+
if (!firstSlide) { isTransitioning = false; return; }
|
|
451
|
+
|
|
452
|
+
for (const elementId of allElementIds) {
|
|
453
|
+
const targetEl = getElementInSlide(firstSlide, elementId);
|
|
454
|
+
const animated = animatedElements.get(elementId);
|
|
455
|
+
if (!animated) continue;
|
|
456
|
+
if (targetEl) {
|
|
457
|
+
animated.x.to(targetEl.position.x, { duration: 0 }); animated.y.to(targetEl.position.y, { duration: 0 });
|
|
458
|
+
animated.width.to(targetEl.size.width, { duration: 0 }); animated.height.to(targetEl.size.height, { duration: 0 });
|
|
459
|
+
animated.rotation.to(targetEl.rotation, { duration: 0 });
|
|
460
|
+
animated.skewX.to(targetEl.skewX ?? 0, { duration: 0 }); animated.skewY.to(targetEl.skewY ?? 0, { duration: 0 });
|
|
461
|
+
animated.tiltX.to(targetEl.tiltX ?? 0, { duration: 0 }); animated.tiltY.to(targetEl.tiltY ?? 0, { duration: 0 });
|
|
462
|
+
animated.perspective.to(targetEl.perspective ?? 1000, { duration: 0 });
|
|
463
|
+
animated.opacity.to((targetEl as any).opacity ?? 1, { duration: 0 });
|
|
464
|
+
animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: 0 });
|
|
465
|
+
animated.blur.to(targetEl.blur ?? 0, { duration: 0 });
|
|
466
|
+
animated.brightness.to(targetEl.brightness ?? 100, { duration: 0 });
|
|
467
|
+
animated.contrast.to(targetEl.contrast ?? 100, { duration: 0 });
|
|
468
|
+
animated.saturate.to(targetEl.saturate ?? 100, { duration: 0 });
|
|
469
|
+
animated.grayscale.to(targetEl.grayscale ?? 0, { duration: 0 });
|
|
470
|
+
if (targetEl.type === 'text' && animated.fontSize) animated.fontSize.to((targetEl as TextElement).fontSize, { duration: 0 });
|
|
471
|
+
if (targetEl.type === 'shape') {
|
|
472
|
+
const s = targetEl as ShapeElement;
|
|
473
|
+
if (animated.fillColor) animated.fillColor.to(s.fillColor, { duration: 0 });
|
|
474
|
+
if (animated.strokeColor) animated.strokeColor.to(s.strokeColor, { duration: 0 });
|
|
475
|
+
if (animated.strokeWidth) animated.strokeWidth.to(s.strokeWidth, { duration: 0 });
|
|
476
|
+
}
|
|
477
|
+
if (animated.motionPathProgress) animated.motionPathProgress.to(0, { duration: 0 });
|
|
478
|
+
} else {
|
|
479
|
+
animated.opacity.to(0, { duration: 0 });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
for (const elementId of allElementIds) {
|
|
484
|
+
const targetEl = getElementInSlide(firstSlide, elementId);
|
|
485
|
+
if (targetEl) elementContent.set(elementId, JSON.parse(JSON.stringify(targetEl)));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const newPreviousCodeContent = new Map<string, string>();
|
|
489
|
+
for (const element of firstSlide.canvas.elements) {
|
|
490
|
+
if (element.type === 'code') newPreviousCodeContent.set(element.id, (element as CodeElement).code);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
for (const element of firstSlide.canvas.elements) {
|
|
494
|
+
if (element.type === 'code') {
|
|
495
|
+
const codeEl = element as CodeElement;
|
|
496
|
+
const key = `${codeEl.id}-${codeEl.code}-${codeEl.language}-${codeEl.showLineNumbers}`;
|
|
497
|
+
if (!codeHighlights.has(key)) {
|
|
498
|
+
const html = await highlightCode(codeEl.code, codeEl.language, codeEl.theme, { showLineNumbers: codeEl.showLineNumbers });
|
|
499
|
+
codeHighlights.set(key, html);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
codeHighlights = new Map(codeHighlights);
|
|
504
|
+
|
|
505
|
+
codeMorphState = new Map();
|
|
506
|
+
previousCodeContent = newPreviousCodeContent;
|
|
507
|
+
shapeMorphStates = new Map();
|
|
508
|
+
elementContent = new Map(elementContent);
|
|
509
|
+
currentSlideIndex = 0;
|
|
510
|
+
isTransitioning = false;
|
|
511
|
+
|
|
512
|
+
for (const element of firstSlide.canvas.elements) {
|
|
513
|
+
if (element.type === 'text') {
|
|
514
|
+
const textEl = element as TextElement;
|
|
515
|
+
if (textEl.animation?.mode === 'typewriter') startTypewriterAnimation(element.id, textEl.content, textEl.animation.typewriterSpeed || 50);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
animateMotionPaths(firstSlide);
|
|
520
|
+
onslidechange?.(0, slides.length);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Animate to slide
|
|
524
|
+
async function animateToSlide(targetIndex: number) {
|
|
525
|
+
if (isTransitioning || targetIndex < 0 || targetIndex >= slides.length) return;
|
|
526
|
+
if (targetIndex === currentSlideIndex) return;
|
|
527
|
+
isTransitioning = true;
|
|
528
|
+
transitionDirection = targetIndex > currentSlideIndex ? 'forward' : 'backward';
|
|
529
|
+
const targetSlide = slides[targetIndex];
|
|
530
|
+
clearAllTypewriterAnimations();
|
|
531
|
+
cancelMotionPathLoops();
|
|
532
|
+
const transition = targetSlide.transition;
|
|
533
|
+
const duration = durationOverride ?? transition.duration;
|
|
534
|
+
transitionDurationMs = duration;
|
|
535
|
+
const hasSlideTransition = transition.type !== 'none';
|
|
536
|
+
|
|
537
|
+
if (hasSlideTransition) {
|
|
538
|
+
transitionClass = `transition-${transition.type}-out`;
|
|
539
|
+
await new Promise(r => setTimeout(r, duration * 0.4));
|
|
540
|
+
const newElementContent = new Map(elementContent);
|
|
541
|
+
const newCodeMorphState = new Map(codeMorphState);
|
|
542
|
+
const newPreviousCodeContent = new Map(previousCodeContent);
|
|
543
|
+
for (const elementId of allElementIds) {
|
|
544
|
+
const targetEl = getElementInSlide(targetSlide, elementId);
|
|
545
|
+
const animated = animatedElements.get(elementId);
|
|
546
|
+
if (targetEl) {
|
|
547
|
+
if (targetEl.type === 'code') {
|
|
548
|
+
const codeEl = targetEl as CodeElement;
|
|
549
|
+
const prevCode = newPreviousCodeContent.get(elementId) || '';
|
|
550
|
+
newCodeMorphState.set(elementId, { oldCode: prevCode, newCode: codeEl.code, mode: codeEl.animation?.mode || 'highlight-changes', speed: codeEl.animation?.typewriterSpeed || 50, highlightColor: codeEl.animation?.highlightColor || '#fef08a' });
|
|
551
|
+
newPreviousCodeContent.set(elementId, codeEl.code);
|
|
552
|
+
}
|
|
553
|
+
newElementContent.set(elementId, JSON.parse(JSON.stringify(targetEl)));
|
|
554
|
+
if (animated) {
|
|
555
|
+
animated.x.to(targetEl.position.x, { duration: 0 }); animated.y.to(targetEl.position.y, { duration: 0 });
|
|
556
|
+
animated.width.to(targetEl.size.width, { duration: 0 }); animated.height.to(targetEl.size.height, { duration: 0 });
|
|
557
|
+
animated.rotation.to(targetEl.rotation, { duration: 0 });
|
|
558
|
+
animated.skewX.to(targetEl.skewX ?? 0, { duration: 0 }); animated.skewY.to(targetEl.skewY ?? 0, { duration: 0 });
|
|
559
|
+
animated.tiltX.to(targetEl.tiltX ?? 0, { duration: 0 }); animated.tiltY.to(targetEl.tiltY ?? 0, { duration: 0 });
|
|
560
|
+
animated.perspective.to(targetEl.perspective ?? 1000, { duration: 0 });
|
|
561
|
+
animated.opacity.to((targetEl as any).opacity ?? 1, { duration: 0 });
|
|
562
|
+
animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: 0 });
|
|
563
|
+
animated.blur.to(targetEl.blur ?? 0, { duration: 0 });
|
|
564
|
+
animated.brightness.to(targetEl.brightness ?? 100, { duration: 0 });
|
|
565
|
+
animated.contrast.to(targetEl.contrast ?? 100, { duration: 0 });
|
|
566
|
+
animated.saturate.to(targetEl.saturate ?? 100, { duration: 0 });
|
|
567
|
+
animated.grayscale.to(targetEl.grayscale ?? 0, { duration: 0 });
|
|
568
|
+
if (targetEl.type === 'text') animated.fontSize?.to((targetEl as TextElement).fontSize, { duration: 0 });
|
|
569
|
+
if (targetEl.type === 'shape') {
|
|
570
|
+
const s = targetEl as ShapeElement;
|
|
571
|
+
animated.fillColor?.to(s.fillColor, { duration: 0 });
|
|
572
|
+
animated.strokeColor?.to(s.strokeColor, { duration: 0 });
|
|
573
|
+
animated.strokeWidth?.to(s.strokeWidth, { duration: 0 });
|
|
574
|
+
}
|
|
575
|
+
if (animated.motionPathProgress) animated.motionPathProgress.to(0, { duration: 0 });
|
|
576
|
+
}
|
|
577
|
+
} else if (animated) { animated.opacity.to(0, { duration: 0 }); }
|
|
578
|
+
}
|
|
579
|
+
for (const [, element] of newElementContent) {
|
|
580
|
+
if (element.type === 'code') {
|
|
581
|
+
const codeEl = element as CodeElement;
|
|
582
|
+
const key = `${codeEl.id}-${codeEl.code}-${codeEl.language}-${codeEl.showLineNumbers}`;
|
|
583
|
+
if (!codeHighlights.has(key)) {
|
|
584
|
+
const html = await highlightCode(codeEl.code, codeEl.language, codeEl.theme, { showLineNumbers: codeEl.showLineNumbers });
|
|
585
|
+
codeHighlights.set(key, html);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
codeHighlights = new Map(codeHighlights);
|
|
590
|
+
shapeMorphStates = new Map();
|
|
591
|
+
codeMorphState = newCodeMorphState;
|
|
592
|
+
previousCodeContent = newPreviousCodeContent;
|
|
593
|
+
elementContent = newElementContent;
|
|
594
|
+
animatedElements = new Map(animatedElements);
|
|
595
|
+
currentSlideIndex = targetIndex;
|
|
596
|
+
for (const elementId of allElementIds) {
|
|
597
|
+
const targetEl = getElementInSlide(targetSlide, elementId);
|
|
598
|
+
if (targetEl?.type === 'text') {
|
|
599
|
+
const textEl = targetEl as TextElement;
|
|
600
|
+
if (textEl.animation?.mode === 'typewriter') startTypewriterAnimation(elementId, textEl.content, textEl.animation.typewriterSpeed || 50);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
transitionClass = `transition-${transition.type}-in`;
|
|
604
|
+
await new Promise(r => setTimeout(r, duration * 0.6));
|
|
605
|
+
transitionClass = '';
|
|
606
|
+
animateMotionPaths(targetSlide);
|
|
607
|
+
isTransitioning = false;
|
|
608
|
+
onslidechange?.(targetIndex, slides.length);
|
|
609
|
+
if (targetIndex === slides.length - 1 && !loop) oncomplete?.();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Per-element morphing (transition type = 'none')
|
|
614
|
+
const animations: Promise<void>[] = [];
|
|
615
|
+
for (const elementId of allElementIds) {
|
|
616
|
+
const currentEl = getElementInSlide(currentSlide, elementId);
|
|
617
|
+
const animated = animatedElements.get(elementId);
|
|
618
|
+
if (!animated) continue;
|
|
619
|
+
if (currentEl) {
|
|
620
|
+
await animated.x.to(currentEl.position.x, { duration: 0 });
|
|
621
|
+
await animated.y.to(currentEl.position.y, { duration: 0 });
|
|
622
|
+
await animated.width.to(currentEl.size.width, { duration: 0 });
|
|
623
|
+
await animated.height.to(currentEl.size.height, { duration: 0 });
|
|
624
|
+
await animated.rotation.to(currentEl.rotation, { duration: 0 });
|
|
625
|
+
await animated.skewX.to(currentEl.skewX ?? 0, { duration: 0 });
|
|
626
|
+
await animated.skewY.to(currentEl.skewY ?? 0, { duration: 0 });
|
|
627
|
+
await animated.tiltX.to(currentEl.tiltX ?? 0, { duration: 0 });
|
|
628
|
+
await animated.tiltY.to(currentEl.tiltY ?? 0, { duration: 0 });
|
|
629
|
+
await animated.perspective.to(currentEl.perspective ?? 1000, { duration: 0 });
|
|
630
|
+
await animated.borderRadius.to((currentEl as any).borderRadius ?? 0, { duration: 0 });
|
|
631
|
+
await animated.blur.to(currentEl.blur ?? 0, { duration: 0 });
|
|
632
|
+
await animated.brightness.to(currentEl.brightness ?? 100, { duration: 0 });
|
|
633
|
+
await animated.contrast.to(currentEl.contrast ?? 100, { duration: 0 });
|
|
634
|
+
await animated.saturate.to(currentEl.saturate ?? 100, { duration: 0 });
|
|
635
|
+
await animated.grayscale.to(currentEl.grayscale ?? 0, { duration: 0 });
|
|
636
|
+
await animated.opacity.to((currentEl as any).opacity ?? 1, { duration: 0 });
|
|
637
|
+
if (currentEl.type === 'text' && animated.fontSize) await animated.fontSize.to((currentEl as TextElement).fontSize, { duration: 0 });
|
|
638
|
+
if (currentEl.type === 'shape') {
|
|
639
|
+
const s = currentEl as ShapeElement;
|
|
640
|
+
if (animated.fillColor) await animated.fillColor.to(s.fillColor, { duration: 0 });
|
|
641
|
+
if (animated.strokeColor) await animated.strokeColor.to(s.strokeColor, { duration: 0 });
|
|
642
|
+
if (animated.strokeWidth) await animated.strokeWidth.to(s.strokeWidth, { duration: 0 });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Update elementContent BEFORE animations start so rendered elements
|
|
648
|
+
// (especially SVG viewBox) use target slide data while animating
|
|
649
|
+
for (const elementId of allElementIds) {
|
|
650
|
+
const targetEl = getElementInSlide(targetSlide, elementId);
|
|
651
|
+
if (targetEl && targetEl.type !== 'code') {
|
|
652
|
+
elementContent.set(elementId, JSON.parse(JSON.stringify(targetEl)));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
elementContent = new Map(elementContent);
|
|
656
|
+
|
|
657
|
+
interface AnimationTask { elementId: string; order: number; delay: number; elementDuration: number; run: () => Promise<void>[]; }
|
|
658
|
+
const animationTasks: AnimationTask[] = [];
|
|
659
|
+
|
|
660
|
+
for (const elementId of allElementIds) {
|
|
661
|
+
const currentEl = getElementInSlide(currentSlide, elementId);
|
|
662
|
+
const targetEl = getElementInSlide(targetSlide, elementId);
|
|
663
|
+
const animated = animatedElements.get(elementId);
|
|
664
|
+
if (!animated) continue;
|
|
665
|
+
const animConfig = targetEl?.animationConfig || currentEl?.animationConfig;
|
|
666
|
+
const order = animConfig?.order ?? 0;
|
|
667
|
+
const delay = animConfig?.delay ?? 0;
|
|
668
|
+
const elementDuration = animConfig?.duration ?? duration;
|
|
669
|
+
|
|
670
|
+
if (targetEl) {
|
|
671
|
+
const easing = getEasingFn(animConfig?.easing);
|
|
672
|
+
const propertySequences = targetEl.animationConfig?.propertySequences;
|
|
673
|
+
if (targetEl.type === 'text') {
|
|
674
|
+
const textEl = targetEl as TextElement;
|
|
675
|
+
if (textEl.animation?.mode === 'typewriter') startTypewriterAnimation(elementId, textEl.content, textEl.animation.typewriterSpeed || 50);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const getSeqTiming = (prop: AnimatableProperty) => {
|
|
679
|
+
if (!propertySequences?.length) return { duration: elementDuration, delay: 0, order: 0 };
|
|
680
|
+
const seq = propertySequences.find(s => s.property === prop);
|
|
681
|
+
return seq ? { duration: seq.duration, delay: seq.delay, order: seq.order } : { duration: elementDuration, delay: 0, order: 99 };
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
animationTasks.push({
|
|
685
|
+
elementId, order, delay, elementDuration,
|
|
686
|
+
run: () => {
|
|
687
|
+
const anims: Promise<void>[] = [];
|
|
688
|
+
if (propertySequences?.length) {
|
|
689
|
+
const sequencedProps = new Set(propertySequences.map(s => s.property));
|
|
690
|
+
if (!sequencedProps.has('position')) { anims.push(animated.x.to(targetEl.position.x, { duration: elementDuration, easing })); anims.push(animated.y.to(targetEl.position.y, { duration: elementDuration, easing })); }
|
|
691
|
+
if (!sequencedProps.has('rotation')) anims.push(animated.rotation.to(targetEl.rotation, { duration: elementDuration, easing }));
|
|
692
|
+
if (!sequencedProps.has('tilt')) { anims.push(animated.tiltX.to(targetEl.tiltX ?? 0, { duration: elementDuration, easing })); anims.push(animated.tiltY.to(targetEl.tiltY ?? 0, { duration: elementDuration, easing })); }
|
|
693
|
+
if (!sequencedProps.has('skew')) { anims.push(animated.skewX.to(targetEl.skewX ?? 0, { duration: elementDuration, easing })); anims.push(animated.skewY.to(targetEl.skewY ?? 0, { duration: elementDuration, easing })); }
|
|
694
|
+
if (!sequencedProps.has('size')) { anims.push(animated.width.to(targetEl.size.width, { duration: elementDuration, easing })); anims.push(animated.height.to(targetEl.size.height, { duration: elementDuration, easing })); }
|
|
695
|
+
if (!sequencedProps.has('borderRadius')) anims.push(animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: elementDuration, easing }));
|
|
696
|
+
if (!sequencedProps.has('blur')) {
|
|
697
|
+
animated.blur.to(targetEl.blur ?? 0, { duration: elementDuration, easing });
|
|
698
|
+
animated.brightness.to(targetEl.brightness ?? 100, { duration: elementDuration, easing });
|
|
699
|
+
animated.contrast.to(targetEl.contrast ?? 100, { duration: elementDuration, easing });
|
|
700
|
+
animated.saturate.to(targetEl.saturate ?? 100, { duration: elementDuration, easing });
|
|
701
|
+
animated.grayscale.to(targetEl.grayscale ?? 0, { duration: elementDuration, easing });
|
|
702
|
+
}
|
|
703
|
+
if (!sequencedProps.has('perspective')) anims.push(animated.perspective.to(targetEl.perspective ?? 1000, { duration: elementDuration, easing }));
|
|
704
|
+
if (!sequencedProps.has('opacity')) {
|
|
705
|
+
const targetOpacity = (targetEl as any).opacity ?? 1;
|
|
706
|
+
if (animated.opacity.current !== targetOpacity) anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration, easing }));
|
|
707
|
+
}
|
|
708
|
+
const sortedSeqs = [...propertySequences].sort((a, b) => a.order - b.order);
|
|
709
|
+
let cumulativeDelay = 0;
|
|
710
|
+
for (const seq of sortedSeqs) {
|
|
711
|
+
const seqDelay = cumulativeDelay + seq.delay;
|
|
712
|
+
const seqDuration = seq.duration;
|
|
713
|
+
setTimeout(() => {
|
|
714
|
+
if (seq.property === 'position') { animated.x.to(targetEl.position.x, { duration: seqDuration, easing }); animated.y.to(targetEl.position.y, { duration: seqDuration, easing }); }
|
|
715
|
+
else if (seq.property === 'rotation') animated.rotation.to(targetEl.rotation, { duration: seqDuration, easing });
|
|
716
|
+
else if (seq.property === 'tilt') { animated.tiltX.to(targetEl.tiltX ?? 0, { duration: seqDuration, easing }); animated.tiltY.to(targetEl.tiltY ?? 0, { duration: seqDuration, easing }); }
|
|
717
|
+
else if (seq.property === 'skew') { animated.skewX.to(targetEl.skewX ?? 0, { duration: seqDuration, easing }); animated.skewY.to(targetEl.skewY ?? 0, { duration: seqDuration, easing }); }
|
|
718
|
+
else if (seq.property === 'size') { animated.width.to(targetEl.size.width, { duration: seqDuration, easing }); animated.height.to(targetEl.size.height, { duration: seqDuration, easing }); }
|
|
719
|
+
else if (seq.property === 'borderRadius') animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: seqDuration, easing });
|
|
720
|
+
else if (seq.property === 'blur') {
|
|
721
|
+
animated.blur.to(targetEl.blur ?? 0, { duration: seqDuration, easing });
|
|
722
|
+
animated.brightness.to(targetEl.brightness ?? 100, { duration: seqDuration, easing });
|
|
723
|
+
animated.contrast.to(targetEl.contrast ?? 100, { duration: seqDuration, easing });
|
|
724
|
+
animated.saturate.to(targetEl.saturate ?? 100, { duration: seqDuration, easing });
|
|
725
|
+
animated.grayscale.to(targetEl.grayscale ?? 0, { duration: seqDuration, easing });
|
|
726
|
+
}
|
|
727
|
+
else if (seq.property === 'color' && targetEl.type === 'shape') {
|
|
728
|
+
const s = targetEl as ShapeElement;
|
|
729
|
+
animated.fillColor?.to(s.fillColor, { duration: seqDuration, easing });
|
|
730
|
+
animated.strokeColor?.to(s.strokeColor, { duration: seqDuration, easing });
|
|
731
|
+
animated.strokeWidth?.to(s.strokeWidth, { duration: seqDuration, easing });
|
|
732
|
+
}
|
|
733
|
+
else if (seq.property === 'perspective') animated.perspective.to(targetEl.perspective ?? 1000, { duration: seqDuration, easing });
|
|
734
|
+
else if (seq.property === 'opacity') animated.opacity.to((targetEl as any).opacity ?? 1, { duration: seqDuration, easing });
|
|
735
|
+
}, seqDelay);
|
|
736
|
+
cumulativeDelay = seqDelay + seqDuration;
|
|
737
|
+
}
|
|
738
|
+
anims.push(new Promise(r => setTimeout(r, cumulativeDelay)));
|
|
739
|
+
} else {
|
|
740
|
+
anims.push(animated.x.to(targetEl.position.x, { duration: elementDuration, easing }));
|
|
741
|
+
anims.push(animated.y.to(targetEl.position.y, { duration: elementDuration, easing }));
|
|
742
|
+
anims.push(animated.width.to(targetEl.size.width, { duration: elementDuration, easing }));
|
|
743
|
+
anims.push(animated.height.to(targetEl.size.height, { duration: elementDuration, easing }));
|
|
744
|
+
anims.push(animated.rotation.to(targetEl.rotation, { duration: elementDuration, easing }));
|
|
745
|
+
anims.push(animated.skewX.to(targetEl.skewX ?? 0, { duration: elementDuration, easing }));
|
|
746
|
+
anims.push(animated.skewY.to(targetEl.skewY ?? 0, { duration: elementDuration, easing }));
|
|
747
|
+
anims.push(animated.tiltX.to(targetEl.tiltX ?? 0, { duration: elementDuration, easing }));
|
|
748
|
+
anims.push(animated.tiltY.to(targetEl.tiltY ?? 0, { duration: elementDuration, easing }));
|
|
749
|
+
anims.push(animated.perspective.to(targetEl.perspective ?? 1000, { duration: elementDuration, easing }));
|
|
750
|
+
anims.push(animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: elementDuration, easing }));
|
|
751
|
+
anims.push(animated.blur.to(targetEl.blur ?? 0, { duration: elementDuration, easing }));
|
|
752
|
+
anims.push(animated.brightness.to(targetEl.brightness ?? 100, { duration: elementDuration, easing }));
|
|
753
|
+
anims.push(animated.contrast.to(targetEl.contrast ?? 100, { duration: elementDuration, easing }));
|
|
754
|
+
anims.push(animated.saturate.to(targetEl.saturate ?? 100, { duration: elementDuration, easing }));
|
|
755
|
+
anims.push(animated.grayscale.to(targetEl.grayscale ?? 0, { duration: elementDuration, easing }));
|
|
756
|
+
// Opacity interpolation for morphing elements
|
|
757
|
+
const currOpacity = animated.opacity.current;
|
|
758
|
+
const targetOpacity = (targetEl as any).opacity ?? 1;
|
|
759
|
+
if (currOpacity !== targetOpacity) {
|
|
760
|
+
anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration, easing }));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// Motion path progress — await reset, then animate forward
|
|
764
|
+
if (animated.motionPathProgress && targetEl.motionPathConfig) {
|
|
765
|
+
const shouldLoop = targetEl.motionPathConfig.loop;
|
|
766
|
+
if (!shouldLoop) {
|
|
767
|
+
anims.push((async () => {
|
|
768
|
+
await animated.motionPathProgress!.to(0, { duration: 0 });
|
|
769
|
+
await animated.motionPathProgress!.to(1, { duration: elementDuration, easing });
|
|
770
|
+
})());
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (targetEl.type === 'text' && animated.fontSize) anims.push(animated.fontSize.to((targetEl as TextElement).fontSize, { duration: elementDuration, easing }));
|
|
774
|
+
if (targetEl.type === 'shape' && currentEl?.type === 'shape') {
|
|
775
|
+
const ts = targetEl as ShapeElement;
|
|
776
|
+
const cs = currentEl as ShapeElement;
|
|
777
|
+
if (!propertySequences?.length) {
|
|
778
|
+
if (animated.fillColor) anims.push(animated.fillColor.to(ts.fillColor, { duration: elementDuration, easing }));
|
|
779
|
+
if (animated.strokeColor) anims.push(animated.strokeColor.to(ts.strokeColor, { duration: elementDuration, easing }));
|
|
780
|
+
if (animated.strokeWidth) anims.push(animated.strokeWidth.to(ts.strokeWidth, { duration: elementDuration, easing }));
|
|
781
|
+
}
|
|
782
|
+
if (cs.shapeType !== ts.shapeType && animated.shapeMorph) {
|
|
783
|
+
shapeMorphStates.set(elementId, { fromType: cs.shapeType, toType: ts.shapeType });
|
|
784
|
+
shapeMorphStates = new Map(shapeMorphStates);
|
|
785
|
+
anims.push(animated.shapeMorph.to(0, { duration: 0 }));
|
|
786
|
+
anims.push(animated.shapeMorph.to(1, { duration: elementDuration, easing }));
|
|
787
|
+
}
|
|
788
|
+
} else if (targetEl.type === 'shape' && !propertySequences?.length) {
|
|
789
|
+
const s = targetEl as ShapeElement;
|
|
790
|
+
if (animated.fillColor) anims.push(animated.fillColor.to(s.fillColor, { duration: elementDuration, easing }));
|
|
791
|
+
if (animated.strokeColor) anims.push(animated.strokeColor.to(s.strokeColor, { duration: elementDuration, easing }));
|
|
792
|
+
if (animated.strokeWidth) anims.push(animated.strokeWidth.to(s.strokeWidth, { duration: elementDuration, easing }));
|
|
793
|
+
}
|
|
794
|
+
if (!currentEl) {
|
|
795
|
+
// Snap ALL properties to target instantly — the tween may hold
|
|
796
|
+
// stale values from a previous slide where the element last appeared
|
|
797
|
+
anims.push(animated.x.to(targetEl.position.x, { duration: 0 }));
|
|
798
|
+
anims.push(animated.y.to(targetEl.position.y, { duration: 0 }));
|
|
799
|
+
anims.push(animated.width.to(targetEl.size.width, { duration: 0 }));
|
|
800
|
+
anims.push(animated.height.to(targetEl.size.height, { duration: 0 }));
|
|
801
|
+
anims.push(animated.rotation.to(targetEl.rotation, { duration: 0 }));
|
|
802
|
+
anims.push(animated.skewX.to(targetEl.skewX ?? 0, { duration: 0 }));
|
|
803
|
+
anims.push(animated.skewY.to(targetEl.skewY ?? 0, { duration: 0 }));
|
|
804
|
+
anims.push(animated.tiltX.to(targetEl.tiltX ?? 0, { duration: 0 }));
|
|
805
|
+
anims.push(animated.tiltY.to(targetEl.tiltY ?? 0, { duration: 0 }));
|
|
806
|
+
anims.push(animated.perspective.to(targetEl.perspective ?? 1000, { duration: 0 }));
|
|
807
|
+
anims.push(animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: 0 }));
|
|
808
|
+
anims.push(animated.blur.to(targetEl.blur ?? 0, { duration: 0 }));
|
|
809
|
+
anims.push(animated.brightness.to(targetEl.brightness ?? 100, { duration: 0 }));
|
|
810
|
+
anims.push(animated.contrast.to(targetEl.contrast ?? 100, { duration: 0 }));
|
|
811
|
+
anims.push(animated.saturate.to(targetEl.saturate ?? 100, { duration: 0 }));
|
|
812
|
+
anims.push(animated.grayscale.to(targetEl.grayscale ?? 0, { duration: 0 }));
|
|
813
|
+
if (targetEl.type === 'text' && animated.fontSize) {
|
|
814
|
+
anims.push(animated.fontSize.to((targetEl as TextElement).fontSize, { duration: 0 }));
|
|
815
|
+
}
|
|
816
|
+
if (targetEl.type === 'shape') {
|
|
817
|
+
const s = targetEl as ShapeElement;
|
|
818
|
+
if (animated.fillColor) anims.push(animated.fillColor.to(s.fillColor, { duration: 0 }));
|
|
819
|
+
if (animated.strokeColor) anims.push(animated.strokeColor.to(s.strokeColor, { duration: 0 }));
|
|
820
|
+
if (animated.strokeWidth) anims.push(animated.strokeWidth.to(s.strokeWidth, { duration: 0 }));
|
|
821
|
+
}
|
|
822
|
+
const entrance = targetEl.animationConfig?.entrance ?? 'fade';
|
|
823
|
+
const targetOpacity = (targetEl as any).opacity ?? 1;
|
|
824
|
+
if (entrance === 'fade') {
|
|
825
|
+
anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration / 2, easing }));
|
|
826
|
+
} else {
|
|
827
|
+
anims.push(animated.opacity.to(targetOpacity, { duration: 0 }));
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return anims;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
} else if (currentEl) {
|
|
834
|
+
const exit = currentEl.animationConfig?.exit ?? 'fade';
|
|
835
|
+
if (exit === 'fade') {
|
|
836
|
+
const fadeOutDuration = Math.min(elementDuration / 2, 300);
|
|
837
|
+
animationTasks.push({ elementId, order, delay, elementDuration, run: () => [animated.opacity.to(0, { duration: fadeOutDuration, easing: easeInOutCubic })] });
|
|
838
|
+
} else {
|
|
839
|
+
animationTasks.push({ elementId, order, delay: 0, elementDuration: 0, run: () => [animated.opacity.to(0, { duration: 0 })] });
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
animationTasks.sort((a, b) => a.order - b.order);
|
|
845
|
+
const orderGroups = new Map<number, AnimationTask[]>();
|
|
846
|
+
for (const task of animationTasks) {
|
|
847
|
+
if (!orderGroups.has(task.order)) orderGroups.set(task.order, []);
|
|
848
|
+
orderGroups.get(task.order)!.push(task);
|
|
849
|
+
}
|
|
850
|
+
const sortedOrders = [...orderGroups.keys()].sort((a, b) => a - b);
|
|
851
|
+
for (let orderIdx = 0; orderIdx < sortedOrders.length; orderIdx++) {
|
|
852
|
+
const order = sortedOrders[orderIdx];
|
|
853
|
+
const tasks = orderGroups.get(order)!;
|
|
854
|
+
const groupAnimations: Promise<void>[] = [];
|
|
855
|
+
for (const task of tasks) {
|
|
856
|
+
if (task.delay > 0) setTimeout(() => { task.run().forEach(p => animations.push(p)); }, task.delay);
|
|
857
|
+
else groupAnimations.push(...task.run());
|
|
858
|
+
}
|
|
859
|
+
animations.push(...groupAnimations);
|
|
860
|
+
if (orderIdx < sortedOrders.length - 1) {
|
|
861
|
+
const maxDur = Math.max(...tasks.map(t => t.elementDuration));
|
|
862
|
+
await new Promise(r => setTimeout(r, maxDur * 0.3));
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const newElementContent = new Map(elementContent);
|
|
867
|
+
const newCodeMorphState = new Map(codeMorphState);
|
|
868
|
+
const newPreviousCodeContent = new Map(previousCodeContent);
|
|
869
|
+
for (const elementId of allElementIds) {
|
|
870
|
+
const targetEl = getElementInSlide(targetSlide, elementId);
|
|
871
|
+
if (targetEl) {
|
|
872
|
+
if (targetEl.type === 'code') {
|
|
873
|
+
const codeEl = targetEl as CodeElement;
|
|
874
|
+
const prevCode = newPreviousCodeContent.get(elementId) || '';
|
|
875
|
+
newCodeMorphState.set(elementId, { oldCode: prevCode, newCode: codeEl.code, mode: codeEl.animation?.mode || 'highlight-changes', speed: codeEl.animation?.typewriterSpeed || 50, highlightColor: codeEl.animation?.highlightColor || '#fef08a' });
|
|
876
|
+
newPreviousCodeContent.set(elementId, codeEl.code);
|
|
877
|
+
}
|
|
878
|
+
newElementContent.set(elementId, JSON.parse(JSON.stringify(targetEl)));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
for (const [, element] of newElementContent) {
|
|
882
|
+
if (element.type === 'code') {
|
|
883
|
+
const codeEl = element as CodeElement;
|
|
884
|
+
const key = `${codeEl.id}-${codeEl.code}-${codeEl.language}-${codeEl.showLineNumbers}`;
|
|
885
|
+
if (!codeHighlights.has(key)) {
|
|
886
|
+
const html = await highlightCode(codeEl.code, codeEl.language, codeEl.theme, { showLineNumbers: codeEl.showLineNumbers });
|
|
887
|
+
codeHighlights.set(key, html);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
codeHighlights = new Map(codeHighlights);
|
|
892
|
+
shapeMorphStates = new Map();
|
|
893
|
+
codeMorphState = newCodeMorphState;
|
|
894
|
+
previousCodeContent = newPreviousCodeContent;
|
|
895
|
+
elementContent = newElementContent;
|
|
896
|
+
currentSlideIndex = targetIndex;
|
|
897
|
+
isTransitioning = false;
|
|
898
|
+
// Ensure elements not on the new slide are fully hidden
|
|
899
|
+
const newSlide = slides[targetIndex];
|
|
900
|
+
for (const elementId of allElementIds) {
|
|
901
|
+
const onSlide = getElementInSlide(newSlide, elementId);
|
|
902
|
+
const animated = animatedElements.get(elementId);
|
|
903
|
+
if (!onSlide && animated) { animated.opacity.to(0, { duration: 0 }); }
|
|
904
|
+
}
|
|
905
|
+
animateMotionPaths(slides[targetIndex]);
|
|
906
|
+
onslidechange?.(targetIndex, slides.length);
|
|
907
|
+
if (targetIndex === slides.length - 1 && !loop) oncomplete?.();
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Autoplay
|
|
911
|
+
function clearAutoplayTimer() { if (autoplayTimer) { clearTimeout(autoplayTimer); autoplayTimer = null; } }
|
|
912
|
+
function scheduleNextSlide() {
|
|
913
|
+
clearAutoplayTimer();
|
|
914
|
+
if (!isAutoplay) return;
|
|
915
|
+
const slideDuration = durationOverride ?? currentSlide?.duration ?? 3000;
|
|
916
|
+
autoplayTimer = setTimeout(() => {
|
|
917
|
+
if (currentSlideIndex < slides.length - 1) animateToSlide(currentSlideIndex + 1);
|
|
918
|
+
else if (loop) {
|
|
919
|
+
const loopMode = project?.settings?.loopMode ?? 'reset';
|
|
920
|
+
if (loopMode === 'transition') animateToSlide(0);
|
|
921
|
+
else resetToFirstSlide();
|
|
922
|
+
}
|
|
923
|
+
else isAutoplay = false;
|
|
924
|
+
}, slideDuration);
|
|
925
|
+
}
|
|
926
|
+
$effect(() => { if (isAutoplay && !isTransitioning) scheduleNextSlide(); });
|
|
927
|
+
$effect(() => () => clearAutoplayTimer());
|
|
928
|
+
|
|
929
|
+
function handleNextSlide() {
|
|
930
|
+
if (currentSlideIndex < slides.length - 1) {
|
|
931
|
+
animateToSlide(currentSlideIndex + 1);
|
|
932
|
+
} else if (loop) {
|
|
933
|
+
const loopMode = project?.settings?.loopMode ?? 'reset';
|
|
934
|
+
if (loopMode === 'transition') {
|
|
935
|
+
animateToSlide(0);
|
|
936
|
+
} else {
|
|
937
|
+
resetToFirstSlide();
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Keyboard
|
|
943
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
944
|
+
if (!keyboard) return;
|
|
945
|
+
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') { e.preventDefault(); handleNextSlide(); }
|
|
946
|
+
else if (e.key === 'ArrowLeft' || e.key === 'Backspace') { e.preventDefault(); animateToSlide(currentSlideIndex - 1); }
|
|
947
|
+
else if (e.key === 'Home') animateToSlide(0);
|
|
948
|
+
else if (e.key === 'End') animateToSlide(slides.length - 1);
|
|
949
|
+
else if (e.key === 'p' || e.key === 'P') { isAutoplay = !isAutoplay; if (!isAutoplay) clearAutoplayTimer(); }
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function resetMouseIdleTimer() {
|
|
953
|
+
menuVisible = true;
|
|
954
|
+
if (mouseIdleTimer) clearTimeout(mouseIdleTimer);
|
|
955
|
+
mouseIdleTimer = setTimeout(() => { menuVisible = false; }, 3000);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Code highlight helpers
|
|
959
|
+
async function loadCodeHighlights() {
|
|
960
|
+
for (const slide of slides) {
|
|
961
|
+
for (const element of slide.canvas.elements) {
|
|
962
|
+
if (element.type === 'code') {
|
|
963
|
+
const codeEl = element as CodeElement;
|
|
964
|
+
const key = `${codeEl.id}-${codeEl.code}-${codeEl.language}-${codeEl.showLineNumbers}`;
|
|
965
|
+
if (!codeHighlights.has(key)) {
|
|
966
|
+
const html = await highlightCode(codeEl.code, codeEl.language, codeEl.theme, { showLineNumbers: codeEl.showLineNumbers });
|
|
967
|
+
codeHighlights.set(key, html);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
codeHighlights = new Map(codeHighlights);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function getCodeHighlight(elementId: string): string {
|
|
976
|
+
const slideElement = getElementInSlide(currentSlide, elementId);
|
|
977
|
+
if (!slideElement || slideElement.type !== 'code') return '';
|
|
978
|
+
const codeEl = slideElement as CodeElement;
|
|
979
|
+
const key = `${codeEl.id}-${codeEl.code}-${codeEl.language}-${codeEl.showLineNumbers}`;
|
|
980
|
+
const cached = codeHighlights.get(key);
|
|
981
|
+
if (cached) return cached;
|
|
982
|
+
highlightCode(codeEl.code, codeEl.language, codeEl.theme, { showLineNumbers: codeEl.showLineNumbers }).then(html => {
|
|
983
|
+
codeHighlights.set(key, html);
|
|
984
|
+
codeHighlights = new Map(codeHighlights);
|
|
985
|
+
});
|
|
986
|
+
return '';
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Public API (exposed via bind:this)
|
|
990
|
+
export async function goto(slideIndex: number) { await animateToSlide(slideIndex); }
|
|
991
|
+
export async function next() { handleNextSlide(); }
|
|
992
|
+
export async function prev() { await animateToSlide(currentSlideIndex - 1); }
|
|
993
|
+
export function play() { isAutoplay = true; }
|
|
994
|
+
export function pause() { isAutoplay = false; clearAutoplayTimer(); }
|
|
995
|
+
export function getCurrentSlide() { return currentSlideIndex; }
|
|
996
|
+
export function getTotalSlides() { return slides.length; }
|
|
997
|
+
export function getIsPlaying() { return isAutoplay; }
|
|
998
|
+
|
|
999
|
+
// Auto-load Google Fonts used by text elements in the project.
|
|
1000
|
+
// Generic CSS font families that don't need loading
|
|
1001
|
+
const GENERIC_FONTS = new Set([
|
|
1002
|
+
'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy',
|
|
1003
|
+
'system-ui', 'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded',
|
|
1004
|
+
'math', 'emoji', 'fangsong', 'inherit', 'initial', 'unset'
|
|
1005
|
+
]);
|
|
1006
|
+
|
|
1007
|
+
// Extract individual font names from a CSS font-family string.
|
|
1008
|
+
// e.g. '"JetBrains Mono", system-ui, monospace' → ['JetBrains Mono']
|
|
1009
|
+
function extractFontNames(fontFamily: string): string[] {
|
|
1010
|
+
return fontFamily
|
|
1011
|
+
.split(',')
|
|
1012
|
+
.map(f => f.trim().replace(/^['"]|['"]$/g, ''))
|
|
1013
|
+
.filter(f => f && !GENERIC_FONTS.has(f.toLowerCase()));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Auto-load fonts used by text/counter elements.
|
|
1017
|
+
// Uses fontsource CDN (jsDelivr) which registers the SAME font-family names
|
|
1018
|
+
// as the app (e.g. "Plus Jakarta Sans Variable"), unlike Google Fonts which
|
|
1019
|
+
// strips the "Variable" suffix.
|
|
1020
|
+
function loadProjectFonts(proj: AnimotProject) {
|
|
1021
|
+
const fonts = new Set<string>();
|
|
1022
|
+
for (const slide of proj.slides) {
|
|
1023
|
+
for (const el of slide.canvas.elements) {
|
|
1024
|
+
if (el.type === 'text' || el.type === 'counter') {
|
|
1025
|
+
const f = (el as any).fontFamily as string | undefined;
|
|
1026
|
+
if (f) {
|
|
1027
|
+
for (const name of extractFontNames(f)) fonts.add(name);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (fonts.size === 0) return;
|
|
1033
|
+
|
|
1034
|
+
// Deduplicate against already-injected links to avoid double-loading
|
|
1035
|
+
const loaded = new Set<string>();
|
|
1036
|
+
document.querySelectorAll<HTMLLinkElement>('link[data-animot-font]').forEach(l => loaded.add(l.dataset.animotFont!));
|
|
1037
|
+
|
|
1038
|
+
for (const font of fonts) {
|
|
1039
|
+
if (loaded.has(font)) continue;
|
|
1040
|
+
const isVariable = /\s+Variable$/i.test(font);
|
|
1041
|
+
// Convert font name to fontsource package slug:
|
|
1042
|
+
// "Plus Jakarta Sans Variable" → "plus-jakarta-sans"
|
|
1043
|
+
// "JetBrains Mono" → "jetbrains-mono"
|
|
1044
|
+
const baseName = font.replace(/\s*Variable$/i, '');
|
|
1045
|
+
const slug = baseName.toLowerCase().replace(/\s+/g, '-');
|
|
1046
|
+
const pkg = isVariable
|
|
1047
|
+
? `@fontsource-variable/${slug}`
|
|
1048
|
+
: `@fontsource/${slug}`;
|
|
1049
|
+
const link = document.createElement('link');
|
|
1050
|
+
link.rel = 'stylesheet';
|
|
1051
|
+
link.href = `https://cdn.jsdelivr.net/npm/${pkg}/index.css`;
|
|
1052
|
+
link.dataset.animotFont = font;
|
|
1053
|
+
document.head.appendChild(link);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Load data
|
|
1058
|
+
async function loadProject() {
|
|
1059
|
+
loading = true; error = null;
|
|
1060
|
+
try {
|
|
1061
|
+
if (data) { project = data; }
|
|
1062
|
+
else if (src) {
|
|
1063
|
+
const res = await fetch(src);
|
|
1064
|
+
if (!res.ok) throw new Error(`Failed to load: ${res.status}`);
|
|
1065
|
+
project = await res.json();
|
|
1066
|
+
} else { throw new Error('Either src or data prop is required'); }
|
|
1067
|
+
loadProjectFonts(project!);
|
|
1068
|
+
currentSlideIndex = startSlide;
|
|
1069
|
+
await new Promise(r => setTimeout(r, 10));
|
|
1070
|
+
initAllAnimatedElements();
|
|
1071
|
+
await loadCodeHighlights();
|
|
1072
|
+
loading = false;
|
|
1073
|
+
if (currentSlide) setTimeout(() => animateMotionPaths(currentSlide!), 300);
|
|
1074
|
+
if (autoplay) isAutoplay = true;
|
|
1075
|
+
} catch (e: any) { error = e.message; loading = false; }
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// ResizeObserver
|
|
1079
|
+
let resizeObserver: ResizeObserver;
|
|
1080
|
+
|
|
1081
|
+
onMount(() => {
|
|
1082
|
+
loadProject();
|
|
1083
|
+
resizeObserver = new ResizeObserver(entries => {
|
|
1084
|
+
for (const entry of entries) {
|
|
1085
|
+
containerWidth = entry.contentRect.width;
|
|
1086
|
+
containerHeight = entry.contentRect.height;
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
if (containerEl) resizeObserver.observe(containerEl);
|
|
1090
|
+
resetMouseIdleTimer();
|
|
1091
|
+
return () => { resizeObserver?.disconnect(); clearAutoplayTimer(); clearAllTypewriterAnimations(); if (mouseIdleTimer) clearTimeout(mouseIdleTimer); };
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
// Watch for prop changes
|
|
1095
|
+
$effect(() => { if (data) { project = data; } });
|
|
1096
|
+
</script>
|
|
1097
|
+
|
|
1098
|
+
<svelte:window onkeydown={handleKeyDown} />
|
|
1099
|
+
|
|
1100
|
+
<div
|
|
1101
|
+
class="animot-presenter {className}"
|
|
1102
|
+
class:animot-menu-visible={menuVisible}
|
|
1103
|
+
bind:this={containerEl}
|
|
1104
|
+
onmousemove={resetMouseIdleTimer}
|
|
1105
|
+
role="region"
|
|
1106
|
+
aria-label="Animot Presentation"
|
|
1107
|
+
>
|
|
1108
|
+
{#if loading}
|
|
1109
|
+
<div class="animot-loading"><div class="animot-spinner"></div></div>
|
|
1110
|
+
{:else if error}
|
|
1111
|
+
<div class="animot-error">{error}</div>
|
|
1112
|
+
{:else if project && currentSlide}
|
|
1113
|
+
<div class="animot-canvas-wrapper" style:transform="scale({presentationScale})">
|
|
1114
|
+
<div
|
|
1115
|
+
class="animot-canvas {transitionClass}"
|
|
1116
|
+
class:forward={transitionDirection === 'forward'}
|
|
1117
|
+
class:backward={transitionDirection === 'backward'}
|
|
1118
|
+
style:width="{canvasWidth}px"
|
|
1119
|
+
style:height="{canvasHeight}px"
|
|
1120
|
+
style:--transition-duration="{transitionDurationMs}ms"
|
|
1121
|
+
style={backgroundStyle}
|
|
1122
|
+
>
|
|
1123
|
+
{#if currentSlide.canvas.background.particles?.enabled}
|
|
1124
|
+
<ParticlesBackground config={currentSlide.canvas.background.particles} width={canvasWidth} height={canvasHeight} />
|
|
1125
|
+
{/if}
|
|
1126
|
+
{#if currentSlide.canvas.background.confetti?.enabled}
|
|
1127
|
+
<ConfettiEffect config={currentSlide.canvas.background.confetti} width={canvasWidth} height={canvasHeight} />
|
|
1128
|
+
{/if}
|
|
1129
|
+
|
|
1130
|
+
{#each sortedElementIds as elementId}
|
|
1131
|
+
{@const element = elementContent.get(elementId)}
|
|
1132
|
+
{@const animated = animatedElements.get(elementId)}
|
|
1133
|
+
{@const floatCfg = element?.floatingAnimation}
|
|
1134
|
+
{@const hasFloat = floatCfg?.enabled}
|
|
1135
|
+
{@const floatGroupId = element?.groupId}
|
|
1136
|
+
{@const mpConfig = element?.motionPathConfig}
|
|
1137
|
+
{@const mpElement = mpConfig ? currentSlide?.canvas.elements.find(el => el.id === mpConfig.motionPathId) as MotionPathElement | undefined : undefined}
|
|
1138
|
+
{@const mpProgress = animated?.motionPathProgress?.current ?? 0}
|
|
1139
|
+
{@const mpPoint = mpElement && mpConfig ? getPresenterPointOnPath(mpElement.points, mpElement.closed, (mpConfig.startPercent + (mpConfig.endPercent - mpConfig.startPercent) * mpProgress) / 100) : null}
|
|
1140
|
+
{@const mpStartPoint = mpElement && mpConfig ? getPresenterPointOnPath(mpElement.points, mpElement.closed, mpConfig.startPercent / 100) : null}
|
|
1141
|
+
{@const mpPos = mpPoint && mpStartPoint && animated && mpElement
|
|
1142
|
+
? computeMotionPathPosition(mpPoint, mpStartPoint,
|
|
1143
|
+
animated.x.current, animated.y.current,
|
|
1144
|
+
animated.width.current, animated.height.current,
|
|
1145
|
+
mpElement.closed)
|
|
1146
|
+
: null}
|
|
1147
|
+
{@const elemX = mpPos ? mpPos.x : (animated?.x.current ?? 0)}
|
|
1148
|
+
{@const elemY = mpPos ? mpPos.y : (animated?.y.current ?? 0)}
|
|
1149
|
+
{@const mpRotation = mpPoint && mpConfig?.autoRotate
|
|
1150
|
+
? mpPoint.angle + (mpConfig.orientationOffset ?? 0)
|
|
1151
|
+
: null}
|
|
1152
|
+
{#if element && animated && animated.opacity.current > 0.01 && element.visible !== false && !(element.type === 'motionPath' && !(element as MotionPathElement).showInPresentation)}
|
|
1153
|
+
<div
|
|
1154
|
+
class="animot-element"
|
|
1155
|
+
class:floating={hasFloat}
|
|
1156
|
+
style:left="{elemX}px"
|
|
1157
|
+
style:top="{elemY}px"
|
|
1158
|
+
style:width="{animated.width.current}px"
|
|
1159
|
+
style:height="{animated.height.current}px"
|
|
1160
|
+
style:opacity={animated.opacity.current}
|
|
1161
|
+
style:transform="perspective({animated.perspective.current}px) rotateX({animated.tiltX.current}deg) rotateY({animated.tiltY.current}deg) rotate({mpRotation ?? animated.rotation.current}deg) skewX({animated.skewX.current}deg) skewY({animated.skewY.current}deg)"
|
|
1162
|
+
style:transform-origin={element.tiltOrigin ?? 'center'}
|
|
1163
|
+
style:backface-visibility={element.backfaceVisibility ?? 'visible'}
|
|
1164
|
+
style:z-index={element.zIndex}
|
|
1165
|
+
style:--float-amp="{hasFloat ? computeFloatAmp(floatCfg, floatGroupId || elementId) : 10}px"
|
|
1166
|
+
style:--float-speed="{hasFloat ? computeFloatSpeed(floatCfg, floatGroupId || elementId) : 3}s"
|
|
1167
|
+
style:--float-delay="{hashFraction(floatGroupId || elementId, 3) * 2}s"
|
|
1168
|
+
style:animation-name={hasFloat ? getFloatAnimName(floatCfg!.direction, floatGroupId || elementId) : 'none'}
|
|
1169
|
+
style:filter={(() => { const parts: string[] = []; const b = animated.blur.current; const br2 = animated.brightness.current; const c = animated.contrast.current; const s = animated.saturate.current; const g = animated.grayscale.current; if (b) parts.push(`blur(${b}px)`); if (br2 !== 100) parts.push(`brightness(${br2}%)`); if (c !== 100) parts.push(`contrast(${c}%)`); if (s !== 100) parts.push(`saturate(${s}%)`); if (g) parts.push(`grayscale(${g}%)`); return parts.length ? parts.join(' ') : 'none'; })()}
|
|
1170
|
+
>
|
|
1171
|
+
{#if element.type === 'code'}
|
|
1172
|
+
{@const codeEl = element as CodeElement}
|
|
1173
|
+
{@const morphState = codeMorphState.get(codeEl.id)}
|
|
1174
|
+
<div class="animot-code-block" class:transparent-bg={codeEl.transparentBackground} style:font-size="{codeEl.fontSize}px" style:font-weight={codeEl.fontWeight || 400} style:padding="{codeEl.padding}px" style:border-radius="{animated.borderRadius.current}px" style:background={codeEl.bgColor ?? '#0d1117'}>
|
|
1175
|
+
{#if codeEl.showHeader}
|
|
1176
|
+
<div class="animot-code-header" class:macos={codeEl.headerStyle === 'macos'} class:windows={codeEl.headerStyle === 'windows'} style:border-radius="{codeEl.headerRadius ?? animated.borderRadius.current}px {codeEl.headerRadius ?? animated.borderRadius.current}px 0 0">
|
|
1177
|
+
{#if codeEl.headerStyle === 'macos'}
|
|
1178
|
+
<div class="animot-window-controls">
|
|
1179
|
+
<span class="animot-control close"></span>
|
|
1180
|
+
<span class="animot-control minimize"></span>
|
|
1181
|
+
<span class="animot-control maximize"></span>
|
|
1182
|
+
</div>
|
|
1183
|
+
{:else if codeEl.headerStyle === 'windows'}
|
|
1184
|
+
<div class="animot-window-controls">
|
|
1185
|
+
<span class="animot-control win-minimize">
|
|
1186
|
+
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M2 5h6" stroke="currentColor" stroke-width="1.2"/></svg>
|
|
1187
|
+
</span>
|
|
1188
|
+
<span class="animot-control win-maximize">
|
|
1189
|
+
<svg width="10" height="10" viewBox="0 0 10 10"><rect x="1.5" y="1.5" width="7" height="7" rx="0.5" fill="none" stroke="currentColor" stroke-width="1.2"/></svg>
|
|
1190
|
+
</span>
|
|
1191
|
+
<span class="animot-control win-close">
|
|
1192
|
+
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M2 2l6 6M8 2l-6 6" stroke="currentColor" stroke-width="1.2"/></svg>
|
|
1193
|
+
</span>
|
|
1194
|
+
</div>
|
|
1195
|
+
{/if}
|
|
1196
|
+
<div class="animot-filename-tab" style:border-radius="{codeEl.tabRadius ?? 6}px">
|
|
1197
|
+
<svg class="animot-file-icon" width="14" height="14" viewBox="0 0 16 16" fill="none">
|
|
1198
|
+
<path d="M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z" stroke="currentColor" stroke-width="1.2" opacity="0.5"/>
|
|
1199
|
+
<path d="M9.5 1v3.5H13" stroke="currentColor" stroke-width="1.2" opacity="0.5"/>
|
|
1200
|
+
</svg>
|
|
1201
|
+
<span class="animot-filename">{codeEl.filename}</span>
|
|
1202
|
+
</div>
|
|
1203
|
+
<button class="animot-copy-code-btn" onclick={(e) => { e.stopPropagation(); navigator.clipboard.writeText(codeEl.code); const btn = e.currentTarget as HTMLElement; btn.classList.add('copied'); setTimeout(() => btn.classList.remove('copied'), 1500); }}>
|
|
1204
|
+
<span class="animot-copy-label">Copy</span><span class="animot-copied-label">Copied!</span>
|
|
1205
|
+
<svg class="animot-copy-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
|
1206
|
+
<svg class="animot-check-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>
|
|
1207
|
+
</button>
|
|
1208
|
+
</div>
|
|
1209
|
+
{:else}
|
|
1210
|
+
<button class="animot-copy-code-btn animot-floating" onclick={(e) => { e.stopPropagation(); navigator.clipboard.writeText(codeEl.code); const btn = e.currentTarget as HTMLElement; btn.classList.add('copied'); setTimeout(() => btn.classList.remove('copied'), 1500); }}>
|
|
1211
|
+
<span class="animot-copy-label">Copy</span><span class="animot-copied-label">Copied!</span>
|
|
1212
|
+
<svg class="animot-copy-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
|
1213
|
+
<svg class="animot-check-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>
|
|
1214
|
+
</button>
|
|
1215
|
+
{/if}
|
|
1216
|
+
<div class="animot-code-content">
|
|
1217
|
+
<div class="animot-highlighted-code">
|
|
1218
|
+
{#if morphState && morphState.oldCode !== morphState.newCode && morphState.mode !== 'instant'}
|
|
1219
|
+
{#key currentSlideIndex}
|
|
1220
|
+
<CodeMorph oldCode={morphState?.oldCode ?? ''} newCode={morphState?.newCode ?? ''} language={codeEl.language} theme={codeEl.theme} mode={morphState?.mode ?? 'highlight-changes'} speed={morphState?.speed ?? 50} highlightColor={morphState?.highlightColor ?? '#fef08a'} highlightDuration={codeEl.animation?.highlightDuration || 1000} showLineNumbers={(getElementInSlide(currentSlide, codeEl.id) as CodeElement | undefined)?.showLineNumbers ?? false} />
|
|
1221
|
+
{/key}
|
|
1222
|
+
{:else}
|
|
1223
|
+
{@html getCodeHighlight(codeEl.id)}
|
|
1224
|
+
{/if}
|
|
1225
|
+
</div>
|
|
1226
|
+
</div>
|
|
1227
|
+
</div>
|
|
1228
|
+
{:else if element.type === 'text'}
|
|
1229
|
+
{@const textEl = element as TextElement}
|
|
1230
|
+
{@const animFontSize = animated.fontSize?.current ?? textEl.fontSize}
|
|
1231
|
+
{@const typewriterState = textTypewriterState.get(element.id)}
|
|
1232
|
+
{@const displayText = typewriterState?.isAnimating ? typewriterState.fullText.slice(0, typewriterState.displayedChars) : textEl.content}
|
|
1233
|
+
<div
|
|
1234
|
+
class="animot-text-element"
|
|
1235
|
+
style:font-size="{animFontSize}px"
|
|
1236
|
+
style:font-weight={textEl.fontWeight}
|
|
1237
|
+
style:font-family="'{textEl.fontFamily}', sans-serif"
|
|
1238
|
+
style:font-style={textEl.fontStyle ?? 'normal'}
|
|
1239
|
+
style:text-decoration={textEl.textDecoration ?? 'none'}
|
|
1240
|
+
style:color={textEl.backgroundImage ? 'transparent' : (textEl.hollow && textEl.textStroke?.enabled ? 'transparent' : textEl.color)}
|
|
1241
|
+
style:background-color={textEl.backgroundImage ? 'transparent' : textEl.backgroundColor}
|
|
1242
|
+
style:background-image={textEl.backgroundImage ? `url(${textEl.backgroundImage})` : 'none'}
|
|
1243
|
+
style:background-size={textEl.backgroundImage ? `${textEl.backgroundScale ?? 100}%` : 'cover'}
|
|
1244
|
+
style:background-position={textEl.backgroundImage ? `${textEl.backgroundPositionX ?? 50}% ${textEl.backgroundPositionY ?? 50}%` : 'center'}
|
|
1245
|
+
style:-webkit-background-clip={textEl.backgroundImage ? 'text' : 'border-box'}
|
|
1246
|
+
style:background-clip={textEl.backgroundImage ? 'text' : 'border-box'}
|
|
1247
|
+
style:padding="{textEl.padding}px"
|
|
1248
|
+
style:border-radius="{textEl.borderRadius}px"
|
|
1249
|
+
style:text-align={textEl.textAlign}
|
|
1250
|
+
style:justify-content={textEl.textAlign === 'center' ? 'center' : textEl.textAlign === 'right' ? 'flex-end' : 'flex-start'}
|
|
1251
|
+
style:-webkit-text-stroke={textEl.textStroke?.enabled ? `${textEl.textStroke.width}px ${textEl.textStroke.color}` : '0'}
|
|
1252
|
+
style:text-shadow={textEl.textShadow?.enabled ? `${textEl.textShadow.offsetX}px ${textEl.textShadow.offsetY}px ${textEl.textShadow.blur}px ${textEl.textShadow.color}` : 'none'}
|
|
1253
|
+
>
|
|
1254
|
+
{displayText}{#if typewriterState?.isAnimating}<span class="animot-typewriter-cursor">|</span>{/if}
|
|
1255
|
+
</div>
|
|
1256
|
+
{:else if element.type === 'arrow'}
|
|
1257
|
+
{@const arrowEl = element as ArrowElement}
|
|
1258
|
+
{@const cp = arrowEl.controlPoints || []}
|
|
1259
|
+
{@const pathD = cp.length === 0 ? `M ${arrowEl.startPoint.x} ${arrowEl.startPoint.y} L ${arrowEl.endPoint.x} ${arrowEl.endPoint.y}` : cp.length === 1 ? `M ${arrowEl.startPoint.x} ${arrowEl.startPoint.y} Q ${cp[0].x} ${cp[0].y} ${arrowEl.endPoint.x} ${arrowEl.endPoint.y}` : cp.length === 2 ? `M ${arrowEl.startPoint.x} ${arrowEl.startPoint.y} C ${cp[0].x} ${cp[0].y} ${cp[1].x} ${cp[1].y} ${arrowEl.endPoint.x} ${arrowEl.endPoint.y}` : buildCatmullRomPath(arrowEl.startPoint, cp, arrowEl.endPoint)}
|
|
1260
|
+
{@const lastCp = cp.length > 0 ? cp[cp.length - 1] : arrowEl.startPoint}
|
|
1261
|
+
{@const endAngle = Math.atan2(arrowEl.endPoint.y - lastCp.y, arrowEl.endPoint.x - lastCp.x)}
|
|
1262
|
+
{@const headAngle = Math.PI / 6}
|
|
1263
|
+
{@const headSize = arrowEl.headSize}
|
|
1264
|
+
{@const arrowHeadPath = `M ${arrowEl.endPoint.x - headSize * Math.cos(endAngle - headAngle)} ${arrowEl.endPoint.y - headSize * Math.sin(endAngle - headAngle)} L ${arrowEl.endPoint.x} ${arrowEl.endPoint.y} L ${arrowEl.endPoint.x - headSize * Math.cos(endAngle + headAngle)} ${arrowEl.endPoint.y - headSize * Math.sin(endAngle + headAngle)}`}
|
|
1265
|
+
{@const arrowAnimMode = arrowEl.animation?.mode ?? 'none'}
|
|
1266
|
+
{@const arrowAnimDuration = arrowEl.animation?.duration ?? 500}
|
|
1267
|
+
{@const isStyledArrow = arrowEl.style !== 'solid'}
|
|
1268
|
+
{@const isDrawType = arrowAnimMode === 'draw' || arrowAnimMode === 'undraw' || arrowAnimMode === 'draw-undraw' || arrowAnimMode === 'flow'}
|
|
1269
|
+
{@const baseDashArray = arrowEl.style === 'dashed' ? '10,5' : arrowEl.style === 'dotted' ? '2,5' : 'none'}
|
|
1270
|
+
<svg class="animot-arrow-element" class:arrow-animate-grow={arrowAnimMode === 'grow'} viewBox="0 0 {arrowEl.size.width} {arrowEl.size.height}" preserveAspectRatio="none" style="--arrow-anim-duration: {arrowAnimDuration}ms;" use:arrowClipDraw={{ enabled: isDrawType, mode: arrowAnimMode, duration: arrowAnimDuration, startX: arrowEl.startPoint.x, startY: arrowEl.startPoint.y, endX: arrowEl.endPoint.x, endY: arrowEl.endPoint.y, loop: !!arrowEl.animation?.loop, reverse: arrowEl.animation?.direction === 'reverse', key: `${pathD}-${arrowAnimMode}-${arrowAnimDuration}-${arrowEl.animation?.loop}-${arrowEl.animation?.direction}-${currentSlideIndex}` }}>
|
|
1271
|
+
<path class="arrow-path" d={pathD} fill="none" stroke={arrowEl.color} stroke-width={arrowEl.strokeWidth} stroke-dasharray={baseDashArray} stroke-linecap="round" stroke-linejoin="round" />
|
|
1272
|
+
{#if arrowEl.showHead !== false}
|
|
1273
|
+
<path class="arrow-head" class:arrow-head-styled-draw={isDrawType && isStyledArrow} class:arrow-head-undraw={arrowAnimMode === 'undraw'} class:arrow-head-draw-undraw={arrowAnimMode === 'draw-undraw'} d={arrowHeadPath} fill="none" stroke={arrowEl.color} stroke-width={arrowEl.strokeWidth} stroke-linecap="round" stroke-linejoin="round" style={isDrawType && isStyledArrow ? `--arrow-anim-duration: ${arrowAnimDuration}ms;` : ''} />
|
|
1274
|
+
{/if}
|
|
1275
|
+
{#if arrowEl.flowMarkers?.enabled}
|
|
1276
|
+
<FlowMarkers config={arrowEl.flowMarkers} start={arrowEl.startPoint} end={arrowEl.endPoint} controlPoints={cp} />
|
|
1277
|
+
{/if}
|
|
1278
|
+
</svg>
|
|
1279
|
+
{:else if element.type === 'image'}
|
|
1280
|
+
{@const imgEl = element as ImageElement}
|
|
1281
|
+
{@const clipPath = imgEl.clipMask?.enabled ? (imgEl.clipMask.shapeType === 'circle' ? 'circle(50% at 50% 50%)' : imgEl.clipMask.shapeType === 'ellipse' ? 'ellipse(50% 50% at 50% 50%)' : imgEl.clipMask.shapeType === 'triangle' ? 'polygon(50% 0%, 0% 100%, 100% 100%)' : imgEl.clipMask.shapeType === 'star' ? 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)' : imgEl.clipMask.shapeType === 'hexagon' ? 'polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%)' : (imgEl.clipMask.borderRadius ?? 0) > 0 ? `inset(0 round ${imgEl.clipMask.borderRadius}px)` : 'none') : 'none'}
|
|
1282
|
+
<img class="animot-image-element" src={imgEl.src} alt="" style:object-fit={imgEl.objectFit} style:border-radius="{imgEl.clipMask?.enabled ? 0 : imgEl.borderRadius}px" style:clip-path={clipPath} style:background-color={imgEl.backgroundColor ?? 'transparent'} />
|
|
1283
|
+
{:else if element.type === 'shape'}
|
|
1284
|
+
{@const shapeEl = element as ShapeElement}
|
|
1285
|
+
{@const animFill = animated.fillColor?.current ?? shapeEl.fillColor}
|
|
1286
|
+
{@const animStroke = animated.strokeColor?.current ?? shapeEl.strokeColor}
|
|
1287
|
+
{@const animStrokeWidth = animated.strokeWidth?.current ?? shapeEl.strokeWidth}
|
|
1288
|
+
{@const mState = shapeMorphStates.get(element.id)}
|
|
1289
|
+
{@const morphProgress = animated.shapeMorph?.current ?? 1}
|
|
1290
|
+
{@const effectiveShapeType = mState ? (morphProgress >= 1 ? mState.toType : (morphProgress <= 0 ? mState.fromType : null)) : shapeEl.shapeType}
|
|
1291
|
+
{@const isMorphing = mState && morphProgress > 0 && morphProgress < 1}
|
|
1292
|
+
<svg class="animot-shape-element" viewBox="0 0 {animated.width.current} {animated.height.current}" style:filter={shapeEl.boxShadow?.enabled ? `drop-shadow(${shapeEl.boxShadow.offsetX}px ${shapeEl.boxShadow.offsetY}px ${shapeEl.boxShadow.blur}px ${shapeEl.boxShadow.color})` : 'none'}>
|
|
1293
|
+
{#if isMorphing}
|
|
1294
|
+
{@const w = animated.width.current}
|
|
1295
|
+
{@const h = animated.height.current}
|
|
1296
|
+
{@const sw = animStrokeWidth}
|
|
1297
|
+
<g style:opacity={1 - morphProgress}>{@html renderShape(mState!.fromType, w, h, animated.borderRadius.current, animFill, animStroke, sw, shapeEl.strokeStyle, shapeEl.strokeDashGap)}</g>
|
|
1298
|
+
<g style:opacity={morphProgress}>{@html renderShape(mState!.toType, w, h, animated.borderRadius.current, animFill, animStroke, sw, shapeEl.strokeStyle, shapeEl.strokeDashGap)}</g>
|
|
1299
|
+
{:else}
|
|
1300
|
+
{@html renderShape(effectiveShapeType ?? shapeEl.shapeType, animated.width.current, animated.height.current, animated.borderRadius.current, animFill, animStroke, animStrokeWidth, shapeEl.strokeStyle, shapeEl.strokeDashGap)}
|
|
1301
|
+
{/if}
|
|
1302
|
+
</svg>
|
|
1303
|
+
{:else if element.type === 'counter'}
|
|
1304
|
+
<CounterRenderer element={element as CounterElement} slideId={currentSlide?.id ?? ''} />
|
|
1305
|
+
{:else if element.type === 'chart'}
|
|
1306
|
+
<ChartRenderer element={element as ChartElement} slideId={currentSlide?.id ?? ''} />
|
|
1307
|
+
{:else if element.type === 'icon'}
|
|
1308
|
+
<IconRenderer element={element as IconElement} />
|
|
1309
|
+
{:else if element.type === 'svg'}
|
|
1310
|
+
{@const svgEl = element as SvgElement}
|
|
1311
|
+
{@const svgParsed = (() => { const m = svgEl.svgContent.trim().match(/^<svg([^>]*)>([\s\S]*)<\/svg>$/i); if (m) { const vb = m[1].match(/viewBox=["']([^"']+)["']/i); return { inner: m[2], viewBox: vb ? vb[1] : null }; } return { inner: svgEl.svgContent, viewBox: null }; })()}
|
|
1312
|
+
{@const svgAnimMode = svgEl.animation?.mode ?? 'none'}
|
|
1313
|
+
{@const svgAnimDur = svgEl.animation?.duration ?? 800}
|
|
1314
|
+
{@const svgAnimLoop = svgEl.animation?.loop ?? false}
|
|
1315
|
+
{@const svgAnimReverse = svgEl.animation?.direction === 'reverse'}
|
|
1316
|
+
<div
|
|
1317
|
+
class="animot-svg-element"
|
|
1318
|
+
use:traceSvgPaths={{
|
|
1319
|
+
enabled: svgAnimMode !== 'none',
|
|
1320
|
+
mode: svgAnimMode as 'none' | 'draw' | 'undraw' | 'draw-undraw' | 'flow',
|
|
1321
|
+
duration: svgAnimDur,
|
|
1322
|
+
loop: svgAnimLoop,
|
|
1323
|
+
reverse: svgAnimReverse,
|
|
1324
|
+
key: `${svgEl.id}-${svgAnimMode}-${svgAnimDur}-${currentSlideIndex}`
|
|
1325
|
+
}}
|
|
1326
|
+
>
|
|
1327
|
+
<svg width="100%" height="100%" viewBox={svgEl.viewBox ?? svgParsed.viewBox ?? `0 0 ${svgEl.size.width} ${svgEl.size.height}`} preserveAspectRatio={svgEl.preserveAspectRatio} xmlns="http://www.w3.org/2000/svg">
|
|
1328
|
+
<g style={svgEl.color ? `fill:${svgEl.color};stroke:${svgEl.color}` : ''}>
|
|
1329
|
+
{@html svgParsed.inner}
|
|
1330
|
+
</g>
|
|
1331
|
+
</svg>
|
|
1332
|
+
</div>
|
|
1333
|
+
{:else if element.type === 'motionPath'}
|
|
1334
|
+
{@const mpEl = element as MotionPathElement}
|
|
1335
|
+
{#if mpEl.showInPresentation}
|
|
1336
|
+
<svg width="100%" height="100%" viewBox="0 0 {animated.width.current} {animated.height.current}" style="position:absolute;top:0;left:0;pointer-events:none;overflow:visible;">
|
|
1337
|
+
<path d={buildPresenterPathD(mpEl.points, mpEl.closed)} stroke={mpEl.pathColor} stroke-width={mpEl.pathWidth} fill="none" stroke-dasharray="8 4" />
|
|
1338
|
+
</svg>
|
|
1339
|
+
{/if}
|
|
1340
|
+
{/if}
|
|
1341
|
+
</div>
|
|
1342
|
+
{/if}
|
|
1343
|
+
{/each}
|
|
1344
|
+
</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
|
|
1347
|
+
{#if arrows}
|
|
1348
|
+
<button class="animot-arrow animot-arrow-left" onclick={() => animateToSlide(currentSlideIndex - 1)} disabled={currentSlideIndex === 0 || isTransitioning} aria-label="Previous slide">
|
|
1349
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
|
|
1350
|
+
</button>
|
|
1351
|
+
<button class="animot-arrow animot-arrow-right" onclick={() => handleNextSlide()} disabled={(!loop && currentSlideIndex === slides.length - 1) || isTransitioning} aria-label="Next slide">
|
|
1352
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
|
|
1353
|
+
</button>
|
|
1354
|
+
{/if}
|
|
1355
|
+
|
|
1356
|
+
{#if controls}
|
|
1357
|
+
<div class="animot-controls">
|
|
1358
|
+
<button onclick={() => animateToSlide(currentSlideIndex - 1)} disabled={currentSlideIndex === 0 || isTransitioning} aria-label="Previous">
|
|
1359
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
|
1360
|
+
</button>
|
|
1361
|
+
<span class="animot-slide-indicator">{currentSlideIndex + 1} / {slides.length}</span>
|
|
1362
|
+
<button onclick={() => handleNextSlide()} disabled={(!loop && currentSlideIndex === slides.length - 1) || isTransitioning} aria-label="Next">
|
|
1363
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
|
1364
|
+
</button>
|
|
1365
|
+
<button onclick={() => { isAutoplay = !isAutoplay; if (!isAutoplay) clearAutoplayTimer(); }} class:active={isAutoplay} aria-label={isAutoplay ? 'Pause' : 'Play'}>
|
|
1366
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1367
|
+
{#if isAutoplay}<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>{:else}<polygon points="5 3 19 12 5 21 5 3"/>{/if}
|
|
1368
|
+
</svg>
|
|
1369
|
+
</button>
|
|
1370
|
+
</div>
|
|
1371
|
+
{/if}
|
|
1372
|
+
|
|
1373
|
+
{#if showProgress}
|
|
1374
|
+
<div class="animot-progress-bar">
|
|
1375
|
+
<div class="animot-progress-fill" style:width="{((currentSlideIndex + 1) / slides.length) * 100}%"></div>
|
|
1376
|
+
</div>
|
|
1377
|
+
{/if}
|
|
1378
|
+
{/if}
|
|
1379
|
+
</div>
|
|
1380
|
+
|
|
1381
|
+
<script module lang="ts">
|
|
1382
|
+
function roundedPolygonPath(pointsStr: string, radius: number): string {
|
|
1383
|
+
const pts = pointsStr.split(/\s+/).map(p => { const [x, y] = p.split(',').map(Number); return { x, y }; });
|
|
1384
|
+
if (pts.length < 3 || radius <= 0) return 'M' + pts.map(p => `${p.x},${p.y}`).join('L') + 'Z';
|
|
1385
|
+
const n = pts.length;
|
|
1386
|
+
const parts: string[] = [];
|
|
1387
|
+
for (let i = 0; i < n; i++) {
|
|
1388
|
+
const prev = pts[(i - 1 + n) % n], curr = pts[i], next = pts[(i + 1) % n];
|
|
1389
|
+
const dx1 = prev.x - curr.x, dy1 = prev.y - curr.y;
|
|
1390
|
+
const dx2 = next.x - curr.x, dy2 = next.y - curr.y;
|
|
1391
|
+
const len1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
|
1392
|
+
const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
1393
|
+
const r = Math.min(radius, len1 / 2, len2 / 2);
|
|
1394
|
+
const sx = curr.x + (dx1 / len1) * r, sy = curr.y + (dy1 / len1) * r;
|
|
1395
|
+
const ex = curr.x + (dx2 / len2) * r, ey = curr.y + (dy2 / len2) * r;
|
|
1396
|
+
parts.push(i === 0 ? `M${sx},${sy}` : `L${sx},${sy}`);
|
|
1397
|
+
parts.push(`Q${curr.x},${curr.y} ${ex},${ey}`);
|
|
1398
|
+
}
|
|
1399
|
+
parts.push('Z');
|
|
1400
|
+
return parts.join(' ');
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
function renderShape(type: string, w: number, h: number, br: number, fill: string, stroke: string, sw: number, strokeStyle?: string, strokeDashGap?: number): string {
|
|
1404
|
+
let dashAttr = '';
|
|
1405
|
+
if (strokeStyle && strokeStyle !== 'solid') {
|
|
1406
|
+
const s = sw || 1;
|
|
1407
|
+
const gap = strokeDashGap ?? (strokeStyle === 'dashed' ? s * 3 : s * 2);
|
|
1408
|
+
const da = strokeStyle === 'dashed' ? `${s * 3},${gap}` : `${s * 0.1},${gap}`;
|
|
1409
|
+
const lc = strokeStyle === 'dotted' ? 'round' : 'butt';
|
|
1410
|
+
dashAttr = ` stroke-dasharray="${da}" stroke-linecap="${lc}"`;
|
|
1411
|
+
}
|
|
1412
|
+
const polyOrPath = (pts: string) => {
|
|
1413
|
+
if (br > 0) return `<path d="${roundedPolygonPath(pts, br)}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr}/>`;
|
|
1414
|
+
return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr} stroke-linejoin="round"/>`;
|
|
1415
|
+
};
|
|
1416
|
+
switch (type) {
|
|
1417
|
+
case 'rectangle': return `<rect x="${sw/2}" y="${sw/2}" width="${w-sw}" height="${h-sw}" rx="${br}" ry="${br}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr}/>`;
|
|
1418
|
+
case 'circle': return `<circle cx="${w/2}" cy="${h/2}" r="${Math.min(w,h)/2-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr}/>`;
|
|
1419
|
+
case 'ellipse': return `<ellipse cx="${w/2}" cy="${h/2}" rx="${w/2-sw/2}" ry="${h/2-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr}/>`;
|
|
1420
|
+
case 'triangle': return polyOrPath(`${w/2},${sw/2} ${sw/2},${h-sw/2} ${w-sw/2},${h-sw/2}`);
|
|
1421
|
+
case 'star': {
|
|
1422
|
+
const cx = w/2, cy = h/2, outerR = Math.min(w,h)/2-sw/2, innerR = outerR*0.4;
|
|
1423
|
+
const pts = Array.from({length:10},(_,i)=>{const a=(i*Math.PI/5)-Math.PI/2;const r=i%2===0?outerR:innerR;return`${cx+r*Math.cos(a)},${cy+r*Math.sin(a)}`;}).join(' ');
|
|
1424
|
+
return polyOrPath(pts);
|
|
1425
|
+
}
|
|
1426
|
+
case 'hexagon': {
|
|
1427
|
+
const cx = w/2, cy = h/2, r = Math.min(w,h)/2-sw/2;
|
|
1428
|
+
const pts = Array.from({length:6},(_,i)=>{const a=(i*Math.PI/3)-Math.PI/2;return`${cx+r*Math.cos(a)},${cy+r*Math.sin(a)}`;}).join(' ');
|
|
1429
|
+
return polyOrPath(pts);
|
|
1430
|
+
}
|
|
1431
|
+
default: return '';
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
</script>
|
|
1435
|
+
|
|
1436
|
+
<style>
|
|
1437
|
+
/* Universal reset — mirrors the animot app's global * reset to prevent
|
|
1438
|
+
host page defaults (margins on p/h1, padding, box-sizing) from leaking in */
|
|
1439
|
+
.animot-presenter :global(*) {
|
|
1440
|
+
margin: 0;
|
|
1441
|
+
padding: 0;
|
|
1442
|
+
box-sizing: border-box;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
.animot-presenter {
|
|
1446
|
+
position: relative;
|
|
1447
|
+
width: 100%;
|
|
1448
|
+
height: 100%;
|
|
1449
|
+
display: flex;
|
|
1450
|
+
align-items: center;
|
|
1451
|
+
justify-content: center;
|
|
1452
|
+
overflow: hidden;
|
|
1453
|
+
background: transparent;
|
|
1454
|
+
/* Reset inheritable CSS from host page to prevent style leakage */
|
|
1455
|
+
line-height: normal;
|
|
1456
|
+
font-size: 16px;
|
|
1457
|
+
font-weight: 400;
|
|
1458
|
+
font-style: normal;
|
|
1459
|
+
letter-spacing: normal;
|
|
1460
|
+
word-spacing: normal;
|
|
1461
|
+
text-transform: none;
|
|
1462
|
+
text-indent: 0;
|
|
1463
|
+
text-align: left;
|
|
1464
|
+
color: inherit;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
.animot-canvas-wrapper {
|
|
1468
|
+
display: flex;
|
|
1469
|
+
align-items: center;
|
|
1470
|
+
justify-content: center;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
.animot-canvas {
|
|
1474
|
+
position: relative;
|
|
1475
|
+
overflow: hidden;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
.animot-element {
|
|
1479
|
+
position: absolute;
|
|
1480
|
+
box-sizing: border-box;
|
|
1481
|
+
will-change: transform, opacity, left, top, width, height;
|
|
1482
|
+
isolation: isolate;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
.animot-element.floating {
|
|
1486
|
+
animation-duration: var(--float-speed, 3s);
|
|
1487
|
+
animation-timing-function: ease-in-out;
|
|
1488
|
+
animation-iteration-count: infinite;
|
|
1489
|
+
animation-delay: var(--float-delay, 0s);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/* Code */
|
|
1493
|
+
.animot-code-block {
|
|
1494
|
+
width: 100%; height: 100%; overflow: hidden;
|
|
1495
|
+
display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
|
1496
|
+
margin: 0; box-sizing: border-box;
|
|
1497
|
+
}
|
|
1498
|
+
.animot-code-block.transparent-bg { background: transparent !important; box-shadow: none; }
|
|
1499
|
+
.animot-code-block.transparent-bg .animot-code-header { background: transparent; border-bottom-color: rgba(255,255,255,0.06); }
|
|
1500
|
+
.animot-code-header { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: rgba(0, 0, 0, 0.2); border-bottom: 1px solid rgba(255, 255, 255, 0.06); flex-shrink: 0; min-height: 40px; }
|
|
1501
|
+
.animot-window-controls { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
|
|
1502
|
+
.macos .animot-control { width: 12px; height: 12px; border-radius: 50%; display: block; }
|
|
1503
|
+
.macos .animot-control.close { background: #ff5f57; }
|
|
1504
|
+
.macos .animot-control.minimize { background: #febc2e; }
|
|
1505
|
+
.macos .animot-control.maximize { background: #28c840; }
|
|
1506
|
+
.windows .animot-window-controls { order: 99; margin-left: auto; gap: 0; }
|
|
1507
|
+
.windows .animot-control { display: flex; align-items: center; justify-content: center; width: 28px; height: 24px; border-radius: 4px; color: rgba(255,255,255,0.45); }
|
|
1508
|
+
.animot-filename-tab { display: flex; align-items: center; gap: 6px; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); border-radius: 6px; padding: 4px 10px; max-width: 220px; color: rgba(255,255,255,0.4); }
|
|
1509
|
+
.animot-file-icon { flex-shrink: 0; }
|
|
1510
|
+
.animot-filename { color: rgba(255,255,255,0.55); font-size: 12px; line-height: 18px; }
|
|
1511
|
+
.animot-copy-code-btn { display: flex; align-items: center; gap: 5px; height: 28px; padding: 0 8px; margin-left: auto; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; color: rgba(255,255,255,0.4); cursor: pointer; opacity: 0; transition: opacity 0.2s, background 0.15s, color 0.15s; flex-shrink: 0; font-size: 12px; font-family: inherit; white-space: nowrap; }
|
|
1512
|
+
.animot-copy-code-btn:hover { background: rgba(255,255,255,0.12); color: rgba(255,255,255,0.8); }
|
|
1513
|
+
.animot-copy-code-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
|
|
1514
|
+
.animot-copy-code-btn .animot-check-icon { display: none; }
|
|
1515
|
+
.animot-copy-code-btn .animot-copied-label { display: none; }
|
|
1516
|
+
.animot-copy-code-btn.copied .animot-copy-icon { display: none; }
|
|
1517
|
+
.animot-copy-code-btn.copied .animot-copy-label { display: none; }
|
|
1518
|
+
.animot-copy-code-btn.copied .animot-check-icon { display: block; color: #4ade80; }
|
|
1519
|
+
.animot-copy-code-btn.copied .animot-copied-label { display: inline; color: #4ade80; }
|
|
1520
|
+
.animot-copy-code-btn.animot-floating { position: absolute; top: 8px; right: 8px; z-index: 2; }
|
|
1521
|
+
.animot-code-block:hover .animot-copy-code-btn { opacity: 1; }
|
|
1522
|
+
.animot-code-content { flex: 1; overflow: hidden; position: relative; }
|
|
1523
|
+
.animot-highlighted-code { width: 100%; height: 100%; }
|
|
1524
|
+
.animot-code-content :global(pre), .animot-highlighted-code :global(pre) { margin: 0; padding: 16px; background: transparent !important; line-height: 1.6; font-size: inherit; overflow: visible; }
|
|
1525
|
+
.animot-highlighted-code :global(code) { font-family: inherit; font-size: inherit; font-weight: inherit; }
|
|
1526
|
+
.animot-highlighted-code :global(.line-number) { display: inline-block; width: 2.5em; margin-right: 1em; text-align: right; color: #6e7681; user-select: none; opacity: 0.6; }
|
|
1527
|
+
|
|
1528
|
+
/* Text */
|
|
1529
|
+
.animot-text-element { width: 100%; height: 100%; display: flex; align-items: center; white-space: pre-wrap; word-wrap: break-word; }
|
|
1530
|
+
.animot-typewriter-cursor { animation: animot-blink 0.7s infinite; font-weight: 100; }
|
|
1531
|
+
@keyframes animot-blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } }
|
|
1532
|
+
|
|
1533
|
+
/* Arrow */
|
|
1534
|
+
.animot-arrow-element { width: 100%; height: 100%; }
|
|
1535
|
+
.arrow-animate-draw .arrow-path { stroke-dashoffset: var(--path-len, 1000); animation: animot-arrow-draw var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1536
|
+
.arrow-animate-undraw .arrow-path { stroke-dashoffset: 0; animation: animot-arrow-undraw var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1537
|
+
.arrow-animate-draw-undraw .arrow-path { stroke-dashoffset: var(--path-len, 1000); animation: animot-arrow-draw-undraw var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1538
|
+
.arrow-head-styled-draw { opacity: 0; animation: animot-arrow-head-appear var(--arrow-anim-duration, 500ms) ease-out forwards; animation-delay: calc(var(--arrow-anim-duration, 500ms) * 0.7); }
|
|
1539
|
+
.arrow-animate-draw .arrow-head { opacity: 0; animation: animot-arrow-head-appear var(--arrow-anim-duration, 500ms) ease-out forwards; animation-delay: calc(var(--arrow-anim-duration, 500ms) * 0.7); }
|
|
1540
|
+
.arrow-animate-undraw .arrow-head, .arrow-head-undraw { opacity: 1; animation: animot-arrow-head-disappear var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1541
|
+
.arrow-animate-draw-undraw .arrow-head, .arrow-head-draw-undraw { opacity: 0; animation: animot-arrow-head-draw-undraw var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1542
|
+
.arrow-animate-grow { transform-origin: left center; animation: animot-arrow-grow var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1543
|
+
/* loop: replay continuously while slide is shown */
|
|
1544
|
+
.arrow-anim-loop .arrow-path, .arrow-anim-loop .arrow-head { animation-iteration-count: infinite !important; }
|
|
1545
|
+
/* reverse: flip start ↔ end */
|
|
1546
|
+
.arrow-anim-reverse .arrow-path, .arrow-anim-reverse .arrow-head { animation-direction: reverse !important; }
|
|
1547
|
+
@keyframes animot-arrow-draw { to { stroke-dashoffset: 0; } }
|
|
1548
|
+
@keyframes animot-arrow-undraw { from { stroke-dashoffset: 0; } to { stroke-dashoffset: var(--path-len, 1000); } }
|
|
1549
|
+
@keyframes animot-arrow-draw-undraw { 0% { stroke-dashoffset: var(--path-len, 1000); } 50% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: var(--path-len, 1000); } }
|
|
1550
|
+
@keyframes animot-arrow-head-appear { from { opacity: 0; } to { opacity: 1; } }
|
|
1551
|
+
@keyframes animot-arrow-head-disappear { 0% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }
|
|
1552
|
+
@keyframes animot-arrow-head-draw-undraw { 0% { opacity: 0; } 35% { opacity: 1; } 65% { opacity: 1; } 100% { opacity: 0; } }
|
|
1553
|
+
@keyframes animot-arrow-grow { from { transform: scaleX(0); opacity: 0; } to { transform: scaleX(1); opacity: 1; } }
|
|
1554
|
+
|
|
1555
|
+
/* Image */
|
|
1556
|
+
.animot-image-element { width: 100%; height: 100%; display: block; }
|
|
1557
|
+
|
|
1558
|
+
/* Shape */
|
|
1559
|
+
.animot-shape-element { width: 100%; height: 100%; display: block; overflow: visible; }
|
|
1560
|
+
|
|
1561
|
+
/* Transitions */
|
|
1562
|
+
.animot-canvas { --transition-duration: 500ms; transition: transform calc(var(--transition-duration) * 0.4) ease, opacity calc(var(--transition-duration) * 0.4) ease; }
|
|
1563
|
+
.animot-canvas.transition-fade-out { opacity: 0; }
|
|
1564
|
+
.animot-canvas.transition-fade-in { animation: animot-fadeIn calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1565
|
+
.animot-canvas.transition-slide-left-out.forward { transform: translateX(-100%); opacity: 0; }
|
|
1566
|
+
.animot-canvas.transition-slide-left-in.forward { animation: animot-slideInFromRight calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1567
|
+
.animot-canvas.transition-slide-left-out.backward { transform: translateX(100%); opacity: 0; }
|
|
1568
|
+
.animot-canvas.transition-slide-left-in.backward { animation: animot-slideInFromLeft calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1569
|
+
.animot-canvas.transition-slide-right-out.forward { transform: translateX(100%); opacity: 0; }
|
|
1570
|
+
.animot-canvas.transition-slide-right-in.forward { animation: animot-slideInFromLeft calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1571
|
+
.animot-canvas.transition-slide-up-out { transform: translateY(-100%); opacity: 0; }
|
|
1572
|
+
.animot-canvas.transition-slide-up-in { animation: animot-slideInFromBottom calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1573
|
+
.animot-canvas.transition-slide-down-out { transform: translateY(100%); opacity: 0; }
|
|
1574
|
+
.animot-canvas.transition-slide-down-in { animation: animot-slideInFromTop calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1575
|
+
.animot-canvas.transition-zoom-in-out { transform: scale(0.5); opacity: 0; }
|
|
1576
|
+
.animot-canvas.transition-zoom-in-in { animation: animot-zoomIn calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1577
|
+
.animot-canvas.transition-zoom-out-out { transform: scale(1.5); opacity: 0; }
|
|
1578
|
+
.animot-canvas.transition-zoom-out-in { animation: animot-zoomOut calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1579
|
+
.animot-canvas.transition-flip-out { transform: perspective(1000px) rotateY(90deg); opacity: 0; }
|
|
1580
|
+
.animot-canvas.transition-flip-in { animation: animot-flipIn calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1581
|
+
|
|
1582
|
+
@keyframes animot-fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
1583
|
+
@keyframes animot-slideInFromRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
1584
|
+
@keyframes animot-slideInFromLeft { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
1585
|
+
@keyframes animot-slideInFromBottom { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
1586
|
+
@keyframes animot-slideInFromTop { from { transform: translateY(-100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
1587
|
+
@keyframes animot-zoomIn { from { transform: scale(0.5); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
|
1588
|
+
@keyframes animot-zoomOut { from { transform: scale(1.5); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
|
1589
|
+
@keyframes animot-flipIn { from { transform: perspective(1000px) rotateY(-90deg); opacity: 0; } to { transform: perspective(1000px) rotateY(0); opacity: 1; } }
|
|
1590
|
+
|
|
1591
|
+
/* Flip-X transition */
|
|
1592
|
+
.animot-canvas.transition-flip-x-out { transform: perspective(1000px) rotateX(90deg); opacity: 0; }
|
|
1593
|
+
.animot-canvas.transition-flip-x-in { animation: animot-flipXIn calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1594
|
+
@keyframes animot-flipXIn { from { transform: perspective(1000px) rotateX(-90deg); opacity: 0; } to { transform: perspective(1000px) rotateX(0); opacity: 1; } }
|
|
1595
|
+
|
|
1596
|
+
/* Flip-Y transition */
|
|
1597
|
+
.animot-canvas.transition-flip-y-out { transform: perspective(1000px) rotateY(90deg); opacity: 0; }
|
|
1598
|
+
.animot-canvas.transition-flip-y-in { animation: animot-flipYIn calc(var(--transition-duration) * 0.6) ease forwards; }
|
|
1599
|
+
@keyframes animot-flipYIn { from { transform: perspective(1000px) rotateY(-90deg); opacity: 0; } to { transform: perspective(1000px) rotateY(0); opacity: 1; } }
|
|
1600
|
+
|
|
1601
|
+
/* SVG element */
|
|
1602
|
+
.animot-svg-element { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
|
|
1603
|
+
.animot-svg-element :global(svg) { width: 100%; height: 100%; }
|
|
1604
|
+
|
|
1605
|
+
/* Controls */
|
|
1606
|
+
.animot-controls {
|
|
1607
|
+
position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%);
|
|
1608
|
+
display: flex; align-items: center; gap: 8px; padding: 8px 16px;
|
|
1609
|
+
background: rgba(0,0,0,0.7); backdrop-filter: blur(10px); border-radius: 10px;
|
|
1610
|
+
opacity: 0; transition: opacity 0.3s ease 0.15s; z-index: 100;
|
|
1611
|
+
}
|
|
1612
|
+
.animot-presenter:hover .animot-controls, .animot-menu-visible .animot-controls { opacity: 1; transition-delay: 0s; }
|
|
1613
|
+
.animot-controls button {
|
|
1614
|
+
display: flex; align-items: center; justify-content: center;
|
|
1615
|
+
width: 32px; height: 32px; border-radius: 6px; border: none; cursor: pointer;
|
|
1616
|
+
background: rgba(255,255,255,0.1); color: white; transition: background 0.2s;
|
|
1617
|
+
}
|
|
1618
|
+
.animot-controls button:hover:not(:disabled) { background: rgba(255,255,255,0.2); }
|
|
1619
|
+
.animot-controls button:disabled { opacity: 0.3; cursor: not-allowed; }
|
|
1620
|
+
.animot-controls button.active { background: rgba(99,102,241,0.6); }
|
|
1621
|
+
.animot-controls button svg { width: 16px; height: 16px; }
|
|
1622
|
+
.animot-slide-indicator { font-size: 12px; color: white; min-width: 50px; text-align: center; font-family: system-ui, sans-serif; }
|
|
1623
|
+
|
|
1624
|
+
/* Arrows */
|
|
1625
|
+
.animot-arrow {
|
|
1626
|
+
position: absolute; top: 50%; transform: translateY(-50%);
|
|
1627
|
+
width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer;
|
|
1628
|
+
background: rgba(0,0,0,0.5); color: white; display: flex; align-items: center; justify-content: center;
|
|
1629
|
+
opacity: 0; transition: opacity 0.3s 0.15s; z-index: 100;
|
|
1630
|
+
/* Extra padding extends the hover hit area beyond the visible button */
|
|
1631
|
+
padding: 0; margin: 0;
|
|
1632
|
+
}
|
|
1633
|
+
.animot-presenter:hover .animot-arrow { opacity: 1; transition-delay: 0s; }
|
|
1634
|
+
.animot-presenter:hover .animot-arrow:disabled { opacity: 0.3; cursor: not-allowed; }
|
|
1635
|
+
.animot-arrow:hover:not(:disabled) { background: rgba(0,0,0,0.7); }
|
|
1636
|
+
.animot-arrow svg { width: 20px; height: 20px; }
|
|
1637
|
+
.animot-arrow-left { left: 8px; }
|
|
1638
|
+
.animot-arrow-right { right: 8px; }
|
|
1639
|
+
|
|
1640
|
+
/* Progress bar */
|
|
1641
|
+
.animot-progress-bar {
|
|
1642
|
+
position: absolute; bottom: 0; left: 0; right: 0; height: 3px;
|
|
1643
|
+
background: rgba(255,255,255,0.1); z-index: 100;
|
|
1644
|
+
opacity: 0; transition: opacity 0.3s 0.15s;
|
|
1645
|
+
}
|
|
1646
|
+
.animot-presenter:hover .animot-progress-bar { opacity: 1; transition-delay: 0s; }
|
|
1647
|
+
.animot-progress-fill { height: 100%; background: linear-gradient(135deg, #7c3aed, #ec4899); transition: width 0.6s ease; }
|
|
1648
|
+
|
|
1649
|
+
/* Loading / Error */
|
|
1650
|
+
.animot-loading { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
|
|
1651
|
+
.animot-spinner { width: 32px; height: 32px; border: 3px solid rgba(255,255,255,0.2); border-top-color: #7c3aed; border-radius: 50%; animation: animot-spin 0.8s linear infinite; }
|
|
1652
|
+
@keyframes animot-spin { to { transform: rotate(360deg); } }
|
|
1653
|
+
.animot-error { color: #ef4444; padding: 20px; text-align: center; font-family: system-ui, sans-serif; }
|
|
1654
|
+
</style>
|