animot-presenter 0.2.4 → 0.2.6
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/README.md +35 -3
- package/dist/AnimotPresenter.svelte +258 -51
- package/dist/cdn/animot-presenter.css +1 -1
- package/dist/cdn/animot-presenter.esm.js +4209 -4117
- package/dist/cdn/animot-presenter.min.js +9 -9
- package/dist/types.d.ts +14 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -472,7 +472,7 @@ animot-presenter .animot-controls { display: none; }
|
|
|
472
472
|
|
|
473
473
|
## Features
|
|
474
474
|
|
|
475
|
-
- **Morphing animations** — Elements with the same ID across slides smoothly morph position, size, rotation, color, opacity, and
|
|
475
|
+
- **Morphing animations** — Elements with the same ID across slides smoothly morph position, size, rotation, color, opacity, border radius, and CSS filters (blur, brightness, contrast, saturate, grayscale)
|
|
476
476
|
- **Code highlighting** — Syntax highlighting via Shiki with typewriter, highlight-changes, and instant animation modes
|
|
477
477
|
- **Shape morphing** — Rectangles, circles, triangles, stars, hexagons with smooth transitions
|
|
478
478
|
- **Charts** — Animated bar, line, area, pie, and donut charts
|
|
@@ -483,7 +483,8 @@ animot-presenter .animot-controls { display: none; }
|
|
|
483
483
|
- **Transitions** — Fade, slide, zoom, flip, and morphing (none) transition types
|
|
484
484
|
- **Responsive** — Automatically scales to fit any container size
|
|
485
485
|
- **Keyboard navigation** — Arrow keys, spacebar, Home/End
|
|
486
|
-
- **
|
|
486
|
+
- **CSS filters** — Blur, brightness, contrast, saturate, and grayscale on any element, animated across slides
|
|
487
|
+
- **Property sequencing** — Fine-grained control over which properties animate first (including filters via the `blur` property sequence)
|
|
487
488
|
|
|
488
489
|
## JSON Schema
|
|
489
490
|
|
|
@@ -517,7 +518,12 @@ Animot JSON files follow this structure:
|
|
|
517
518
|
"color": "#ffffff",
|
|
518
519
|
"rotation": 0,
|
|
519
520
|
"visible": true,
|
|
520
|
-
"zIndex": 1
|
|
521
|
+
"zIndex": 1,
|
|
522
|
+
"blur": 0,
|
|
523
|
+
"brightness": 100,
|
|
524
|
+
"contrast": 100,
|
|
525
|
+
"saturate": 100,
|
|
526
|
+
"grayscale": 0
|
|
521
527
|
}
|
|
522
528
|
]
|
|
523
529
|
},
|
|
@@ -563,6 +569,32 @@ You can replace the base64 data URL with a remote or local URL:
|
|
|
563
569
|
|
|
564
570
|
This lets you keep your JSON files lightweight by hosting images separately instead of embedding them as base64. The same applies to text elements with `backgroundImage` and background `image` fields.
|
|
565
571
|
|
|
572
|
+
### CSS Filters
|
|
573
|
+
|
|
574
|
+
All element types support CSS filter properties that animate smoothly between slides:
|
|
575
|
+
|
|
576
|
+
```json
|
|
577
|
+
{
|
|
578
|
+
"id": "hero-image",
|
|
579
|
+
"type": "image",
|
|
580
|
+
"blur": 5,
|
|
581
|
+
"brightness": 120,
|
|
582
|
+
"contrast": 110,
|
|
583
|
+
"saturate": 80,
|
|
584
|
+
"grayscale": 0
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
| Property | Range | Default | Description |
|
|
589
|
+
|--------------|---------|---------|----------------------------------|
|
|
590
|
+
| `blur` | 0–20 | 0 | Gaussian blur in pixels |
|
|
591
|
+
| `brightness` | 0–200 | 100 | Brightness percentage (100 = normal) |
|
|
592
|
+
| `contrast` | 0–200 | 100 | Contrast percentage (100 = normal) |
|
|
593
|
+
| `saturate` | 0–200 | 100 | Saturation percentage (100 = normal) |
|
|
594
|
+
| `grayscale` | 0–100 | 0 | Grayscale percentage (0 = none) |
|
|
595
|
+
|
|
596
|
+
Set different filter values on the same element across slides to create smooth animated transitions (e.g., blur 10 on slide 1 to blur 0 on slide 2 for a reveal effect). Use the `blur` property sequence in `animationConfig.propertySequences` to control filter animation timing independently.
|
|
597
|
+
|
|
566
598
|
> **Note:** Remote URLs must allow cross-origin requests (CORS) if served from a different domain.
|
|
567
599
|
|
|
568
600
|
## Bundle Size
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
strokeWidth: TweenValue | null;
|
|
31
31
|
shapeMorph: TweenValue | null;
|
|
32
32
|
motionPathProgress: TweenValue | null;
|
|
33
|
+
blur: TweenValue;
|
|
34
|
+
brightness: TweenValue;
|
|
35
|
+
contrast: TweenValue;
|
|
36
|
+
saturate: TweenValue;
|
|
37
|
+
grayscale: TweenValue;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
interface ShapeMorphState { fromType: string; toType: string; }
|
|
@@ -106,6 +111,22 @@
|
|
|
106
111
|
return { x: points[0].x, y: points[0].y, angle: 0 };
|
|
107
112
|
}
|
|
108
113
|
|
|
114
|
+
function computeMotionPathPosition(
|
|
115
|
+
mpPoint: { x: number; y: number; angle: number },
|
|
116
|
+
startPoint: { x: number; y: number; angle: number },
|
|
117
|
+
animX: number, animY: number, animW: number, animH: number
|
|
118
|
+
): { x: number; y: number } {
|
|
119
|
+
const offsetX = (animX + animW / 2) - startPoint.x;
|
|
120
|
+
const offsetY = (animY + animH / 2) - startPoint.y;
|
|
121
|
+
const angleDelta = (mpPoint.angle - startPoint.angle) * Math.PI / 180;
|
|
122
|
+
const cos = Math.cos(angleDelta);
|
|
123
|
+
const sin = Math.sin(angleDelta);
|
|
124
|
+
return {
|
|
125
|
+
x: mpPoint.x + offsetX * cos - offsetY * sin - animW / 2,
|
|
126
|
+
y: mpPoint.y + offsetX * sin + offsetY * cos - animH / 2
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
let {
|
|
110
131
|
src, data, autoplay = false, loop = false, controls = true, arrows = false,
|
|
111
132
|
progress: showProgress = true, keyboard = true, duration: durationOverride,
|
|
@@ -205,8 +226,26 @@
|
|
|
205
226
|
textTypewriterState = new Map(textTypewriterState);
|
|
206
227
|
}
|
|
207
228
|
|
|
208
|
-
//
|
|
209
|
-
function
|
|
229
|
+
// Build SVG path for 3+ control points using Catmull-Rom spline
|
|
230
|
+
function buildCatmullRomPath(start: {x:number,y:number}, cps: {x:number,y:number}[], end: {x:number,y:number}): string {
|
|
231
|
+
const pts = [start, ...cps, end];
|
|
232
|
+
let d = `M ${pts[0].x} ${pts[0].y}`;
|
|
233
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
234
|
+
const p0 = pts[i === 0 ? 0 : i - 1];
|
|
235
|
+
const p1 = pts[i];
|
|
236
|
+
const p2 = pts[i + 1];
|
|
237
|
+
const p3 = pts[i + 2 < pts.length ? i + 2 : pts.length - 1];
|
|
238
|
+
const c1x = p1.x + (p2.x - p0.x) / 6;
|
|
239
|
+
const c1y = p1.y + (p2.y - p0.y) / 6;
|
|
240
|
+
const c2x = p2.x - (p3.x - p1.x) / 6;
|
|
241
|
+
const c2y = p2.y - (p3.y - p1.y) / 6;
|
|
242
|
+
d += ` C ${c1x} ${c1y} ${c2x} ${c2y} ${p2.x} ${p2.y}`;
|
|
243
|
+
}
|
|
244
|
+
return d;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Arrow draw/undraw/draw-undraw animation action
|
|
248
|
+
function animateStyledArrowDraw(node: SVGPathElement, params: { enabled: boolean; mode: string; duration: number; dashPattern: string; startX: number; endX: number; slideIndex: number }) {
|
|
210
249
|
let lastSlideIndex = params.slideIndex;
|
|
211
250
|
let animationId: number | null = null;
|
|
212
251
|
function runAnimation() {
|
|
@@ -215,24 +254,63 @@
|
|
|
215
254
|
const svg = node.closest('svg') as SVGSVGElement | null;
|
|
216
255
|
if (!svg) return;
|
|
217
256
|
const goesLeftToRight = params.endX >= params.startX;
|
|
218
|
-
|
|
219
|
-
const startTime = performance.now();
|
|
257
|
+
const mode = params.mode;
|
|
220
258
|
const dur = params.duration;
|
|
259
|
+
const startTime = performance.now();
|
|
260
|
+
if (mode === 'draw' || mode === 'draw-undraw') {
|
|
261
|
+
svg.style.clipPath = goesLeftToRight ? 'inset(0 100% 0 0)' : 'inset(0 0 0 100%)';
|
|
262
|
+
} else if (mode === 'undraw') {
|
|
263
|
+
svg.style.clipPath = 'none';
|
|
264
|
+
}
|
|
221
265
|
function animate(currentTime: number) {
|
|
222
266
|
const elapsed = currentTime - startTime;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
267
|
+
if (mode === 'draw') {
|
|
268
|
+
const progress = Math.min(elapsed / dur, 1);
|
|
269
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
270
|
+
const inset = 100 * (1 - eased);
|
|
271
|
+
svg!.style.clipPath = goesLeftToRight ? `inset(0 ${inset}% 0 0)` : `inset(0 0 0 ${inset}%)`;
|
|
272
|
+
if (progress < 1) { animationId = requestAnimationFrame(animate); }
|
|
273
|
+
else { svg!.style.clipPath = 'none'; animationId = null; }
|
|
274
|
+
} else if (mode === 'undraw') {
|
|
275
|
+
const progress = Math.min(elapsed / dur, 1);
|
|
276
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
277
|
+
const inset = 100 * eased;
|
|
278
|
+
svg!.style.clipPath = goesLeftToRight ? `inset(0 0 0 ${inset}%)` : `inset(0 ${inset}% 0 0)`;
|
|
279
|
+
if (progress < 1) { animationId = requestAnimationFrame(animate); }
|
|
280
|
+
else { svg!.style.clipPath = 'inset(0 0 0 100%)'; animationId = null; }
|
|
281
|
+
} else if (mode === 'draw-undraw') {
|
|
282
|
+
const halfDur = dur / 2;
|
|
283
|
+
if (elapsed < halfDur) {
|
|
284
|
+
const progress = Math.min(elapsed / halfDur, 1);
|
|
285
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
286
|
+
const inset = 100 * (1 - eased);
|
|
287
|
+
svg!.style.clipPath = goesLeftToRight ? `inset(0 ${inset}% 0 0)` : `inset(0 0 0 ${inset}%)`;
|
|
288
|
+
animationId = requestAnimationFrame(animate);
|
|
289
|
+
} else {
|
|
290
|
+
const progress = Math.min((elapsed - halfDur) / halfDur, 1);
|
|
291
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
292
|
+
const inset = 100 * eased;
|
|
293
|
+
svg!.style.clipPath = goesLeftToRight ? `inset(0 0 0 ${inset}%)` : `inset(0 ${inset}% 0 0)`;
|
|
294
|
+
if (progress < 1) { animationId = requestAnimationFrame(animate); }
|
|
295
|
+
else { svg!.style.clipPath = 'inset(0 0 0 100%)'; animationId = null; }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
229
298
|
}
|
|
230
299
|
animationId = requestAnimationFrame(animate);
|
|
231
300
|
}
|
|
232
301
|
runAnimation();
|
|
233
302
|
return {
|
|
234
303
|
update(newParams: typeof params) {
|
|
235
|
-
|
|
304
|
+
const slideChanged = newParams.slideIndex !== lastSlideIndex;
|
|
305
|
+
params = newParams;
|
|
306
|
+
if (!params.enabled) {
|
|
307
|
+
if (animationId) { cancelAnimationFrame(animationId); animationId = null; }
|
|
308
|
+
const svg = node.closest('svg') as SVGSVGElement | null;
|
|
309
|
+
if (svg) svg.style.clipPath = '';
|
|
310
|
+
lastSlideIndex = newParams.slideIndex;
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (slideChanged) { lastSlideIndex = newParams.slideIndex; runAnimation(); }
|
|
236
314
|
},
|
|
237
315
|
destroy() { if (animationId) cancelAnimationFrame(animationId); }
|
|
238
316
|
};
|
|
@@ -254,7 +332,7 @@
|
|
|
254
332
|
for (const element of slide.canvas.elements) {
|
|
255
333
|
if (!animatedElements.has(element.id)) {
|
|
256
334
|
const inCurrent = getElementInSlide(currentSlide, element.id);
|
|
257
|
-
const startOpacity = inCurrent ? 1 : 0;
|
|
335
|
+
const startOpacity = inCurrent ? ((inCurrent as any).opacity ?? 1) : 0;
|
|
258
336
|
const br = (element as any).borderRadius ?? 0;
|
|
259
337
|
const isShape = element.type === 'shape';
|
|
260
338
|
const shapeEl = isShape ? element as ShapeElement : null;
|
|
@@ -278,7 +356,12 @@
|
|
|
278
356
|
strokeColor: shapeEl ? tween(shapeEl.strokeColor, { duration: 500 }) : null,
|
|
279
357
|
strokeWidth: shapeEl ? tween(shapeEl.strokeWidth, { duration: 500 }) : null,
|
|
280
358
|
shapeMorph: shapeEl ? tween(1, { duration: 500 }) : null,
|
|
281
|
-
motionPathProgress: element.motionPathConfig ? tween(0, { duration: 500 }) : null
|
|
359
|
+
motionPathProgress: element.motionPathConfig ? tween(0, { duration: 500 }) : null,
|
|
360
|
+
blur: tween(element.blur ?? 0, { duration: 500 }),
|
|
361
|
+
brightness: tween(element.brightness ?? 100, { duration: 500 }),
|
|
362
|
+
contrast: tween(element.contrast ?? 100, { duration: 500 }),
|
|
363
|
+
saturate: tween(element.saturate ?? 100, { duration: 500 }),
|
|
364
|
+
grayscale: tween(element.grayscale ?? 0, { duration: 500 })
|
|
282
365
|
});
|
|
283
366
|
const currentSlideEl = getElementInSlide(currentSlide, element.id);
|
|
284
367
|
elementContent.set(element.id, JSON.parse(JSON.stringify(currentSlideEl || element)));
|
|
@@ -315,11 +398,14 @@
|
|
|
315
398
|
const shouldLoop = element.motionPathConfig.loop;
|
|
316
399
|
|
|
317
400
|
if (shouldLoop) {
|
|
401
|
+
const laps = element.motionPathConfig.laps ?? 0;
|
|
318
402
|
(async () => {
|
|
319
|
-
|
|
403
|
+
let lap = 0;
|
|
404
|
+
while (!signal.aborted && (laps === 0 || lap < laps)) {
|
|
320
405
|
await animated.motionPathProgress!.to(0, { duration: 0 });
|
|
321
406
|
await animated.motionPathProgress!.to(1, { duration, easing });
|
|
322
|
-
|
|
407
|
+
lap++;
|
|
408
|
+
if (!signal.aborted && (laps === 0 || lap < laps)) await new Promise(r => setTimeout(r, 50));
|
|
323
409
|
}
|
|
324
410
|
})();
|
|
325
411
|
} else {
|
|
@@ -368,8 +454,13 @@
|
|
|
368
454
|
animated.skewX.to(targetEl.skewX ?? 0, { duration: 0 }); animated.skewY.to(targetEl.skewY ?? 0, { duration: 0 });
|
|
369
455
|
animated.tiltX.to(targetEl.tiltX ?? 0, { duration: 0 }); animated.tiltY.to(targetEl.tiltY ?? 0, { duration: 0 });
|
|
370
456
|
animated.perspective.to(targetEl.perspective ?? 1000, { duration: 0 });
|
|
371
|
-
animated.opacity.to(1, { duration: 0 });
|
|
457
|
+
animated.opacity.to((targetEl as any).opacity ?? 1, { duration: 0 });
|
|
372
458
|
animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: 0 });
|
|
459
|
+
animated.blur.to(targetEl.blur ?? 0, { duration: 0 });
|
|
460
|
+
animated.brightness.to(targetEl.brightness ?? 100, { duration: 0 });
|
|
461
|
+
animated.contrast.to(targetEl.contrast ?? 100, { duration: 0 });
|
|
462
|
+
animated.saturate.to(targetEl.saturate ?? 100, { duration: 0 });
|
|
463
|
+
animated.grayscale.to(targetEl.grayscale ?? 0, { duration: 0 });
|
|
373
464
|
if (targetEl.type === 'text') animated.fontSize?.to((targetEl as TextElement).fontSize, { duration: 0 });
|
|
374
465
|
if (targetEl.type === 'shape') {
|
|
375
466
|
const s = targetEl as ShapeElement;
|
|
@@ -433,7 +524,12 @@
|
|
|
433
524
|
await animated.tiltY.to(currentEl.tiltY ?? 0, { duration: 0 });
|
|
434
525
|
await animated.perspective.to(currentEl.perspective ?? 1000, { duration: 0 });
|
|
435
526
|
await animated.borderRadius.to((currentEl as any).borderRadius ?? 0, { duration: 0 });
|
|
436
|
-
await animated.
|
|
527
|
+
await animated.blur.to(currentEl.blur ?? 0, { duration: 0 });
|
|
528
|
+
await animated.brightness.to(currentEl.brightness ?? 100, { duration: 0 });
|
|
529
|
+
await animated.contrast.to(currentEl.contrast ?? 100, { duration: 0 });
|
|
530
|
+
await animated.saturate.to(currentEl.saturate ?? 100, { duration: 0 });
|
|
531
|
+
await animated.grayscale.to(currentEl.grayscale ?? 0, { duration: 0 });
|
|
532
|
+
await animated.opacity.to((currentEl as any).opacity ?? 1, { duration: 0 });
|
|
437
533
|
if (currentEl.type === 'text' && animated.fontSize) await animated.fontSize.to((currentEl as TextElement).fontSize, { duration: 0 });
|
|
438
534
|
if (currentEl.type === 'shape') {
|
|
439
535
|
const s = currentEl as ShapeElement;
|
|
@@ -493,7 +589,18 @@
|
|
|
493
589
|
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 })); }
|
|
494
590
|
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 })); }
|
|
495
591
|
if (!sequencedProps.has('borderRadius')) anims.push(animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: elementDuration, easing }));
|
|
592
|
+
if (!sequencedProps.has('blur')) {
|
|
593
|
+
animated.blur.to(targetEl.blur ?? 0, { duration: elementDuration, easing });
|
|
594
|
+
animated.brightness.to(targetEl.brightness ?? 100, { duration: elementDuration, easing });
|
|
595
|
+
animated.contrast.to(targetEl.contrast ?? 100, { duration: elementDuration, easing });
|
|
596
|
+
animated.saturate.to(targetEl.saturate ?? 100, { duration: elementDuration, easing });
|
|
597
|
+
animated.grayscale.to(targetEl.grayscale ?? 0, { duration: elementDuration, easing });
|
|
598
|
+
}
|
|
496
599
|
if (!sequencedProps.has('perspective')) anims.push(animated.perspective.to(targetEl.perspective ?? 1000, { duration: elementDuration, easing }));
|
|
600
|
+
if (!sequencedProps.has('opacity')) {
|
|
601
|
+
const targetOpacity = (targetEl as any).opacity ?? 1;
|
|
602
|
+
if (animated.opacity.current !== targetOpacity) anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration, easing }));
|
|
603
|
+
}
|
|
497
604
|
const sortedSeqs = [...propertySequences].sort((a, b) => a.order - b.order);
|
|
498
605
|
let cumulativeDelay = 0;
|
|
499
606
|
for (const seq of sortedSeqs) {
|
|
@@ -506,6 +613,13 @@
|
|
|
506
613
|
else if (seq.property === 'skew') { animated.skewX.to(targetEl.skewX ?? 0, { duration: seqDuration, easing }); animated.skewY.to(targetEl.skewY ?? 0, { duration: seqDuration, easing }); }
|
|
507
614
|
else if (seq.property === 'size') { animated.width.to(targetEl.size.width, { duration: seqDuration, easing }); animated.height.to(targetEl.size.height, { duration: seqDuration, easing }); }
|
|
508
615
|
else if (seq.property === 'borderRadius') animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: seqDuration, easing });
|
|
616
|
+
else if (seq.property === 'blur') {
|
|
617
|
+
animated.blur.to(targetEl.blur ?? 0, { duration: seqDuration, easing });
|
|
618
|
+
animated.brightness.to(targetEl.brightness ?? 100, { duration: seqDuration, easing });
|
|
619
|
+
animated.contrast.to(targetEl.contrast ?? 100, { duration: seqDuration, easing });
|
|
620
|
+
animated.saturate.to(targetEl.saturate ?? 100, { duration: seqDuration, easing });
|
|
621
|
+
animated.grayscale.to(targetEl.grayscale ?? 0, { duration: seqDuration, easing });
|
|
622
|
+
}
|
|
509
623
|
else if (seq.property === 'color' && targetEl.type === 'shape') {
|
|
510
624
|
const s = targetEl as ShapeElement;
|
|
511
625
|
animated.fillColor?.to(s.fillColor, { duration: seqDuration, easing });
|
|
@@ -513,6 +627,7 @@
|
|
|
513
627
|
animated.strokeWidth?.to(s.strokeWidth, { duration: seqDuration, easing });
|
|
514
628
|
}
|
|
515
629
|
else if (seq.property === 'perspective') animated.perspective.to(targetEl.perspective ?? 1000, { duration: seqDuration, easing });
|
|
630
|
+
else if (seq.property === 'opacity') animated.opacity.to((targetEl as any).opacity ?? 1, { duration: seqDuration, easing });
|
|
516
631
|
}, seqDelay);
|
|
517
632
|
cumulativeDelay = seqDelay + seqDuration;
|
|
518
633
|
}
|
|
@@ -529,6 +644,17 @@
|
|
|
529
644
|
anims.push(animated.tiltY.to(targetEl.tiltY ?? 0, { duration: elementDuration, easing }));
|
|
530
645
|
anims.push(animated.perspective.to(targetEl.perspective ?? 1000, { duration: elementDuration, easing }));
|
|
531
646
|
anims.push(animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: elementDuration, easing }));
|
|
647
|
+
anims.push(animated.blur.to(targetEl.blur ?? 0, { duration: elementDuration, easing }));
|
|
648
|
+
anims.push(animated.brightness.to(targetEl.brightness ?? 100, { duration: elementDuration, easing }));
|
|
649
|
+
anims.push(animated.contrast.to(targetEl.contrast ?? 100, { duration: elementDuration, easing }));
|
|
650
|
+
anims.push(animated.saturate.to(targetEl.saturate ?? 100, { duration: elementDuration, easing }));
|
|
651
|
+
anims.push(animated.grayscale.to(targetEl.grayscale ?? 0, { duration: elementDuration, easing }));
|
|
652
|
+
// Opacity interpolation for morphing elements
|
|
653
|
+
const currOpacity = animated.opacity.current;
|
|
654
|
+
const targetOpacity = (targetEl as any).opacity ?? 1;
|
|
655
|
+
if (currOpacity !== targetOpacity) {
|
|
656
|
+
anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration, easing }));
|
|
657
|
+
}
|
|
532
658
|
}
|
|
533
659
|
// Motion path progress — await reset, then animate forward
|
|
534
660
|
if (animated.motionPathProgress && targetEl.motionPathConfig) {
|
|
@@ -575,6 +701,11 @@
|
|
|
575
701
|
anims.push(animated.tiltY.to(targetEl.tiltY ?? 0, { duration: 0 }));
|
|
576
702
|
anims.push(animated.perspective.to(targetEl.perspective ?? 1000, { duration: 0 }));
|
|
577
703
|
anims.push(animated.borderRadius.to((targetEl as any).borderRadius ?? 0, { duration: 0 }));
|
|
704
|
+
anims.push(animated.blur.to(targetEl.blur ?? 0, { duration: 0 }));
|
|
705
|
+
anims.push(animated.brightness.to(targetEl.brightness ?? 100, { duration: 0 }));
|
|
706
|
+
anims.push(animated.contrast.to(targetEl.contrast ?? 100, { duration: 0 }));
|
|
707
|
+
anims.push(animated.saturate.to(targetEl.saturate ?? 100, { duration: 0 }));
|
|
708
|
+
anims.push(animated.grayscale.to(targetEl.grayscale ?? 0, { duration: 0 }));
|
|
578
709
|
if (targetEl.type === 'text' && animated.fontSize) {
|
|
579
710
|
anims.push(animated.fontSize.to((targetEl as TextElement).fontSize, { duration: 0 }));
|
|
580
711
|
}
|
|
@@ -585,10 +716,11 @@
|
|
|
585
716
|
if (animated.strokeWidth) anims.push(animated.strokeWidth.to(s.strokeWidth, { duration: 0 }));
|
|
586
717
|
}
|
|
587
718
|
const entrance = targetEl.animationConfig?.entrance ?? 'fade';
|
|
719
|
+
const targetOpacity = (targetEl as any).opacity ?? 1;
|
|
588
720
|
if (entrance === 'fade') {
|
|
589
|
-
anims.push(animated.opacity.to(
|
|
721
|
+
anims.push(animated.opacity.to(targetOpacity, { duration: elementDuration / 2, easing }));
|
|
590
722
|
} else {
|
|
591
|
-
anims.push(animated.opacity.to(
|
|
723
|
+
anims.push(animated.opacity.to(targetOpacity, { duration: 0 }));
|
|
592
724
|
}
|
|
593
725
|
}
|
|
594
726
|
return anims;
|
|
@@ -659,6 +791,13 @@
|
|
|
659
791
|
elementContent = newElementContent;
|
|
660
792
|
currentSlideIndex = targetIndex;
|
|
661
793
|
isTransitioning = false;
|
|
794
|
+
// Ensure elements not on the new slide are fully hidden
|
|
795
|
+
const newSlide = slides[targetIndex];
|
|
796
|
+
for (const elementId of allElementIds) {
|
|
797
|
+
const onSlide = getElementInSlide(newSlide, elementId);
|
|
798
|
+
const animated = animatedElements.get(elementId);
|
|
799
|
+
if (!onSlide && animated) { animated.opacity.to(0, { duration: 0 }); }
|
|
800
|
+
}
|
|
662
801
|
animateMotionPaths(slides[targetIndex]);
|
|
663
802
|
onslidechange?.(targetIndex, slides.length);
|
|
664
803
|
if (targetIndex === slides.length - 1 && !loop) oncomplete?.();
|
|
@@ -877,8 +1016,14 @@
|
|
|
877
1016
|
{@const mpElement = mpConfig ? currentSlide?.canvas.elements.find(el => el.id === mpConfig.motionPathId) as MotionPathElement | undefined : undefined}
|
|
878
1017
|
{@const mpProgress = animated?.motionPathProgress?.current ?? 0}
|
|
879
1018
|
{@const mpPoint = mpElement && mpConfig ? getPresenterPointOnPath(mpElement.points, mpElement.closed, (mpConfig.startPercent + (mpConfig.endPercent - mpConfig.startPercent) * mpProgress) / 100) : null}
|
|
880
|
-
{@const
|
|
881
|
-
{@const
|
|
1019
|
+
{@const mpStartPoint = mpElement && mpConfig ? getPresenterPointOnPath(mpElement.points, mpElement.closed, mpConfig.startPercent / 100) : null}
|
|
1020
|
+
{@const mpPos = mpPoint && mpStartPoint && animated
|
|
1021
|
+
? computeMotionPathPosition(mpPoint, mpStartPoint,
|
|
1022
|
+
animated.x.current, animated.y.current,
|
|
1023
|
+
animated.width.current, animated.height.current)
|
|
1024
|
+
: null}
|
|
1025
|
+
{@const elemX = mpPos ? mpPos.x : (animated?.x.current ?? 0)}
|
|
1026
|
+
{@const elemY = mpPos ? mpPos.y : (animated?.y.current ?? 0)}
|
|
882
1027
|
{@const mpRotation = mpPoint && mpConfig?.autoRotate
|
|
883
1028
|
? mpPoint.angle + (mpConfig.orientationOffset ?? 0)
|
|
884
1029
|
: null}
|
|
@@ -899,22 +1044,52 @@
|
|
|
899
1044
|
style:--float-speed="{hasFloat ? computeFloatSpeed(floatCfg, floatGroupId || elementId) : 3}s"
|
|
900
1045
|
style:--float-delay="{hashFraction(floatGroupId || elementId, 3) * 2}s"
|
|
901
1046
|
style:animation-name={hasFloat ? getFloatAnimName(floatCfg!.direction, floatGroupId || elementId) : 'none'}
|
|
1047
|
+
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'; })()}
|
|
902
1048
|
>
|
|
903
1049
|
{#if element.type === 'code'}
|
|
904
1050
|
{@const codeEl = element as CodeElement}
|
|
905
1051
|
{@const morphState = codeMorphState.get(codeEl.id)}
|
|
906
|
-
<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">
|
|
1052
|
+
<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'}>
|
|
907
1053
|
{#if codeEl.showHeader}
|
|
908
|
-
<div class="animot-code-header" class:macos={codeEl.headerStyle === 'macos'}>
|
|
1054
|
+
<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">
|
|
909
1055
|
{#if codeEl.headerStyle === 'macos'}
|
|
910
1056
|
<div class="animot-window-controls">
|
|
911
1057
|
<span class="animot-control close"></span>
|
|
912
1058
|
<span class="animot-control minimize"></span>
|
|
913
1059
|
<span class="animot-control maximize"></span>
|
|
914
1060
|
</div>
|
|
1061
|
+
{:else if codeEl.headerStyle === 'windows'}
|
|
1062
|
+
<div class="animot-window-controls">
|
|
1063
|
+
<span class="animot-control win-minimize">
|
|
1064
|
+
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M2 5h6" stroke="currentColor" stroke-width="1.2"/></svg>
|
|
1065
|
+
</span>
|
|
1066
|
+
<span class="animot-control win-maximize">
|
|
1067
|
+
<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>
|
|
1068
|
+
</span>
|
|
1069
|
+
<span class="animot-control win-close">
|
|
1070
|
+
<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>
|
|
1071
|
+
</span>
|
|
1072
|
+
</div>
|
|
915
1073
|
{/if}
|
|
916
|
-
<
|
|
1074
|
+
<div class="animot-filename-tab" style:border-radius="{codeEl.tabRadius ?? 6}px">
|
|
1075
|
+
<svg class="animot-file-icon" width="14" height="14" viewBox="0 0 16 16" fill="none">
|
|
1076
|
+
<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"/>
|
|
1077
|
+
<path d="M9.5 1v3.5H13" stroke="currentColor" stroke-width="1.2" opacity="0.5"/>
|
|
1078
|
+
</svg>
|
|
1079
|
+
<span class="animot-filename">{codeEl.filename}</span>
|
|
1080
|
+
</div>
|
|
1081
|
+
<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); }}>
|
|
1082
|
+
<span class="animot-copy-label">Copy</span><span class="animot-copied-label">Copied!</span>
|
|
1083
|
+
<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>
|
|
1084
|
+
<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>
|
|
1085
|
+
</button>
|
|
917
1086
|
</div>
|
|
1087
|
+
{:else}
|
|
1088
|
+
<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); }}>
|
|
1089
|
+
<span class="animot-copy-label">Copy</span><span class="animot-copied-label">Copied!</span>
|
|
1090
|
+
<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>
|
|
1091
|
+
<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>
|
|
1092
|
+
</button>
|
|
918
1093
|
{/if}
|
|
919
1094
|
<div class="animot-code-content">
|
|
920
1095
|
<div class="animot-highlighted-code">
|
|
@@ -951,7 +1126,6 @@
|
|
|
951
1126
|
style:border-radius="{textEl.borderRadius}px"
|
|
952
1127
|
style:text-align={textEl.textAlign}
|
|
953
1128
|
style:justify-content={textEl.textAlign === 'center' ? 'center' : textEl.textAlign === 'right' ? 'flex-end' : 'flex-start'}
|
|
954
|
-
style:opacity={textEl.opacity ?? 1}
|
|
955
1129
|
style:-webkit-text-stroke={textEl.textStroke?.enabled ? `${textEl.textStroke.width}px ${textEl.textStroke.color}` : '0'}
|
|
956
1130
|
style:text-shadow={textEl.textShadow?.enabled ? `${textEl.textShadow.offsetX}px ${textEl.textShadow.offsetY}px ${textEl.textShadow.blur}px ${textEl.textShadow.color}` : 'none'}
|
|
957
1131
|
>
|
|
@@ -960,25 +1134,27 @@
|
|
|
960
1134
|
{:else if element.type === 'arrow'}
|
|
961
1135
|
{@const arrowEl = element as ArrowElement}
|
|
962
1136
|
{@const cp = arrowEl.controlPoints || []}
|
|
963
|
-
{@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}` : `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}`}
|
|
964
|
-
{@const
|
|
1137
|
+
{@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)}
|
|
1138
|
+
{@const lastCp = cp.length > 0 ? cp[cp.length - 1] : arrowEl.startPoint}
|
|
1139
|
+
{@const endAngle = Math.atan2(arrowEl.endPoint.y - lastCp.y, arrowEl.endPoint.x - lastCp.x)}
|
|
965
1140
|
{@const headAngle = Math.PI / 6}
|
|
966
1141
|
{@const headSize = arrowEl.headSize}
|
|
967
1142
|
{@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)}`}
|
|
968
1143
|
{@const arrowAnimMode = arrowEl.animation?.mode ?? 'none'}
|
|
969
1144
|
{@const arrowAnimDuration = arrowEl.animation?.duration ?? 500}
|
|
970
1145
|
{@const isStyledArrow = arrowEl.style !== 'solid'}
|
|
1146
|
+
{@const isDrawType = arrowAnimMode === 'draw' || arrowAnimMode === 'undraw' || arrowAnimMode === 'draw-undraw'}
|
|
971
1147
|
{@const baseDashArray = arrowEl.style === 'dashed' ? '10,5' : arrowEl.style === 'dotted' ? '2,5' : 'none'}
|
|
972
|
-
<svg class="animot-arrow-element" class:arrow-animate-draw={arrowAnimMode === 'draw' && !isStyledArrow} class:arrow-animate-grow={arrowAnimMode === 'grow'} viewBox="0 0 {arrowEl.size.width} {arrowEl.size.height}" preserveAspectRatio="none" style="--arrow-anim-duration: {arrowAnimDuration}ms;">
|
|
973
|
-
<path class="arrow-path" d={pathD} fill="none" stroke={arrowEl.color} stroke-width={arrowEl.strokeWidth} stroke-dasharray={baseDashArray} stroke-linecap="round" stroke-linejoin="round" use:animateStyledArrowDraw={{ enabled:
|
|
1148
|
+
<svg class="animot-arrow-element" class:arrow-animate-draw={arrowAnimMode === 'draw' && !isStyledArrow} class:arrow-animate-undraw={arrowAnimMode === 'undraw' && !isStyledArrow} class:arrow-animate-draw-undraw={arrowAnimMode === 'draw-undraw' && !isStyledArrow} class:arrow-animate-grow={arrowAnimMode === 'grow'} viewBox="0 0 {arrowEl.size.width} {arrowEl.size.height}" preserveAspectRatio="none" style="--arrow-anim-duration: {arrowAnimDuration}ms;">
|
|
1149
|
+
<path class="arrow-path" d={pathD} fill="none" stroke={arrowEl.color} stroke-width={arrowEl.strokeWidth} stroke-dasharray={baseDashArray} stroke-linecap="round" stroke-linejoin="round" use:animateStyledArrowDraw={{ enabled: isDrawType && isStyledArrow, mode: arrowAnimMode, duration: arrowAnimDuration, dashPattern: baseDashArray, startX: arrowEl.startPoint.x, endX: arrowEl.endPoint.x, slideIndex: currentSlideIndex }} />
|
|
974
1150
|
{#if arrowEl.showHead !== false}
|
|
975
|
-
<path class="arrow-head" class:arrow-head-styled-draw={arrowAnimMode === '
|
|
1151
|
+
<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;` : ''} />
|
|
976
1152
|
{/if}
|
|
977
1153
|
</svg>
|
|
978
1154
|
{:else if element.type === 'image'}
|
|
979
1155
|
{@const imgEl = element as ImageElement}
|
|
980
1156
|
{@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'}
|
|
981
|
-
<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:
|
|
1157
|
+
<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'} />
|
|
982
1158
|
{:else if element.type === 'shape'}
|
|
983
1159
|
{@const shapeEl = element as ShapeElement}
|
|
984
1160
|
{@const animFill = animated.fillColor?.current ?? shapeEl.fillColor}
|
|
@@ -988,15 +1164,15 @@
|
|
|
988
1164
|
{@const morphProgress = animated.shapeMorph?.current ?? 1}
|
|
989
1165
|
{@const effectiveShapeType = mState ? (morphProgress >= 1 ? mState.toType : (morphProgress <= 0 ? mState.fromType : null)) : shapeEl.shapeType}
|
|
990
1166
|
{@const isMorphing = mState && morphProgress > 0 && morphProgress < 1}
|
|
991
|
-
<svg class="animot-shape-element" viewBox="0 0 {animated.width.current} {animated.height.current}" style:
|
|
1167
|
+
<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'}>
|
|
992
1168
|
{#if isMorphing}
|
|
993
1169
|
{@const w = animated.width.current}
|
|
994
1170
|
{@const h = animated.height.current}
|
|
995
1171
|
{@const sw = animStrokeWidth}
|
|
996
|
-
<g style:opacity={1 - morphProgress}>{@html renderShape(mState!.fromType, w, h, animated.borderRadius.current, animFill, animStroke, sw)}</g>
|
|
997
|
-
<g style:opacity={morphProgress}>{@html renderShape(mState!.toType, w, h, animated.borderRadius.current, animFill, animStroke, sw)}</g>
|
|
1172
|
+
<g style:opacity={1 - morphProgress}>{@html renderShape(mState!.fromType, w, h, animated.borderRadius.current, animFill, animStroke, sw, shapeEl.strokeStyle, shapeEl.strokeDashGap)}</g>
|
|
1173
|
+
<g style:opacity={morphProgress}>{@html renderShape(mState!.toType, w, h, animated.borderRadius.current, animFill, animStroke, sw, shapeEl.strokeStyle, shapeEl.strokeDashGap)}</g>
|
|
998
1174
|
{:else}
|
|
999
|
-
{@html renderShape(effectiveShapeType ?? shapeEl.shapeType, animated.width.current, animated.height.current, animated.borderRadius.current, animFill, animStroke, animStrokeWidth)}
|
|
1175
|
+
{@html renderShape(effectiveShapeType ?? shapeEl.shapeType, animated.width.current, animated.height.current, animated.borderRadius.current, animFill, animStroke, animStrokeWidth, shapeEl.strokeStyle, shapeEl.strokeDashGap)}
|
|
1000
1176
|
{/if}
|
|
1001
1177
|
</svg>
|
|
1002
1178
|
{:else if element.type === 'counter'}
|
|
@@ -1008,7 +1184,7 @@
|
|
|
1008
1184
|
{:else if element.type === 'svg'}
|
|
1009
1185
|
{@const svgEl = element as SvgElement}
|
|
1010
1186
|
{@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 }; })()}
|
|
1011
|
-
<div class="animot-svg-element"
|
|
1187
|
+
<div class="animot-svg-element">
|
|
1012
1188
|
<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">
|
|
1013
1189
|
<g style={svgEl.color ? `fill:${svgEl.color};stroke:${svgEl.color}` : ''}>
|
|
1014
1190
|
{@html svgParsed.inner}
|
|
@@ -1018,7 +1194,7 @@
|
|
|
1018
1194
|
{:else if element.type === 'motionPath'}
|
|
1019
1195
|
{@const mpEl = element as MotionPathElement}
|
|
1020
1196
|
{#if mpEl.showInPresentation}
|
|
1021
|
-
<svg width="100%" height="100%" style="position:absolute;top:0;left:0;pointer-events:none;overflow:visible;">
|
|
1197
|
+
<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;">
|
|
1022
1198
|
<path d={buildPresenterPathD(mpEl.points, mpEl.closed)} stroke={mpEl.pathColor} stroke-width={mpEl.pathWidth} fill="none" stroke-dasharray="8 4" />
|
|
1023
1199
|
</svg>
|
|
1024
1200
|
{/if}
|
|
@@ -1064,21 +1240,29 @@
|
|
|
1064
1240
|
</div>
|
|
1065
1241
|
|
|
1066
1242
|
<script module lang="ts">
|
|
1067
|
-
function renderShape(type: string, w: number, h: number, br: number, fill: string, stroke: string, sw: number): string {
|
|
1243
|
+
function renderShape(type: string, w: number, h: number, br: number, fill: string, stroke: string, sw: number, strokeStyle?: string, strokeDashGap?: number): string {
|
|
1244
|
+
let dashAttr = '';
|
|
1245
|
+
if (strokeStyle && strokeStyle !== 'solid') {
|
|
1246
|
+
const s = sw || 1;
|
|
1247
|
+
const gap = strokeDashGap ?? (strokeStyle === 'dashed' ? s * 3 : s * 2);
|
|
1248
|
+
const da = strokeStyle === 'dashed' ? `${s * 3},${gap}` : `${s * 0.1},${gap}`;
|
|
1249
|
+
const lc = strokeStyle === 'dotted' ? 'round' : 'butt';
|
|
1250
|
+
dashAttr = ` stroke-dasharray="${da}" stroke-linecap="${lc}"`;
|
|
1251
|
+
}
|
|
1068
1252
|
switch (type) {
|
|
1069
|
-
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}"/>`;
|
|
1070
|
-
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}"/>`;
|
|
1071
|
-
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}"/>`;
|
|
1072
|
-
case 'triangle': return `<polygon points="${w/2},${sw/2} ${sw/2},${h-sw/2} ${w-sw/2},${h-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" stroke-linejoin="round"/>`;
|
|
1253
|
+
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}/>`;
|
|
1254
|
+
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}/>`;
|
|
1255
|
+
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}/>`;
|
|
1256
|
+
case 'triangle': return `<polygon points="${w/2},${sw/2} ${sw/2},${h-sw/2} ${w-sw/2},${h-sw/2}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr} stroke-linejoin="round"/>`;
|
|
1073
1257
|
case 'star': {
|
|
1074
1258
|
const cx = w/2, cy = h/2, outerR = Math.min(w,h)/2-sw/2, innerR = outerR*0.4;
|
|
1075
1259
|
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(' ');
|
|
1076
|
-
return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" stroke-linejoin="round"/>`;
|
|
1260
|
+
return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr} stroke-linejoin="round"/>`;
|
|
1077
1261
|
}
|
|
1078
1262
|
case 'hexagon': {
|
|
1079
1263
|
const cx = w/2, cy = h/2, r = Math.min(w,h)/2-sw/2;
|
|
1080
1264
|
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(' ');
|
|
1081
|
-
return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}" stroke-linejoin="round"/>`;
|
|
1265
|
+
return `<polygon points="${pts}" fill="${fill}" stroke="${stroke}" stroke-width="${sw}"${dashAttr} stroke-linejoin="round"/>`;
|
|
1082
1266
|
}
|
|
1083
1267
|
default: return '';
|
|
1084
1268
|
}
|
|
@@ -1143,19 +1327,34 @@
|
|
|
1143
1327
|
|
|
1144
1328
|
/* Code */
|
|
1145
1329
|
.animot-code-block {
|
|
1146
|
-
width: 100%; height: 100%;
|
|
1330
|
+
width: 100%; height: 100%; overflow: hidden;
|
|
1147
1331
|
display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
|
1148
1332
|
margin: 0; box-sizing: border-box;
|
|
1149
1333
|
}
|
|
1150
|
-
.animot-code-block.transparent-bg { background: transparent; box-shadow: none; }
|
|
1151
|
-
.animot-code-block.transparent-bg .animot-code-header { background: transparent; border-bottom-color: rgba(255,255,255,0.
|
|
1152
|
-
.animot-code-header { display: flex; align-items: center; gap:
|
|
1153
|
-
.animot-window-controls { display: flex; gap: 8px; }
|
|
1154
|
-
.animot-control { width: 12px; height: 12px; border-radius: 50%; display: block; }
|
|
1334
|
+
.animot-code-block.transparent-bg { background: transparent !important; box-shadow: none; }
|
|
1335
|
+
.animot-code-block.transparent-bg .animot-code-header { background: transparent; border-bottom-color: rgba(255,255,255,0.06); }
|
|
1336
|
+
.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; }
|
|
1337
|
+
.animot-window-controls { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
|
|
1338
|
+
.macos .animot-control { width: 12px; height: 12px; border-radius: 50%; display: block; }
|
|
1155
1339
|
.macos .animot-control.close { background: #ff5f57; }
|
|
1156
1340
|
.macos .animot-control.minimize { background: #febc2e; }
|
|
1157
1341
|
.macos .animot-control.maximize { background: #28c840; }
|
|
1158
|
-
.animot-
|
|
1342
|
+
.windows .animot-window-controls { order: 99; margin-left: auto; gap: 0; }
|
|
1343
|
+
.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); }
|
|
1344
|
+
.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); }
|
|
1345
|
+
.animot-file-icon { flex-shrink: 0; }
|
|
1346
|
+
.animot-filename { color: rgba(255,255,255,0.55); font-size: 12px; line-height: 18px; }
|
|
1347
|
+
.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; }
|
|
1348
|
+
.animot-copy-code-btn:hover { background: rgba(255,255,255,0.12); color: rgba(255,255,255,0.8); }
|
|
1349
|
+
.animot-copy-code-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
|
|
1350
|
+
.animot-copy-code-btn .animot-check-icon { display: none; }
|
|
1351
|
+
.animot-copy-code-btn .animot-copied-label { display: none; }
|
|
1352
|
+
.animot-copy-code-btn.copied .animot-copy-icon { display: none; }
|
|
1353
|
+
.animot-copy-code-btn.copied .animot-copy-label { display: none; }
|
|
1354
|
+
.animot-copy-code-btn.copied .animot-check-icon { display: block; color: #4ade80; }
|
|
1355
|
+
.animot-copy-code-btn.copied .animot-copied-label { display: inline; color: #4ade80; }
|
|
1356
|
+
.animot-copy-code-btn.animot-floating { position: absolute; top: 8px; right: 8px; z-index: 2; }
|
|
1357
|
+
.animot-code-block:hover .animot-copy-code-btn { opacity: 1; }
|
|
1159
1358
|
.animot-code-content { flex: 1; overflow: hidden; position: relative; }
|
|
1160
1359
|
.animot-highlighted-code { width: 100%; height: 100%; }
|
|
1161
1360
|
.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; }
|
|
@@ -1170,11 +1369,19 @@
|
|
|
1170
1369
|
/* Arrow */
|
|
1171
1370
|
.animot-arrow-element { width: 100%; height: 100%; }
|
|
1172
1371
|
.arrow-animate-draw .arrow-path { stroke-dasharray: 1000; stroke-dashoffset: 1000; animation: animot-arrow-draw var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1372
|
+
.arrow-animate-undraw .arrow-path { stroke-dasharray: 1000; stroke-dashoffset: 0; animation: animot-arrow-undraw var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1373
|
+
.arrow-animate-draw-undraw .arrow-path { stroke-dasharray: 1000; stroke-dashoffset: 1000; animation: animot-arrow-draw-undraw var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1173
1374
|
.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); }
|
|
1174
1375
|
.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); }
|
|
1376
|
+
.arrow-animate-undraw .arrow-head, .arrow-head-undraw { opacity: 1; animation: animot-arrow-head-disappear var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1377
|
+
.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; }
|
|
1175
1378
|
.arrow-animate-grow { transform-origin: left center; animation: animot-arrow-grow var(--arrow-anim-duration, 500ms) ease-out forwards; }
|
|
1176
1379
|
@keyframes animot-arrow-draw { to { stroke-dashoffset: 0; } }
|
|
1380
|
+
@keyframes animot-arrow-undraw { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 1000; } }
|
|
1381
|
+
@keyframes animot-arrow-draw-undraw { 0% { stroke-dashoffset: 1000; } 50% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: 1000; } }
|
|
1177
1382
|
@keyframes animot-arrow-head-appear { from { opacity: 0; } to { opacity: 1; } }
|
|
1383
|
+
@keyframes animot-arrow-head-disappear { 0% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }
|
|
1384
|
+
@keyframes animot-arrow-head-draw-undraw { 0% { opacity: 0; } 35% { opacity: 1; } 65% { opacity: 1; } 100% { opacity: 0; } }
|
|
1178
1385
|
@keyframes animot-arrow-grow { from { transform: scaleX(0); opacity: 0; } to { transform: scaleX(1); opacity: 1; } }
|
|
1179
1386
|
|
|
1180
1387
|
/* Image */
|