animot-presenter 0.6.3 → 0.6.5

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;
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Svelte action for arrow animations. Two strategies depending on mode:
3
3
  *
4
- * • 'draw' / 'undraw' / 'draw-undraw' — clip-path inset on the host <svg>.
5
- * Doesn't touch stroke-dasharray, so dashed/dotted patterns are preserved
6
- * while the path is progressively revealed/hidden from one side.
4
+ * • 'draw' / 'undraw' / 'draw-undraw' — reveals the stroke ALONG THE PATH using
5
+ * stroke-dasharray/offset keyed to the path's total length. This follows the
6
+ * real geometry, so curved, looping and spiral arrows draw the way they're
7
+ * shaped (a linear clip-path wipe could only sweep left↔right / top↔bottom).
8
+ * The arrowhead (a separate sub-path) is revealed in step. Any dashed/dotted
9
+ * pattern is overridden during the reveal and restored when fully drawn.
7
10
  *
8
11
  * • 'flow' — marching ants. Continuously shifts stroke-dashoffset on the
9
12
  * inner .arrow-path, so the dash pattern appears to flow along the path
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Svelte action for arrow animations. Two strategies depending on mode:
3
3
  *
4
- * • 'draw' / 'undraw' / 'draw-undraw' — clip-path inset on the host <svg>.
5
- * Doesn't touch stroke-dasharray, so dashed/dotted patterns are preserved
6
- * while the path is progressively revealed/hidden from one side.
4
+ * • 'draw' / 'undraw' / 'draw-undraw' — reveals the stroke ALONG THE PATH using
5
+ * stroke-dasharray/offset keyed to the path's total length. This follows the
6
+ * real geometry, so curved, looping and spiral arrows draw the way they're
7
+ * shaped (a linear clip-path wipe could only sweep left↔right / top↔bottom).
8
+ * The arrowhead (a separate sub-path) is revealed in step. Any dashed/dotted
9
+ * pattern is overridden during the reveal and restored when fully drawn.
7
10
  *
8
11
  * • 'flow' — marching ants. Continuously shifts stroke-dashoffset on the
9
12
  * inner .arrow-path, so the dash pattern appears to flow along the path
@@ -13,55 +16,37 @@
13
16
  export function arrowClipDraw(node, params) {
14
17
  let raf = 0;
15
18
  let pathEl = null;
19
+ let headEl = null;
16
20
  let baseDash = '';
17
21
  let baseOffset = '';
18
- function clearArrowPath() {
19
- if (!pathEl)
20
- return;
21
- pathEl.style.strokeDasharray = baseDash;
22
- pathEl.style.strokeDashoffset = baseOffset;
22
+ function restorePath() {
23
+ // Back to the element's natural (fully-drawn) appearance.
24
+ if (pathEl) {
25
+ pathEl.style.strokeDasharray = baseDash;
26
+ pathEl.style.strokeDashoffset = baseOffset;
27
+ }
28
+ if (headEl)
29
+ headEl.style.opacity = '';
23
30
  }
24
31
  function reset() {
25
32
  if (raf)
26
33
  cancelAnimationFrame(raf);
27
34
  raf = 0;
28
35
  node.style.clipPath = '';
29
- clearArrowPath();
36
+ restorePath();
30
37
  }
31
38
  function run() {
32
39
  reset();
33
40
  if (!params.enabled || params.mode === 'none')
34
41
  return;
35
- // Capture the inner arrow path (used by 'flow' mode).
36
42
  pathEl = node.querySelector('.arrow-path');
37
- if (pathEl) {
38
- baseDash = pathEl.style.strokeDasharray || '';
39
- baseOffset = pathEl.style.strokeDashoffset || '';
40
- }
41
- // Pick the dominant axis so vertical arrows reveal top↔bottom and horizontal
42
- // arrows reveal left↔right. clip-path inset(top right bottom left).
43
- const dx = params.endX - params.startX;
44
- const dy = params.endY - params.startY;
45
- const horizontal = Math.abs(dx) >= Math.abs(dy);
46
- const positive = horizontal ? dx >= 0 : dy >= 0;
47
- const goesPositive = params.reverse ? !positive : positive;
48
- // Build the clip-path string for a given progress (0 = fully hidden,
49
- // 100 = fully visible) on the dominant axis.
50
- function clip(insetPct) {
51
- if (horizontal) {
52
- return goesPositive
53
- ? `inset(0 ${insetPct}% 0 0)` // hide from right edge
54
- : `inset(0 0 0 ${insetPct}%)`; // hide from left edge
55
- }
56
- return goesPositive
57
- ? `inset(0 0 ${insetPct}% 0)` // hide from bottom
58
- : `inset(${insetPct}% 0 0 0)`; // hide from top
59
- }
60
- const clipFull = clip(100); // fully hidden (100% inset on hide side)
61
- const clipNone = clip(0);
43
+ headEl = node.querySelector('.arrow-head');
44
+ if (!pathEl)
45
+ return;
46
+ baseDash = pathEl.style.strokeDasharray || '';
47
+ baseOffset = pathEl.style.strokeDashoffset || '';
62
48
  // Round duration so an integer number of cycles fits in slide_duration.
63
- // Without this, GIF loop boundary shows the arrow mid-draw → snap to
64
- // invisible → re-draw, which the user perceives as a reset.
49
+ // Without this, a GIF loop boundary can show the arrow mid-draw → snap.
65
50
  const requested = Math.max(50, params.duration);
66
51
  const dur = params.slideDuration && params.slideDuration > 0 && (params.loop || params.mode === 'flow')
67
52
  ? params.slideDuration / Math.max(1, Math.round(params.slideDuration / requested))
@@ -70,21 +55,15 @@ export function arrowClipDraw(node, params) {
70
55
  const m = params.mode;
71
56
  // FLOW: marching ants — animate dashoffset continuously, keep base pattern.
72
57
  if (m === 'flow') {
73
- if (!pathEl)
74
- return;
75
- // If the arrow has no inline dasharray (i.e. it's solid), give it one
76
- // so dashes are visible to flow. Otherwise keep the user's pattern.
77
58
  const dashAttr = pathEl.getAttribute('stroke-dasharray');
78
59
  if (!dashAttr || dashAttr === 'none') {
79
60
  pathEl.style.strokeDasharray = '8 5';
80
61
  }
81
- // One pixel of dashoffset shift per ms feels like a steady current; use
82
- // `duration` as the ms per cycle of 24px (one base dash repeat-ish).
83
62
  const cycle = 24;
84
63
  const dir = params.reverse ? 1 : -1;
85
64
  function flowStep(now) {
86
65
  const elapsed = now - start;
87
- const offset = (dir * (elapsed / dur) * cycle) % (cycle * 1000); // huge mod just to keep it bounded
66
+ const offset = (dir * (elapsed / dur) * cycle) % (cycle * 1000);
88
67
  if (pathEl)
89
68
  pathEl.style.strokeDashoffset = String(offset);
90
69
  raf = requestAnimationFrame(flowStep);
@@ -92,59 +71,95 @@ export function arrowClipDraw(node, params) {
92
71
  raf = requestAnimationFrame(flowStep);
93
72
  return;
94
73
  }
95
- // Initial clip-path state for draw modes.
96
- if (m === 'draw' || m === 'draw-undraw') {
97
- node.style.clipPath = clipFull;
74
+ // PATH-LENGTH REVEAL for draw / undraw / draw-undraw.
75
+ let len = 0;
76
+ try {
77
+ len = pathEl.getTotalLength();
78
+ }
79
+ catch {
80
+ len = 0;
81
+ }
82
+ if (!len) {
83
+ // Degenerate path — nothing sensible to animate.
84
+ restorePath();
85
+ return;
98
86
  }
99
- else if (m === 'undraw') {
100
- node.style.clipPath = clipNone;
87
+ // dashoffset reveals from the path start (positive) or the path end
88
+ // (negative) toward the other end, so `reverse` flips the draw direction.
89
+ const hideOffset = params.reverse ? -len : len;
90
+ // p: 0 = nothing drawn, 1 = fully drawn (along the path).
91
+ function setProgress(p) {
92
+ if (!pathEl)
93
+ return;
94
+ pathEl.style.strokeDasharray = `${len} ${len}`;
95
+ pathEl.style.strokeDashoffset = String(hideOffset * (1 - p));
96
+ }
97
+ // Head only shows once the end of the line is reached.
98
+ function setHead(p) {
99
+ if (headEl)
100
+ headEl.style.opacity = p >= 0.999 ? '1' : '0';
101
+ }
102
+ const cubicOut = (t) => 1 - Math.pow(1 - t, 3);
103
+ // Initial frame.
104
+ if (m === 'undraw') {
105
+ setProgress(1);
106
+ setHead(1);
107
+ }
108
+ else {
109
+ setProgress(0);
110
+ setHead(0);
101
111
  }
102
112
  function step(now) {
103
113
  const elapsed = now - start;
104
114
  if (m === 'draw') {
105
115
  const t = Math.min(elapsed / dur, 1);
106
- const eased = 1 - Math.pow(1 - t, 3);
107
- node.style.clipPath = clip(100 * (1 - eased));
116
+ const p = cubicOut(t);
117
+ setProgress(p);
118
+ setHead(p);
108
119
  if (t < 1)
109
120
  raf = requestAnimationFrame(step);
110
121
  else if (params.loop)
111
122
  run();
112
123
  else {
113
- node.style.clipPath = clipNone;
124
+ restorePath();
114
125
  raf = 0;
115
- }
126
+ } // settle fully drawn (restores dashes)
116
127
  }
117
128
  else if (m === 'undraw') {
118
129
  const t = Math.min(elapsed / dur, 1);
119
- const eased = 1 - Math.pow(1 - t, 3);
120
- node.style.clipPath = clip(100 * eased);
130
+ const p = 1 - cubicOut(t);
131
+ setProgress(p);
132
+ setHead(p);
121
133
  if (t < 1)
122
134
  raf = requestAnimationFrame(step);
123
135
  else if (params.loop)
124
136
  run();
125
137
  else {
126
- node.style.clipPath = clipFull;
138
+ setProgress(0);
139
+ setHead(0);
127
140
  raf = 0;
128
- }
141
+ } // settle hidden
129
142
  }
130
143
  else if (m === 'draw-undraw') {
131
144
  const half = dur / 2;
132
145
  if (elapsed < half) {
133
- const t = Math.min(elapsed / half, 1);
134
- const eased = 1 - Math.pow(1 - t, 3);
135
- node.style.clipPath = clip(100 * (1 - eased));
146
+ const p = cubicOut(Math.min(elapsed / half, 1));
147
+ setProgress(p);
148
+ setHead(p);
136
149
  raf = requestAnimationFrame(step);
137
150
  }
138
151
  else {
139
152
  const t = Math.min((elapsed - half) / half, 1);
140
- const eased = 1 - Math.pow(1 - t, 3);
141
- node.style.clipPath = clip(100 * eased);
153
+ const p = 1 - cubicOut(t);
154
+ setProgress(p);
155
+ setHead(p);
142
156
  if (t < 1)
143
157
  raf = requestAnimationFrame(step);
144
158
  else if (params.loop)
145
159
  run();
146
160
  else {
147
- node.style.clipPath = clipFull;
161
+ setProgress(0);
162
+ setHead(0);
148
163
  raf = 0;
149
164
  }
150
165
  }
@@ -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;