@motion.page/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +817 -0
  2. package/dist/core/Animation.d.ts +230 -0
  3. package/dist/core/AnimationBuilder.d.ts +125 -0
  4. package/dist/core/Engine.d.ts +140 -0
  5. package/dist/core/Motion.d.ts +56 -0
  6. package/dist/core/PropTween.d.ts +81 -0
  7. package/dist/core/Ticker.d.ts +107 -0
  8. package/dist/core/Timeline.d.ts +294 -0
  9. package/dist/easing/index.d.ts +65 -0
  10. package/dist/fit/FitResolver.d.ts +12 -0
  11. package/dist/index.cjs +34 -0
  12. package/dist/index.cjs.map +52 -0
  13. package/dist/index.d.ts +25 -0
  14. package/dist/index.js +34 -0
  15. package/dist/index.js.map +52 -0
  16. package/dist/memory/AnimationPool.d.ts +14 -0
  17. package/dist/memory/ObjectPool.d.ts +37 -0
  18. package/dist/memory/PropTweenPool.d.ts +14 -0
  19. package/dist/registries/SDKRegistry.d.ts +69 -0
  20. package/dist/render/CSSRenderer.d.ts +33 -0
  21. package/dist/render/PathRenderer.d.ts +24 -0
  22. package/dist/render/RenderBatch.d.ts +27 -0
  23. package/dist/render/TransformCache.d.ts +54 -0
  24. package/dist/stagger/StaggerResolver.d.ts +28 -0
  25. package/dist/triggers/BaseTrigger.d.ts +61 -0
  26. package/dist/triggers/CursorTrigger.d.ts +86 -0
  27. package/dist/triggers/EventTrigger.d.ts +48 -0
  28. package/dist/triggers/GestureTrigger.d.ts +137 -0
  29. package/dist/triggers/MarkerManager.d.ts +42 -0
  30. package/dist/triggers/MouseMoveTrigger.d.ts +49 -0
  31. package/dist/triggers/PageExitTrigger.d.ts +74 -0
  32. package/dist/triggers/PageLoadTrigger.d.ts +37 -0
  33. package/dist/triggers/PinManager.d.ts +131 -0
  34. package/dist/triggers/ScrollTrigger.d.ts +103 -0
  35. package/dist/triggers/TriggerManager.d.ts +133 -0
  36. package/dist/types/index.d.ts +355 -0
  37. package/dist/types/public.d.ts +7 -0
  38. package/dist/utils/ColorParser.d.ts +23 -0
  39. package/dist/utils/DrawSVGParser.d.ts +62 -0
  40. package/dist/utils/FilterParser.d.ts +49 -0
  41. package/dist/utils/MotionUtils.d.ts +136 -0
  42. package/dist/utils/PathParser.d.ts +89 -0
  43. package/dist/utils/PositionParser.d.ts +24 -0
  44. package/dist/utils/PropertyParser.d.ts +159 -0
  45. package/dist/utils/QuickSetter.d.ts +16 -0
  46. package/dist/utils/ScrollPositionParser.d.ts +19 -0
  47. package/dist/utils/StyleReset.d.ts +73 -0
  48. package/dist/utils/TargetResolver.d.ts +23 -0
  49. package/dist/utils/TextSplitter.d.ts +90 -0
  50. package/dist/utils/executeTimelineAction.d.ts +18 -0
  51. package/dist/utils/getLayoutRect.d.ts +10 -0
  52. package/package.json +56 -0
package/README.md ADDED
@@ -0,0 +1,817 @@
1
+ # @motion/sdk
2
+
3
+ A high-performance CSS animation SDK with a declarative, config-based API. Zero runtime dependencies.
4
+
5
+ ![Version](https://img.shields.io/badge/version-0.1.0-blue) ![Bundle Size](https://img.shields.io/badge/bundle-TBD-green) ![License](https://img.shields.io/badge/license-Proprietary-red)
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Quick Start](#quick-start)
13
+ - [Core Concept](#core-concept)
14
+ - [API Reference](#api-reference)
15
+ - [Motion()](#motion-function)
16
+ - [Motion Static Methods](#motion-static-methods)
17
+ - [Motion.utils](#motionutils)
18
+ - [Timeline](#timeline)
19
+ - [Triggers](#triggers)
20
+ - [Lifecycle Callbacks](#lifecycle-callbacks)
21
+ - [AnimationConfig](#animationconfig)
22
+ - [Easing](#easing)
23
+ - [Types](#types)
24
+ - [Browser Build](#browser-build)
25
+ - [Browser Support](#browser-support)
26
+ - [License](#license)
27
+
28
+ ---
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ # Not published to npm — used internally within the Motion.page monorepo.
34
+ # The package name is @motion/sdk
35
+ ```
36
+
37
+ For direct browser use, see [Browser Build](#browser-build).
38
+
39
+ ---
40
+
41
+ ## Quick Start
42
+
43
+ ### Basic Animation
44
+
45
+ ```ts
46
+ import { Motion } from '@motion/sdk';
47
+
48
+ // Fade in and slide up
49
+ Motion('hero-intro', '#hero', {
50
+ from: { opacity: 0, y: 50 },
51
+ to: { opacity: 1, y: 0 },
52
+ duration: 0.8,
53
+ ease: 'power2.out',
54
+ }).play();
55
+ ```
56
+
57
+ ### Scroll-Triggered Animation
58
+
59
+ ```ts
60
+ // Scrub animation progress to scroll position
61
+ Motion('scroll-reveal', '.card', {
62
+ from: { opacity: 0, y: 40 },
63
+ to: { opacity: 1, y: 0 },
64
+ duration: 0.6,
65
+ }).onScroll({ scrub: true, start: 'top 80%', end: 'top 30%' });
66
+ ```
67
+
68
+ ### Hover Effect
69
+
70
+ ```ts
71
+ // Play on hover, reverse on leave
72
+ Motion('btn-hover', '.btn', {
73
+ to: { scale: 1.05, backgroundColor: '#0099ff' },
74
+ duration: 0.3,
75
+ ease: 'power2.out',
76
+ }).onHover({ onLeave: 'reverse' });
77
+ ```
78
+
79
+ ### Multi-Step Timeline with Stagger
80
+
81
+ ```ts
82
+ // Sequence multiple targets; each entry has its own target and position
83
+ Motion('intro-sequence', [
84
+ {
85
+ target: '.title',
86
+ from: { opacity: 0, y: -30 },
87
+ to: { opacity: 1, y: 0 },
88
+ duration: 0.6,
89
+ },
90
+ {
91
+ target: '.cards',
92
+ from: { opacity: 0, y: 20 },
93
+ to: { opacity: 1, y: 0 },
94
+ duration: 0.5,
95
+ stagger: { each: 0.1, from: 'start' },
96
+ position: '+=0.1', // starts 0.1s after the previous entry ends
97
+ },
98
+ {
99
+ target: '.cta',
100
+ from: { opacity: 0, scale: 0.9 },
101
+ to: { opacity: 1, scale: 1 },
102
+ duration: 0.4,
103
+ position: '<', // starts at the same time as the previous entry
104
+ },
105
+ ]).onPageLoad();
106
+ ```
107
+
108
+ ### Replay an Existing Timeline
109
+
110
+ ```ts
111
+ // Retrieve a previously created timeline by name and replay it
112
+ Motion('hero-intro').restart();
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Core Concept
118
+
119
+ Every animation in the SDK is a **named timeline**. The name is the first argument to `Motion()` and acts as a registry key — calling `Motion('same-name')` always returns the same `Timeline` instance.
120
+
121
+ Timelines are built declaratively via config objects; there are no `.to()` / `.from()` method calls. Animation state (targets, transforms, styles) is managed internally by the engine.
122
+
123
+ ---
124
+
125
+ ## API Reference
126
+
127
+ ### Motion Function
128
+
129
+ Three overloads:
130
+
131
+ ```ts
132
+ // 1. Retrieve an existing named timeline
133
+ Motion(name: string): Timeline
134
+
135
+ // 2. Create a single-animation timeline
136
+ Motion(name: string, target: TargetInput, config: AnimationConfig): Timeline
137
+
138
+ // 3. Create a multi-step timeline from an array of entries
139
+ Motion(name: string, animations: AnimationEntry[]): Timeline
140
+ ```
141
+
142
+ **`TargetInput`** accepts: CSS selector string, `string[]`, `Element`, `NodeList`, `Element[]`, or a plain object / array of plain objects (for object tweening).
143
+
144
+ Calling `Motion()` with the same name on an already-existing timeline returns it unchanged (the retrieve overload). To rebuild a timeline, call `.kill()` on the old one first.
145
+
146
+ ---
147
+
148
+ ### Motion Static Methods
149
+
150
+ | Method | Signature | Description |
151
+ |--------|-----------|-------------|
152
+ | `Motion.set` | `(target: TargetInput, vars: AnimationVars): void` | Immediately apply CSS / transform properties with no animation (like `gsap.set`). Values are written synchronously. |
153
+ | `Motion.get` | `(name: string): Timeline \| undefined` | Get a timeline by name; returns `undefined` if none exists. |
154
+ | `Motion.has` | `(name: string): boolean` | Check whether a named timeline is registered. |
155
+ | `Motion.getNames` | `(): string[]` | Return the names of all registered timelines. |
156
+ | `Motion.kill` | `(name: string): void` | Kill a single timeline by name. Restores initial CSS. |
157
+ | `Motion.killAll` | `(): void` | Kill every timeline and animation managed by the engine. |
158
+ | `Motion.reset` | `(targets: TargetInput): void` | Kill animations targeting those elements, revert text splits, clear transform cache and inline animation styles. |
159
+ | `Motion.refreshScrollTriggers` | `(): void` | Recalculate all scroll trigger start/end positions (call after layout changes). |
160
+ | `Motion.cleanup` | `(): void` | Remove ScrollTrigger spacer and marker DOM nodes from the document. |
161
+
162
+ #### Examples
163
+
164
+ ```ts
165
+ // Immediately hide an element before animating it in
166
+ Motion.set('#hero', { opacity: 0, y: -20 });
167
+
168
+ // Check and conditionally replay
169
+ if (Motion.has('hero-intro')) {
170
+ Motion('hero-intro').restart();
171
+ }
172
+
173
+ // Tear down everything (e.g., on page navigation)
174
+ Motion.killAll();
175
+ Motion.cleanup();
176
+
177
+ // Reset a specific element to its original state
178
+ Motion.reset('.animated-card');
179
+ ```
180
+
181
+ ---
182
+
183
+ ### Motion.utils
184
+
185
+ GSAP-compatible utility functions accessible via `Motion.utils`. These are drop-in replacements for `gsap.utils.*` helpers.
186
+
187
+ | Method | Signature | Description |
188
+ |--------|-----------|-------------|
189
+ | `toArray` | `(target, scope?) → Element[]` | Convert CSS selector, NodeList, HTMLCollection, or Element to a flat array. Drop-in for `gsap.utils.toArray()`. |
190
+ | `clamp` | `(min, max, value?) → number \| fn` | Clamp a value between min and max. Curried if value omitted. |
191
+ | `random` | `(min, max, snap?) → number` | Random number between min and max. Optional snap increment. |
192
+ | `snap` | `(snapTo, value?) → number \| fn` | Snap to nearest increment or array value. Curried if value omitted. |
193
+ | `interpolate` | `(start, end, progress) → number` | Linear interpolation (lerp) between two values. |
194
+ | `mapRange` | `(inMin, inMax, outMin, outMax, value?) → number \| fn` | Map a value from one range to another. Curried if value omitted. |
195
+ | `normalize` | `(min, max, value?) → number \| fn` | Normalize a value to 0–1 within a range. Curried if value omitted. |
196
+ | `wrap` | `(min, max, value?) → number \| fn` | Wrap a value within a range (modular arithmetic). Curried if value omitted. |
197
+
198
+ #### Examples
199
+
200
+ ```ts
201
+ // Convert selector to array (replaces gsap.utils.toArray)
202
+ const sections = Motion.utils.toArray('.section');
203
+ console.log(sections.length); // number of matching elements
204
+
205
+ // Clamp
206
+ Motion.utils.clamp(0, 100, 150); // 100
207
+ const clamp01 = Motion.utils.clamp(0, 1);
208
+ clamp01(1.5); // 1
209
+
210
+ // Snap to increment
211
+ Motion.utils.snap(5, 13); // 15
212
+ Motion.utils.snap([0, 25, 50], 30); // 25
213
+
214
+ // Random with snap
215
+ Motion.utils.random(0, 100, 10); // 0, 10, 20, ..., 100
216
+
217
+ // Map between ranges
218
+ Motion.utils.mapRange(0, 100, 0, 1, 50); // 0.5
219
+
220
+ // Normalize to 0–1
221
+ Motion.utils.normalize(0, 255, 128); // ~0.502
222
+
223
+ // Wrap (modular)
224
+ Motion.utils.wrap(0, 360, 450); // 90
225
+
226
+ // Interpolate (lerp)
227
+ Motion.utils.interpolate(0, 100, 0.5); // 50
228
+ ```
229
+
230
+ #### Migration from GSAP
231
+
232
+ | GSAP | Motion SDK |
233
+ |------|------------|
234
+ | `gsap.utils.toArray('.el')` | `Motion.utils.toArray('.el')` |
235
+ | `gsap.utils.clamp(0, 1, v)` | `Motion.utils.clamp(0, 1, v)` |
236
+ | `gsap.utils.random(0, 100)` | `Motion.utils.random(0, 100)` |
237
+ | `gsap.utils.snap(5, v)` | `Motion.utils.snap(5, v)` |
238
+ | `gsap.utils.mapRange(0, 1, 0, 100, v)` | `Motion.utils.mapRange(0, 1, 0, 100, v)` |
239
+ | `gsap.utils.normalize(0, 100, v)` | `Motion.utils.normalize(0, 100, v)` |
240
+ | `gsap.utils.wrap(0, 360, v)` | `Motion.utils.wrap(0, 360, v)` |
241
+ | `gsap.utils.interpolate(0, 100, 0.5)` | `Motion.utils.interpolate(0, 100, 0.5)` |
242
+
243
+ ---
244
+
245
+ ### Timeline
246
+
247
+ `Timeline` is the object returned by every `Motion()` call. All playback and trigger methods return `this` for chaining.
248
+
249
+ #### Playback Control
250
+
251
+ ```ts
252
+ tl.play(from?: number): this // Play forward; optional start time in seconds
253
+ tl.pause(atTime?: number): this // Pause; optional snap-to time
254
+ tl.reverse(from?: number): this // Play backward; optional start time
255
+ tl.restart(): this // Seek to t=0, restore initial CSS, then play forward
256
+ tl.seek(position: number): this // Jump to a time (seconds) without playing
257
+ ```
258
+
259
+ #### State — Getter / Setter
260
+
261
+ Calling with no argument reads the value; calling with an argument sets it and returns `this`.
262
+
263
+ ```ts
264
+ tl.duration(): number // Read total duration in seconds
265
+ tl.progress(value?: number): number | this // Get or set normalized progress (0–1)
266
+ tl.time(value?: number): number | this // Get or set current time in seconds
267
+ tl.timeScale(value?: number): number | this // Get or set playback speed multiplier
268
+ tl.isActive(): boolean // Is the timeline currently animating?
269
+ tl.getName(): string | undefined // Registered name, or undefined for anonymous
270
+ ```
271
+
272
+ ```ts
273
+ // Read
274
+ const p = tl.progress(); // e.g. 0.5
275
+
276
+ // Write (chainable)
277
+ tl.progress(0.5).play();
278
+ tl.timeScale(2).restart(); // play at 2× speed
279
+ ```
280
+
281
+ #### Inserting Callbacks at a Position
282
+
283
+ ```ts
284
+ tl.call(
285
+ callback: (...args: unknown[]) => void,
286
+ params?: unknown[],
287
+ position?: string | number,
288
+ ): this
289
+ ```
290
+
291
+ **Position syntax** (same rules apply to `AnimationEntry.position`):
292
+
293
+ | Value | Meaning |
294
+ |-------|---------|
295
+ | `0.5` | 0.5 s from start (absolute) |
296
+ | `"+=0.5"` | 0.5 s after the previous entry ends |
297
+ | `"-=0.3"` | 0.3 s before the previous entry ends |
298
+ | `"<"` | At the same start time as the previous entry |
299
+ | `">"` | Immediately after the previous entry ends |
300
+
301
+ ```ts
302
+ Motion('demo', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 1 })
303
+ .call(() => console.log('halfway'), [], 0.5)
304
+ .call(() => console.log('done'), [], '>');
305
+ ```
306
+
307
+ #### Cleanup
308
+
309
+ ```ts
310
+ tl.kill(clearProps?: boolean): void
311
+ // Destroy the timeline. clearProps=true (default) restores initial CSS on all targets.
312
+
313
+ tl.clear(): this
314
+ // Reset and rebuild the timeline without destroying it.
315
+ ```
316
+
317
+ ---
318
+
319
+ ### Triggers
320
+
321
+ All trigger methods are chainable and attach behaviour to the timeline without requiring you to manage event listeners manually.
322
+
323
+ #### `.onHover(config?)`
324
+
325
+ Play on `mouseenter`, react on `mouseleave`.
326
+
327
+ ```ts
328
+ interface HoverConfig {
329
+ target?: string | Element; // Defaults to animation target(s)
330
+ each?: boolean; // Apply trigger to each matched element individually
331
+ onLeave?: 'reverse' | 'pause' | 'stop' | 'restart' | 'none';
332
+ leaveDelay?: number; // Seconds to wait before triggering onLeave
333
+ }
334
+ ```
335
+
336
+ ```ts
337
+ Motion('card-hover', '.card', {
338
+ to: { y: -8, boxShadow: '0 12px 24px rgba(0,0,0,0.15)' },
339
+ duration: 0.3,
340
+ ease: 'power2.out',
341
+ }).onHover({ each: true, onLeave: 'reverse' });
342
+ ```
343
+
344
+ #### `.onClick(config?)`
345
+
346
+ Toggle animation on click.
347
+
348
+ ```ts
349
+ interface ClickConfig {
350
+ target?: string | Element;
351
+ each?: boolean;
352
+ secondTarget?: string | Element; // Alternative click target
353
+ toggle?: 'reverse' | 'restart' | 'play';
354
+ preventDefault?: boolean;
355
+ }
356
+ ```
357
+
358
+ ```ts
359
+ Motion('menu-toggle', '#menu', {
360
+ from: { height: 0, opacity: 0 },
361
+ to: { height: 'auto', opacity: 1 },
362
+ duration: 0.4,
363
+ ease: 'power2.inOut',
364
+ }).onClick({ target: '#menu-btn', toggle: 'reverse' });
365
+ ```
366
+
367
+ #### `.onScroll(config?)`
368
+
369
+ Scrub or snap animation to scroll position.
370
+
371
+ ```ts
372
+ interface ScrollConfig {
373
+ target?: string | Element;
374
+ start?: string; // e.g. 'top 80%'
375
+ end?: string; // e.g. 'bottom 20%'
376
+ scrub?: boolean | number; // true = instant, number = smoothing seconds
377
+ snap?: number | number[] | ((progress: number) => number); // Snap scroll progress
378
+ markers?: boolean | MarkerConfig; // Debug markers
379
+ scroller?: string | Element; // Custom scroll container
380
+ pin?: boolean | string; // Pin the element during scroll
381
+ pinSpacing?: boolean | 'margin' | 'padding';
382
+ each?: boolean;
383
+ toggleActions?: string; // e.g. 'play none none reverse'
384
+ }
385
+ ```
386
+
387
+ **`snap`** controls how scroll progress snaps to discrete values:
388
+
389
+ - **Number** — fractional increment to snap to. E.g. `snap: 0.25` snaps to 0, 0.25, 0.5, 0.75, 1.
390
+ - **Array** — explicit progress values to snap to. E.g. `snap: [0, 0.33, 0.66, 1]`.
391
+ - **Function** — custom snap logic. Receives raw progress (0–1), returns snapped value.
392
+
393
+ Common pattern for horizontal scroll with equal sections:
394
+
395
+ ```ts
396
+ const sections = Motion.utils.toArray('.section');
397
+ Motion('h-scroll', '.panel', {
398
+ to: { x: `-${(sections.length - 1) * 100}%` },
399
+ duration: 1,
400
+ }).onScroll({
401
+ scrub: true,
402
+ snap: 1 / (sections.length - 1),
403
+ pin: true,
404
+ start: 'top top',
405
+ end: `+=${sections.length * 100}%`,
406
+ });
407
+ ```
408
+
409
+ ```ts
410
+ Motion('parallax', '.hero-bg', {
411
+ from: { y: 0 },
412
+ to: { y: -100 },
413
+ }).onScroll({ scrub: 1, start: 'top top', end: 'bottom top' });
414
+ ```
415
+
416
+ #### `.onMouseMove(config?)`
417
+
418
+ Drive animation progress from mouse position.
419
+
420
+ ```ts
421
+ interface MouseMoveConfig {
422
+ type?: 'distance' | 'axis';
423
+ target?: string | Element; // Element whose bounds define the movement area
424
+ each?: boolean;
425
+ smooth?: number; // Smoothing factor (higher = slower follow)
426
+ startProgress?: number; // Progress value at rest (0–1)
427
+ leaveProgress?: number; // Progress to animate to on mouse leave
428
+ }
429
+ ```
430
+
431
+ ```ts
432
+ Motion('parallax-depth', '.layer', {
433
+ from: { x: -20, y: -20 },
434
+ to: { x: 20, y: 20 },
435
+ axis: 'x', // bind to horizontal axis only
436
+ }).onMouseMove({ type: 'axis', smooth: 0.1 });
437
+ ```
438
+
439
+ #### `.onPageLoad()`
440
+
441
+ Play the animation automatically when the page finishes loading.
442
+
443
+ ```ts
444
+ Motion('page-intro', [
445
+ { target: '.logo', from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.5 },
446
+ { target: '.nav', from: { y: -20, opacity: 0 }, to: { y: 0, opacity: 1 }, duration: 0.4 },
447
+ ]).onPageLoad();
448
+ ```
449
+
450
+ #### `.onGesture(config)`
451
+
452
+ Respond to pointer, touch, wheel, or scroll gestures with fine-grained event-to-action mapping.
453
+
454
+ ```ts
455
+ type GestureInputType = 'pointer' | 'touch' | 'wheel' | 'scroll';
456
+
457
+ type GestureEvent =
458
+ | 'Up' | 'Down' | 'Left' | 'Right'
459
+ | 'UpComplete' | 'DownComplete' | 'LeftComplete' | 'RightComplete'
460
+ | 'Change' | 'ChangeX' | 'ChangeY'
461
+ | 'ToggleX' | 'ToggleY'
462
+ | 'Press' | 'Release' | 'PressInit'
463
+ | 'Drag' | 'DragEnd'
464
+ | 'Stop' | 'Hover' | 'HoverEnd';
465
+
466
+ type GestureAction =
467
+ | 'play' | 'pause' | 'reverse' | 'restart' | 'toggle' | 'reset' | 'complete' | 'kill'
468
+ | 'playReverse' | 'progressUp' | 'progressDown' | 'playNext' | 'playPrevious';
469
+
470
+ interface GestureConfig {
471
+ target?: string | Element;
472
+ types: GestureInputType[];
473
+ events: Partial<Record<GestureEvent, GestureAction>>;
474
+ tolerance?: number;
475
+ dragMinimum?: number;
476
+ wheelSpeed?: number;
477
+ scrollSpeed?: number;
478
+ preventDefault?: boolean;
479
+ lockAxis?: boolean;
480
+ each?: boolean;
481
+ stopDelay?: number;
482
+ animationStep?: number | Partial<Record<GestureEvent, number>>;
483
+ smooth?: number;
484
+ }
485
+ ```
486
+
487
+ ```ts
488
+ Motion('swipe-gallery', '.gallery', {
489
+ from: { x: 0 },
490
+ to: { x: -100 },
491
+ }).onGesture({
492
+ types: ['pointer', 'touch'],
493
+ events: {
494
+ Left: 'playNext',
495
+ Right: 'playPrevious',
496
+ },
497
+ dragMinimum: 40,
498
+ lockAxis: true,
499
+ });
500
+ ```
501
+
502
+ #### `.onCursor(config)`
503
+
504
+ Replace the native cursor with a fully animated custom cursor.
505
+
506
+ ```ts
507
+ interface CursorConfig {
508
+ target?: string | Element;
509
+ type?: 'basic' | 'text' | 'media';
510
+ smooth?: number;
511
+ squeeze?: boolean | { min?: number; max?: number; multiplier?: number };
512
+ hideNative?: boolean;
513
+ default: CursorStateVars; // Required: default cursor appearance
514
+ hover?: CursorStateVars; // State when hovering interactive elements
515
+ click?: CursorStateVars; // State while pressing
516
+ text?: Record<string, string | number>;
517
+ media?: Record<string, string | number>;
518
+ }
519
+ ```
520
+
521
+ ```ts
522
+ Motion('custom-cursor', 'body', {
523
+ to: { opacity: 1 },
524
+ duration: 0,
525
+ }).onCursor({
526
+ target: '#cursor-dot',
527
+ smooth: 0.08,
528
+ hideNative: true,
529
+ default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
530
+ hover: { width: 40, height: 40, backgroundColor: 'transparent', borderColor: '#fff' },
531
+ click: { scale: 0.8 },
532
+ });
533
+ ```
534
+
535
+ ---
536
+
537
+ ### Lifecycle Callbacks
538
+
539
+ Attach callbacks via the `AnimationConfig` object or directly on the `Timeline` instance. Both approaches are chainable.
540
+
541
+ #### Via `AnimationConfig`
542
+
543
+ ```ts
544
+ Motion('slide-in', '.card', {
545
+ from: { opacity: 0, x: -40 },
546
+ to: { opacity: 1, x: 0 },
547
+ duration: 0.6,
548
+ onStart: () => console.log('started'),
549
+ onUpdate: (progress) => console.log('progress:', progress),
550
+ onComplete: () => console.log('done'),
551
+ onRepeat: (count) => console.log('repeat #', count),
552
+ onReverseComplete: () => console.log('reversed'),
553
+ repeat: { times: 2, yoyo: true, delay: 0.5 },
554
+ });
555
+ ```
556
+
557
+ #### Via `Timeline` Methods
558
+
559
+ ```ts
560
+ Motion('slide-in', '.card', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.6 })
561
+ .onStart(() => console.log('started'))
562
+ .onUpdate((progress, time) => console.log(progress, time))
563
+ .onComplete(() => console.log('done'));
564
+ ```
565
+
566
+ | Callback | Timeline method | AnimationConfig field | Arguments |
567
+ |----------|-----------------|-----------------------|-----------|
568
+ | Start | `.onStart(cb)` | `onStart` | none |
569
+ | Update | `.onUpdate(cb)` | `onUpdate` | `(progress: number, time: number)` via method; `(progress: number)` via config |
570
+ | Complete | `.onComplete(cb)` | `onComplete` | none |
571
+ | Repeat | — | `onRepeat` | `(repeatCount: number)` |
572
+ | Reverse complete | — | `onReverseComplete` | none |
573
+
574
+ ---
575
+
576
+ ## AnimationConfig
577
+
578
+ Full shape of the config object used for both single-animation (`Motion(name, target, config)`) and each entry in a multi-step timeline (`AnimationEntry`).
579
+
580
+ ```ts
581
+ interface AnimationConfig {
582
+ from?: AnimationVars; // Initial values (animated FROM these)
583
+ to?: AnimationVars; // Target values (animated TO these)
584
+ duration?: number; // Seconds (default: engine default)
585
+ delay?: number; // Seconds before animation begins
586
+ ease?: string; // Easing name string, see Easing section
587
+ stagger?: number | StaggerVars; // Per-element stagger delay
588
+ repeat?: RepeatConfig; // Loop/yoyo configuration
589
+ split?: SplitType; // Text splitting for per-char/word/line animation
590
+ axis?: 'x' | 'y'; // Axis binding for onMouseMove animations
591
+
592
+ // Lifecycle
593
+ onStart?: () => void;
594
+ onUpdate?: (progress: number) => void;
595
+ onComplete?: () => void;
596
+ onRepeat?: (repeatCount: number) => void;
597
+ onReverseComplete?: () => void;
598
+ }
599
+ ```
600
+
601
+ ### AnimationVars
602
+
603
+ All animatable properties:
604
+
605
+ ```ts
606
+ // Transforms
607
+ x, y, z // number | string (px default)
608
+ rotate, rotateX, rotateY, rotateZ // number | string (deg default)
609
+ scale, scaleX, scaleY, scaleZ // number
610
+ skewX, skewY // number | string
611
+ perspective // number | string
612
+
613
+ // Visual
614
+ opacity // number (0–1)
615
+ backgroundColor, color // string (CSS color)
616
+ filter // string (CSS filter)
617
+ transformOrigin // string
618
+
619
+ // Layout
620
+ width, height // number | string
621
+ top, left, right, bottom // number | string
622
+ margin, marginTop, marginRight, marginBottom, marginLeft
623
+ padding, paddingTop, paddingRight, paddingBottom, paddingLeft
624
+
625
+ // Typography
626
+ fontSize, lineHeight, letterSpacing // number | string
627
+ borderRadius // number | string
628
+ zIndex // number
629
+ backgroundPosition // string
630
+
631
+ // Border / outline colors
632
+ borderColor, borderTopColor, borderRightColor, borderBottomColor, borderLeftColor
633
+ outlineColor, textDecorationColor, caretColor
634
+
635
+ // SVG
636
+ fill, stroke // string (CSS color)
637
+ drawSVG // string | { start?: number; end?: number }
638
+
639
+ // Motion path
640
+ path: {
641
+ target: string | Element; // SVG <path> selector or element
642
+ align?: string | Element; // Align bounding box to this element
643
+ alignAt?: [number, number]; // Origin point [x%, y%], default [50, 50]
644
+ start?: number; // Path start (0–1), default 0
645
+ end?: number; // Path end (0–1), default 1
646
+ rotate?: boolean; // Auto-rotate along tangent
647
+ }
648
+ ```
649
+
650
+ ### StaggerVars
651
+
652
+ ```ts
653
+ interface StaggerVars {
654
+ each?: number; // Seconds between each element
655
+ amount?: number; // Total stagger spread (alternative to each)
656
+ from?: 'start' | 'center' | 'edges' | 'random' | 'end' | number;
657
+ grid?: 'auto' | [number, number]; // 2D grid stagger
658
+ axis?: 'x' | 'y'; // Grid stagger axis
659
+ ease?: string; // Easing applied to the stagger distribution
660
+ }
661
+ ```
662
+
663
+ ### RepeatConfig
664
+
665
+ ```ts
666
+ interface RepeatConfig {
667
+ times: number; // Number of additional repetitions (-1 = infinite)
668
+ delay?: number; // Seconds between repetitions
669
+ yoyo?: boolean; // Alternate direction each cycle
670
+ }
671
+ ```
672
+
673
+ ### SplitType
674
+
675
+ ```ts
676
+ type SplitType =
677
+ | 'chars'
678
+ | 'words'
679
+ | 'lines'
680
+ | 'chars,words'
681
+ | 'words,lines'
682
+ | 'chars,words,lines';
683
+ ```
684
+
685
+ Text is split into wrapper `<span>` elements before animating. `Motion.reset()` reverts the DOM.
686
+
687
+ ```ts
688
+ Motion('text-reveal', '.headline', {
689
+ from: { opacity: 0, y: 20 },
690
+ to: { opacity: 1, y: 0 },
691
+ duration: 0.5,
692
+ split: 'chars',
693
+ stagger: { each: 0.03, from: 'start' },
694
+ }).onPageLoad();
695
+ ```
696
+
697
+ ---
698
+
699
+ ## Easing
700
+
701
+ Easing names are **case-insensitive** strings. Pass them to `AnimationConfig.ease` or `StaggerVars.ease`.
702
+
703
+ | Family | Variants |
704
+ |--------|----------|
705
+ | `linear`, `none` | — |
706
+ | `power1` | `power1.in` · `power1.out` · `power1.inout` |
707
+ | `power2` | `power2.in` · `power2.out` · `power2.inout` |
708
+ | `power3` | `power3.in` · `power3.out` · `power3.inout` |
709
+ | `power4` | `power4.in` · `power4.out` · `power4.inout` |
710
+ | `sine` | `sine.in` · `sine.out` · `sine.inout` |
711
+ | `expo` | `expo.in` · `expo.out` · `expo.inout` |
712
+ | `circ` | `circ.in` · `circ.out` · `circ.inout` |
713
+ | `back` | `back.in` · `back.out` · `back.inout` |
714
+ | `elastic` | `elastic.in` · `elastic.out` · `elastic.inout` |
715
+ | `bounce` | `bounce.in` · `bounce.out` · `bounce.inout` |
716
+
717
+ Unknown strings fall back to `power1.out`.
718
+
719
+ ```ts
720
+ Motion('spring-in', '.box', {
721
+ from: { scale: 0 },
722
+ to: { scale: 1 },
723
+ duration: 0.8,
724
+ ease: 'elastic.out',
725
+ }).play();
726
+ ```
727
+
728
+ ---
729
+
730
+ ## Types
731
+
732
+ All types are re-exported from the package entry point.
733
+
734
+ ```ts
735
+ import type {
736
+ // Core
737
+ AnimationVars,
738
+ AnimationConfig,
739
+ AnimationEntry,
740
+ TargetInput,
741
+ ObjectTarget,
742
+ AnimationTarget,
743
+ EasingFunction,
744
+
745
+ // Animation options
746
+ StaggerVars,
747
+ RepeatConfig,
748
+ SplitType,
749
+ PathConfig,
750
+
751
+ // Triggers
752
+ HoverConfig,
753
+ ClickConfig,
754
+ ScrollConfig,
755
+ MouseMoveConfig,
756
+ MarkerConfig,
757
+
758
+ // Gesture
759
+ GestureConfig,
760
+ GestureEvent,
761
+ GestureAction,
762
+ GestureInputType,
763
+
764
+ // Cursor
765
+ CursorConfig,
766
+ CursorStateVars,
767
+ CursorSqueezeConfig,
768
+ } from '@motion/sdk';
769
+
770
+ // Namespace import
771
+ import { Types } from '@motion/sdk';
772
+ ```
773
+
774
+ ---
775
+
776
+ ## Browser Build
777
+
778
+ A pre-built IIFE bundle is generated at `dist/motion-sdk.browser.js` for direct `<script>` tag usage:
779
+
780
+ ```html
781
+ <script src="motion-sdk.browser.js"></script>
782
+ <script>
783
+ // Globals are exposed on window:
784
+ const { Motion, MotionTimeline } = window;
785
+
786
+ Motion('fade-in', '.hero', {
787
+ from: { opacity: 0 },
788
+ to: { opacity: 1 },
789
+ duration: 1,
790
+ }).onPageLoad();
791
+ </script>
792
+ ```
793
+
794
+ To regenerate the browser bundle:
795
+
796
+ ```bash
797
+ bun run packages/sdk/scripts/build-iife.ts
798
+ ```
799
+
800
+ ---
801
+
802
+ ## Browser Support
803
+
804
+ Modern evergreen browsers:
805
+
806
+ | Browser | Minimum |
807
+ |---------|---------|
808
+ | Chrome | 90+ |
809
+ | Firefox | 90+ |
810
+ | Safari | 15+ |
811
+ | Edge | 90+ |
812
+
813
+ ---
814
+
815
+ ## License
816
+
817
+ Proprietary — see [LICENSE](../../LICENSE) for details.