animot-presenter 0.6.2 → 0.6.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.
@@ -217,6 +217,44 @@
217
217
  0% { opacity: 1; } 100% { opacity: 0; }
218
218
  }
219
219
 
220
+ /* Freehand draw element */
221
+ .animot-draw-element { display: block; width: 100%; height: 100%; overflow: visible; }
222
+
223
+ /* Sticky note element */
224
+ .animot-sticky-element {
225
+ position: relative;
226
+ width: 100%;
227
+ height: 100%;
228
+ display: flex;
229
+ box-sizing: border-box;
230
+ overflow: hidden;
231
+ }
232
+ .animot-sticky-sheen {
233
+ position: absolute;
234
+ inset: 0;
235
+ pointer-events: none;
236
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0) 45%, rgba(0, 0, 0, 0.06));
237
+ }
238
+ .animot-sticky-fold {
239
+ position: absolute;
240
+ right: 0;
241
+ bottom: 0;
242
+ width: 22px;
243
+ height: 22px;
244
+ pointer-events: none;
245
+ background: linear-gradient(135deg, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.18) 50%);
246
+ border-top-left-radius: 4px;
247
+ box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.12);
248
+ }
249
+ .animot-sticky-text {
250
+ position: relative;
251
+ z-index: 1;
252
+ width: 100%;
253
+ line-height: 1.3;
254
+ white-space: pre-wrap;
255
+ word-break: break-word;
256
+ }
257
+
220
258
  /* SVG path-trace animations (Icon + Svg elements) */
221
259
  .animot-svg-element.icon-anim-draw path,
222
260
  .animot-svg-element.icon-anim-draw circle,
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type ElementType = 'code' | 'text' | 'arrow' | 'image' | 'shape' | 'counter' | 'chart' | 'icon' | 'svg' | 'motionPath' | 'video' | 'progress' | 'container';
1
+ export type ElementType = 'code' | 'text' | 'arrow' | 'image' | 'shape' | 'counter' | 'chart' | 'icon' | 'svg' | 'motionPath' | 'video' | 'progress' | 'container' | 'draw' | 'sticky';
2
2
  /**
3
3
  * Per-element keyframe — animates state at a specific time WITHIN a slide's
4
4
  * display window. Layered on top of slide-to-slide morphing: morphing handles
@@ -28,6 +28,7 @@ export interface Keyframe {
28
28
  contrast?: number;
29
29
  saturate?: number;
30
30
  grayscale?: number;
31
+ path?: string;
31
32
  }
32
33
  export type ShapeType = 'rectangle' | 'circle' | 'triangle' | 'ellipse' | 'star' | 'hexagon';
33
34
  export interface Position {
@@ -118,6 +119,8 @@ export interface BaseElement {
118
119
  depth?: number;
119
120
  motionPathId?: string;
120
121
  motionPathConfig?: MotionPathConfig;
122
+ /** Solid color for the shape-morph transition only (render-time override). */
123
+ morphColor?: string;
121
124
  blur?: number;
122
125
  brightness?: number;
123
126
  contrast?: number;
@@ -416,6 +419,34 @@ export interface SvgElement extends BaseElement {
416
419
  viewBox?: string;
417
420
  animation?: SvgPathAnimationConfig;
418
421
  }
422
+ export type DrawBrush = 'pen' | 'marker' | 'highlighter';
423
+ export interface DrawElement extends BaseElement {
424
+ type: 'draw';
425
+ points: number[][];
426
+ color: string;
427
+ opacity: number;
428
+ brush: DrawBrush;
429
+ strokeWidth: number;
430
+ thinning?: number;
431
+ smoothing?: number;
432
+ streamline?: number;
433
+ taperStart?: boolean;
434
+ taperEnd?: boolean;
435
+ }
436
+ export interface StickyElement extends BaseElement {
437
+ type: 'sticky';
438
+ text: string;
439
+ bgColor: string;
440
+ textColor: string;
441
+ fontSize: number;
442
+ fontFamily: string;
443
+ fontWeight: number;
444
+ textAlign: 'left' | 'center' | 'right';
445
+ padding: number;
446
+ borderRadius: number;
447
+ opacity: number;
448
+ shadow?: boolean;
449
+ }
419
450
  export interface MotionPathElement extends BaseElement {
420
451
  type: 'motionPath';
421
452
  points: PathPoint[];
@@ -480,7 +511,7 @@ export interface ContainerElement extends BaseElement {
480
511
  borderWidth?: number;
481
512
  borderRadius?: number;
482
513
  }
483
- export type CanvasElement = CodeElement | TextElement | ArrowElement | ImageElement | VideoElement | ShapeElement | CounterElement | ChartElement | IconElement | SvgElement | MotionPathElement | ProgressElement | ContainerElement;
514
+ export type CanvasElement = CodeElement | TextElement | ArrowElement | ImageElement | VideoElement | ShapeElement | CounterElement | ChartElement | IconElement | SvgElement | MotionPathElement | ProgressElement | ContainerElement | DrawElement | StickyElement;
484
515
  export type ParticleShape = 'circle' | 'square' | 'star' | 'triangle';
485
516
  export interface ParticlesConfig {
486
517
  enabled: boolean;
@@ -0,0 +1,26 @@
1
+ import type { DrawElement } from '../types';
2
+ export interface StrokeOptions {
3
+ size: number;
4
+ thinning: number;
5
+ smoothing: number;
6
+ streamline: number;
7
+ taperStart: boolean;
8
+ taperEnd: boolean;
9
+ }
10
+ export declare function resolveStrokeOptions(el: Pick<DrawElement, 'brush' | 'strokeWidth' | 'thinning' | 'smoothing' | 'streamline' | 'taperStart' | 'taperEnd'>): StrokeOptions;
11
+ /** Compute the axis-aligned bounding box of raw input points. */
12
+ export declare function pointsBounds(points: number[][]): {
13
+ minX: number;
14
+ minY: number;
15
+ width: number;
16
+ height: number;
17
+ };
18
+ /**
19
+ * Build the renderable path + viewBox for a draw element. Points are assumed
20
+ * to already be in a 0,0-origin local space; the viewBox spans their bounds
21
+ * (plus stroke padding) so the element box scales the stroke on resize.
22
+ */
23
+ export declare function drawElementToPath(el: Pick<DrawElement, 'points' | 'brush' | 'strokeWidth' | 'thinning' | 'smoothing' | 'streamline' | 'taperStart' | 'taperEnd'>): {
24
+ d: string;
25
+ viewBox: string;
26
+ };
@@ -0,0 +1,70 @@
1
+ import getStroke from 'perfect-freehand';
2
+ const BRUSH_DEFAULTS = {
3
+ // Tapered, pressure-responsive — feels like an ink pen.
4
+ pen: { thinning: 0.6, smoothing: 0.5, streamline: 0.5, taperStart: true, taperEnd: true },
5
+ // Flat, even width — a chisel marker.
6
+ marker: { thinning: 0.1, smoothing: 0.55, streamline: 0.5, taperStart: false, taperEnd: false },
7
+ // Even width like marker; the translucency comes from element opacity.
8
+ highlighter: { thinning: 0, smoothing: 0.6, streamline: 0.5, taperStart: false, taperEnd: false }
9
+ };
10
+ export function resolveStrokeOptions(el) {
11
+ const d = BRUSH_DEFAULTS[el.brush] ?? BRUSH_DEFAULTS.pen;
12
+ return {
13
+ size: el.strokeWidth,
14
+ thinning: el.thinning ?? d.thinning ?? 0.5,
15
+ smoothing: el.smoothing ?? d.smoothing ?? 0.5,
16
+ streamline: el.streamline ?? d.streamline ?? 0.5,
17
+ taperStart: el.taperStart ?? d.taperStart ?? false,
18
+ taperEnd: el.taperEnd ?? d.taperEnd ?? false
19
+ };
20
+ }
21
+ /** Turn a perfect-freehand outline polygon into an SVG path `d` string. */
22
+ function outlineToPath(points) {
23
+ if (points.length === 0)
24
+ return '';
25
+ const d = points.reduce((acc, [x0, y0], i, arr) => {
26
+ const [x1, y1] = arr[(i + 1) % arr.length];
27
+ acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
28
+ return acc;
29
+ }, ['M', points[0][0], points[0][1], 'Q']);
30
+ d.push('Z');
31
+ return d.join(' ');
32
+ }
33
+ /** Compute the axis-aligned bounding box of raw input points. */
34
+ export function pointsBounds(points) {
35
+ if (points.length === 0)
36
+ return { minX: 0, minY: 0, width: 0, height: 0 };
37
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
38
+ for (const [x, y] of points) {
39
+ if (x < minX)
40
+ minX = x;
41
+ if (y < minY)
42
+ minY = y;
43
+ if (x > maxX)
44
+ maxX = x;
45
+ if (y > maxY)
46
+ maxY = y;
47
+ }
48
+ return { minX, minY, width: maxX - minX, height: maxY - minY };
49
+ }
50
+ /**
51
+ * Build the renderable path + viewBox for a draw element. Points are assumed
52
+ * to already be in a 0,0-origin local space; the viewBox spans their bounds
53
+ * (plus stroke padding) so the element box scales the stroke on resize.
54
+ */
55
+ export function drawElementToPath(el) {
56
+ const opts = resolveStrokeOptions(el);
57
+ const stroke = getStroke(el.points, {
58
+ size: opts.size,
59
+ thinning: opts.thinning,
60
+ smoothing: opts.smoothing,
61
+ streamline: opts.streamline,
62
+ start: { taper: opts.taperStart ? opts.size * 4 : 0, cap: !opts.taperStart },
63
+ end: { taper: opts.taperEnd ? opts.size * 4 : 0, cap: !opts.taperEnd }
64
+ });
65
+ const d = outlineToPath(stroke);
66
+ const b = pointsBounds(el.points);
67
+ const pad = opts.size;
68
+ const vb = `${b.minX - pad} ${b.minY - pad} ${b.width + pad * 2} ${b.height + pad * 2}`;
69
+ return { d, viewBox: vb };
70
+ }
@@ -0,0 +1,89 @@
1
+ import type { ShapeType, CanvasElement } from '../types';
2
+ /** Build a `d` string for a shape type, fitted to a w×h box (origin 0,0). */
3
+ export declare function shapeToPath(type: ShapeType, w: number, h: number, borderRadius?: number): string;
4
+ /**
5
+ * Extract drawable path `d` strings from raw SVG markup. Returns an array of
6
+ * subpath `d` strings (one per primitive). Empty when nothing is drawable.
7
+ */
8
+ export declare function extractPaths(svgContent: string): string[];
9
+ /**
10
+ * Like extractPaths, but pairs each subpath with its own fill color so a
11
+ * multi-color SVG morphs per-piece (one `<path>` per subpath) instead of
12
+ * flattening to a single representative color. Primitives without their own
13
+ * fill inherit the root `<svg fill="…">` (default black, matching the
14
+ * browser), which is exactly the black outline strokes on icon SVGs.
15
+ */
16
+ export declare function extractPathsWithFill(svgContent: string): Array<{
17
+ d: string;
18
+ fill: string;
19
+ }>;
20
+ /** Single combined `d` (all subpaths concatenated) — for simple morphs. */
21
+ export declare function extractCombinedPath(svgContent: string): string | null;
22
+ /**
23
+ * Pick a representative fill color from SVG markup — the first non-"none"
24
+ * `fill` attribute. Used to color the flattened morph path (which loses
25
+ * per-subpath fills). Returns null if none found.
26
+ */
27
+ export declare function extractFill(svgContent: string): string | null;
28
+ /**
29
+ * Re-fit a path `d` from its own viewBox into a target W×H box (origin 0,0).
30
+ * Morph stops must share a coordinate space or flubber produces scale jumps;
31
+ * authoring normalizes every stop into the element's box via this. Parses to
32
+ * absolute cubics, scales anchors + handles, re-serializes.
33
+ */
34
+ export declare function fitPathToBox(d: string, sourceViewBox: string, targetW: number, targetH: number): string;
35
+ /**
36
+ * Resolve any vector element (shape / svg / draw) to its current geometry as
37
+ * a path `d` plus the viewBox that `d` lives in. Single source of truth for
38
+ * both the morph engine and the "snapshot current" authoring action.
39
+ */
40
+ export declare function resolveElementPath(el: CanvasElement): {
41
+ d: string;
42
+ viewBox: string;
43
+ } | null;
44
+ /**
45
+ * Like resolveElementPath but keeps subpaths SEPARATE, each with its own fill,
46
+ * so multi-color SVGs morph per-piece. Shapes/draws return a single part.
47
+ */
48
+ export declare function resolveElementPathParts(el: CanvasElement): {
49
+ parts: Array<{
50
+ d: string;
51
+ fill: string;
52
+ }>;
53
+ viewBox: string;
54
+ } | null;
55
+ export type PathInterpolator = (t: number) => string;
56
+ /** One morphing piece: a `d` interpolator plus the color it lerps across. */
57
+ export interface MorphPart {
58
+ interp: PathInterpolator;
59
+ fromColor: string;
60
+ toColor: string;
61
+ }
62
+ /**
63
+ * Build per-piece morph parts between two fill-tagged subpath lists. Each `d`
64
+ * MUST already be in the same coordinate space (caller fits the source into
65
+ * the target viewBox). Picks the right flubber routine by count so 1↔many and
66
+ * many↔1 map correctly, and pairs colors per piece so multi-color SVGs keep
67
+ * their colors instead of flattening to one.
68
+ */
69
+ export declare function makeMorphParts(from: Array<{
70
+ d: string;
71
+ fill: string;
72
+ }>, to: Array<{
73
+ d: string;
74
+ fill: string;
75
+ }>): MorphPart[];
76
+ /**
77
+ * Build an interpolator between two `d` strings. Handles differing subpath
78
+ * counts via flubber's many-to-many interpolation, falling back to a simple
79
+ * single-path interpolate. Always returns a function (t:0..1) => d.
80
+ */
81
+ export declare function makePathInterpolator(fromD: string, toD: string): PathInterpolator;
82
+ /**
83
+ * Build a single interpolator that morphs through an ordered list of `d`
84
+ * stops. `t` runs 0..1 across the whole sequence; the segment is picked by
85
+ * scaling `t` across (stops.length - 1) and interpolating within it. Used by
86
+ * the within-slide multi-target morph (circle → star → hexagon → …).
87
+ * When `loop` is true the sequence wraps back to the first stop.
88
+ */
89
+ export declare function makeSequenceInterpolator(stops: string[], loop?: boolean): PathInterpolator;