@motion.page/sdk 0.1.0 → 0.1.1

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 CHANGED
@@ -1,8 +1,10 @@
1
- # @motion/sdk
1
+ # @motion.page/sdk
2
2
 
3
- A high-performance CSS animation SDK with a declarative, config-based API. Zero runtime dependencies.
3
+ A high-performance animation SDK with a declarative API. Scroll-triggered animations, page transitions, custom cursors, gesture controls, text splitting, and more — zero runtime dependencies.
4
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)
5
+ [![npm version](https://img.shields.io/npm/v/@motion.page/sdk)](https://www.npmjs.com/package/@motion.page/sdk)
6
+ ![Bundle Size](https://img.shields.io/bundlephobia/minzip/@motion.page/sdk)
7
+ ![License](https://img.shields.io/badge/license-Proprietary-red)
6
8
 
7
9
  ---
8
10
 
@@ -30,11 +32,16 @@ A high-performance CSS animation SDK with a declarative, config-based API. Zero
30
32
  ## Installation
31
33
 
32
34
  ```bash
33
- # Not published to npm used internally within the Motion.page monorepo.
34
- # The package name is @motion/sdk
35
+ npm install @motion.page/sdk
36
+ # or
37
+ bun add @motion.page/sdk
38
+ # or
39
+ yarn add @motion.page/sdk
40
+ # or
41
+ pnpm add @motion.page/sdk
35
42
  ```
36
43
 
37
- For direct browser use, see [Browser Build](#browser-build).
44
+ For direct browser use without a bundler, see [Browser Build](#browser-build).
38
45
 
39
46
  ---
40
47
 
@@ -43,7 +50,7 @@ For direct browser use, see [Browser Build](#browser-build).
43
50
  ### Basic Animation
44
51
 
45
52
  ```ts
46
- import { Motion } from '@motion/sdk';
53
+ import { Motion } from '@motion.page/sdk';
47
54
 
48
55
  // Fade in and slide up
49
56
  Motion('hero-intro', '#hero', {
@@ -112,6 +119,22 @@ Motion('intro-sequence', [
112
119
  Motion('hero-intro').restart();
113
120
  ```
114
121
 
122
+ ### Object Animation
123
+
124
+ Plain JavaScript objects can be tweened — useful for canvas, audio, WebGL, or any non-DOM state:
125
+
126
+ ```ts
127
+ // Animate plain JS objects (useful for canvas, audio, WebGL)
128
+ const state = { volume: 0, brightness: 100 };
129
+ Motion('audio-fade', state, {
130
+ to: { volume: 1, brightness: 50 },
131
+ duration: 2,
132
+ onUpdate: () => {
133
+ audioNode.gain.value = state.volume;
134
+ },
135
+ }).play();
136
+ ```
137
+
115
138
  ---
116
139
 
117
140
  ## Core Concept
@@ -120,6 +143,13 @@ Every animation in the SDK is a **named timeline**. The name is the first argume
120
143
 
121
144
  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
145
 
146
+ If `Motion('name', target, config)` is called when `'name'` already has a timeline, the new entries are **appended** to the existing timeline rather than replacing it. To rebuild from scratch, call `.kill()` first:
147
+
148
+ ```ts
149
+ Motion('hero').kill();
150
+ Motion('hero', '#hero', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.8 }).onPageLoad();
151
+ ```
152
+
123
153
  ---
124
154
 
125
155
  ## API Reference
@@ -149,7 +179,7 @@ Calling `Motion()` with the same name on an already-existing timeline returns it
149
179
 
150
180
  | Method | Signature | Description |
151
181
  |--------|-----------|-------------|
152
- | `Motion.set` | `(target: TargetInput, vars: AnimationVars): void` | Immediately apply CSS / transform properties with no animation (like `gsap.set`). Values are written synchronously. |
182
+ | `Motion.set` | `(target: TargetInput, vars: AnimationVars): void` | Immediately apply CSS / transform properties with no animation. Goes through the full animation engine pipeline (including color parsing and transform compositing) but completes in zero time, so applied values persist on the DOM. |
153
183
  | `Motion.get` | `(name: string): Timeline \| undefined` | Get a timeline by name; returns `undefined` if none exists. |
154
184
  | `Motion.has` | `(name: string): boolean` | Check whether a named timeline is registered. |
155
185
  | `Motion.getNames` | `(): string[]` | Return the names of all registered timelines. |
@@ -184,6 +214,13 @@ Motion.reset('.animated-card');
184
214
 
185
215
  GSAP-compatible utility functions accessible via `Motion.utils`. These are drop-in replacements for `gsap.utils.*` helpers.
186
216
 
217
+ `MotionUtils` is also exported directly from the package and can be imported independently:
218
+
219
+ ```ts
220
+ import { MotionUtils } from '@motion.page/sdk';
221
+ // Same object as Motion.utils
222
+ ```
223
+
187
224
  | Method | Signature | Description |
188
225
  |--------|-----------|-------------|
189
226
  | `toArray` | `(target, scope?) → Element[]` | Convert CSS selector, NodeList, HTMLCollection, or Element to a flat array. Drop-in for `gsap.utils.toArray()`. |
@@ -297,6 +334,8 @@ tl.call(
297
334
  | `"-=0.3"` | 0.3 s before the previous entry ends |
298
335
  | `"<"` | At the same start time as the previous entry |
299
336
  | `">"` | Immediately after the previous entry ends |
337
+ | `"<0.2"` | 0.2 s after the start of the previous entry |
338
+ | `">-0.1"` | 0.1 s before the end of the previous entry |
300
339
 
301
340
  ```ts
302
341
  Motion('demo', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 1 })
@@ -320,6 +359,22 @@ tl.clear(): this
320
359
 
321
360
  All trigger methods are chainable and attach behaviour to the timeline without requiring you to manage event listeners manually.
322
361
 
362
+ #### Per-Element Triggers (`each: true`)
363
+
364
+ When targeting multiple elements, `each: true` creates independent per-element timeline instances. Without it, all matched elements share one timeline and play/reverse together.
365
+
366
+ ```ts
367
+ // WITHOUT each — hovering any card plays ALL cards
368
+ Motion('card-hover', '.card', { to: { y: -8 }, duration: 0.3 })
369
+ .onHover({ onLeave: 'reverse' });
370
+
371
+ // WITH each — each card animates independently
372
+ Motion('card-hover', '.card', { to: { y: -8 }, duration: 0.3 })
373
+ .onHover({ each: true, onLeave: 'reverse' });
374
+ ```
375
+
376
+ `each` is supported by `.onHover()`, `.onClick()`, `.onScroll()`, `.onMouseMove()`, and `.onGesture()`.
377
+
323
378
  #### `.onHover(config?)`
324
379
 
325
380
  Play on `mouseenter`, react on `mouseleave`.
@@ -375,12 +430,56 @@ interface ScrollConfig {
375
430
  end?: string; // e.g. 'bottom 20%'
376
431
  scrub?: boolean | number; // true = instant, number = smoothing seconds
377
432
  snap?: number | number[] | ((progress: number) => number); // Snap scroll progress
378
- markers?: boolean | MarkerConfig; // Debug markers
433
+ markers?: boolean | MarkerConfig; // Debug markers (pass object for styling)
379
434
  scroller?: string | Element; // Custom scroll container
380
- pin?: boolean | string; // Pin the element during scroll
435
+ pin?: boolean | string; // true = pin animation target; string = pin a different element
381
436
  pinSpacing?: boolean | 'margin' | 'padding';
382
437
  each?: boolean;
383
- toggleActions?: string; // e.g. 'play none none reverse'
438
+ toggleActions?: string; // Format: 'onEnter onLeave onEnterBack onLeaveBack'
439
+ }
440
+ ```
441
+
442
+ **`start` / `end` defaults:**
443
+ - Without `pin`: `start: 'top bottom'`, `end: 'bottom top'`
444
+ - With `pin`: `start: 'top top'`, `end: 'bottom top'`
445
+
446
+ **Relative `end` with `+=`:** When `end` starts with `+=`, the value is a distance measured from the `start` position:
447
+
448
+ ```ts
449
+ .onScroll({ start: 'top center', end: '+=800' }) // 800px of scroll travel
450
+ .onScroll({ start: 'top top', end: '+=100vh' }) // one viewport height of scroll
451
+ ```
452
+
453
+ **`pin: string`** pins a different element (e.g. a parent wrapper) while the animated child scrolls:
454
+
455
+ ```ts
456
+ // Pin the parent section while the child content animates
457
+ Motion('content-reveal', '.content', {
458
+ from: { opacity: 0, y: 40 },
459
+ to: { opacity: 1, y: 0 },
460
+ duration: 1,
461
+ }).onScroll({ scrub: true, pin: '.section-wrapper', start: 'top top', end: '+=600' });
462
+ ```
463
+
464
+ **`toggleActions`** controls what happens at each scroll boundary. Format: `"onEnter onLeave onEnterBack onLeaveBack"`. Default: `"play reverse play reverse"`. Valid actions: `play`, `pause`, `reverse`, `restart`, `reset`, `complete`, `kill`, `none`.
465
+
466
+ ```ts
467
+ // Play once — never reverse (common for reveal animations)
468
+ .onScroll({ toggleActions: 'play none none none' });
469
+
470
+ // Re-animate every time it enters the viewport
471
+ .onScroll({ toggleActions: 'restart none none reset' });
472
+ ```
473
+
474
+ **`markers`** accepts `true` for default debug markers, or a `MarkerConfig` object for custom styling:
475
+
476
+ ```ts
477
+ interface MarkerConfig {
478
+ startColor?: string; // Default: 'green'
479
+ endColor?: string; // Default: 'red'
480
+ fontSize?: string; // e.g. '12px'
481
+ fontWeight?: string;
482
+ indent?: number; // Horizontal offset in pixels
384
483
  }
385
484
  ```
386
485
 
@@ -428,6 +527,15 @@ interface MouseMoveConfig {
428
527
  }
429
528
  ```
430
529
 
530
+ **Defaults:**
531
+
532
+ | Option | Default | Notes |
533
+ |--------|---------|-------|
534
+ | `type` | `'distance'` | |
535
+ | `startProgress` | `0.5` | Progress value when mouse is at rest |
536
+ | `leaveProgress` | `0.5` | Progress to animate to on mouse leave |
537
+ | `smooth` | `0.1` | `0` = instant tracking, `1` = maximum lag |
538
+
431
539
  ```ts
432
540
  Motion('parallax-depth', '.layer', {
433
541
  from: { x: -20, y: -20 },
@@ -438,7 +546,7 @@ Motion('parallax-depth', '.layer', {
438
546
 
439
547
  #### `.onPageLoad()`
440
548
 
441
- Play the animation automatically when the page finishes loading.
549
+ Play the animation automatically when the page finishes loading. If called after `DOMContentLoaded` has already fired (common in SPAs or scripts placed at the bottom of `<body>`), the animation plays immediately.
442
550
 
443
551
  ```ts
444
552
  Motion('page-intro', [
@@ -447,6 +555,45 @@ Motion('page-intro', [
447
555
  ]).onPageLoad();
448
556
  ```
449
557
 
558
+ #### `.onPageExit(config?)`
559
+
560
+ Intercept link clicks, play the exit animation, then navigate to the destination URL after the timeline completes. Works on any website with no server-side dependencies.
561
+
562
+ ```ts
563
+ interface PageExitConfig {
564
+ /** 'all' (default) | 'include' | 'exclude' */
565
+ mode?: 'all' | 'include' | 'exclude';
566
+ /** CSS selectors for links — required when mode is 'include' or 'exclude' */
567
+ selectors?: string;
568
+ /** Href patterns to skip automatically. Note: 'mailto' also skips tel: links. */
569
+ skipHref?: ('anchor' | 'javascript' | 'mailto')[];
570
+ }
571
+ ```
572
+
573
+ - `mode: 'all'` (default) — intercepts every `<a>` on the page
574
+ - `mode: 'include'` — only links matching `selectors`
575
+ - `mode: 'exclude'` — all links except those matching `selectors`
576
+ - Automatically skips `target="_blank"` links and modifier-key clicks (Cmd/Ctrl/Shift/Alt)
577
+
578
+ ```ts
579
+ // Fade-out on page exit — all links
580
+ Motion('page-exit', 'body', {
581
+ to: { opacity: 0 },
582
+ duration: 0.4,
583
+ ease: 'power2.in',
584
+ }).onPageExit();
585
+
586
+ // Only internal nav links
587
+ Motion('page-exit', 'body', {
588
+ to: { opacity: 0 },
589
+ duration: 0.4,
590
+ }).onPageExit({
591
+ mode: 'include',
592
+ selectors: 'nav a',
593
+ skipHref: ['anchor', 'mailto'],
594
+ });
595
+ ```
596
+
450
597
  #### `.onGesture(config)`
451
598
 
452
599
  Respond to pointer, touch, wheel, or scroll gestures with fine-grained event-to-action mapping.
@@ -484,6 +631,33 @@ interface GestureConfig {
484
631
  }
485
632
  ```
486
633
 
634
+ **Config defaults:**
635
+
636
+ | Option | Default | Unit | Notes |
637
+ |--------|---------|------|-------|
638
+ | `tolerance` | `1` | px | Min movement before direction events fire |
639
+ | `dragMinimum` | `10` | px | Distance before `Drag` fires |
640
+ | `wheelSpeed` | `1` | multiplier | Scales wheel delta |
641
+ | `scrollSpeed` | `1` | multiplier | Scales scroll delta |
642
+ | `stopDelay` | `150` | ms | Idle time after movement before `Stop` fires |
643
+ | `smooth` | `0` | 0–1 | Smoothness for `progressUp`/`progressDown` actions |
644
+ | `animationStep` | `0.1` | 0–1 | Progress step per event for `progressUp`/`progressDown` |
645
+
646
+ **Event distinctions:**
647
+
648
+ - `Up`/`Down`/`Left`/`Right` — fire **continuously** during movement
649
+ - `UpComplete`/`DownComplete`/etc. — fire **once on release** if that direction was active
650
+ - `PressInit` — fires immediately on press, before start position is recorded
651
+ - `Press` — fires after start position is recorded
652
+ - `Hover`/`HoverEnd` — require a `target` element (not the window)
653
+ - `playNext`/`playPrevious` actions — only work when `each: true` is set
654
+
655
+ `animationStep` can be a single number or a per-event map:
656
+
657
+ ```ts
658
+ animationStep: { Up: 0.2, Down: 0.1 } // different step size per direction
659
+ ```
660
+
487
661
  ```ts
488
662
  Motion('swipe-gallery', '.gallery', {
489
663
  from: { x: 0 },
@@ -518,6 +692,18 @@ interface CursorConfig {
518
692
  }
519
693
  ```
520
694
 
695
+ **`CursorStateVars`** is the shape used for `default`, `hover`, and `click`. The `targets` field controls which elements trigger that state:
696
+
697
+ ```ts
698
+ interface CursorStateVars {
699
+ targets?: string[]; // CSS selectors that trigger this state (e.g. ['a', 'button', '.btn'])
700
+ duration?: number; // Transition duration in seconds (default: 0.15)
701
+ ease?: string; // Easing (default: 'power3.inOut')
702
+ enabled?: boolean; // Whether state is active (default: true)
703
+ [key: string]: any; // Any CSS property: width, height, backgroundColor, scale, etc.
704
+ }
705
+ ```
706
+
521
707
  ```ts
522
708
  Motion('custom-cursor', 'body', {
523
709
  to: { opacity: 1 },
@@ -527,11 +713,52 @@ Motion('custom-cursor', 'body', {
527
713
  smooth: 0.08,
528
714
  hideNative: true,
529
715
  default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
530
- hover: { width: 40, height: 40, backgroundColor: 'transparent', borderColor: '#fff' },
531
- click: { scale: 0.8 },
716
+ hover: {
717
+ targets: ['a', 'button', '[data-cursor-hover]'],
718
+ width: 40, height: 40,
719
+ backgroundColor: 'transparent',
720
+ borderColor: '#fff',
721
+ },
722
+ click: { scale: 0.8 },
723
+ });
724
+ ```
725
+
726
+ **`type: 'text'`** — reads text from `mp-cursor-text` or `mp-cursor-tooltip` HTML attributes and displays it inside the cursor element. The `text` config object sets CSS properties on the text node:
727
+
728
+ ```ts
729
+ Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
730
+ target: '#cursor',
731
+ type: 'text',
732
+ hideNative: true,
733
+ default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
734
+ hover: { width: 64, height: 64 },
735
+ text: { fontSize: '12px', color: '#000', fontWeight: 'bold' },
532
736
  });
533
737
  ```
534
738
 
739
+ ```html
740
+ <a href="/about" mp-cursor-text="About Us">About</a>
741
+ <button mp-cursor-tooltip="Click me">Button</button>
742
+ ```
743
+
744
+ **`type: 'media'`** — reads an image or video URL from the `mp-cursor-media` attribute and renders it inside the cursor. Supports http/https and relative URLs. The `media` config object sets CSS on the media element:
745
+
746
+ ```ts
747
+ Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
748
+ target: '#cursor',
749
+ type: 'media',
750
+ hideNative: true,
751
+ default: { width: 48, height: 48, borderRadius: '50%' },
752
+ hover: { width: 120, height: 80 },
753
+ media: { borderRadius: '8px', objectFit: 'cover' },
754
+ });
755
+ ```
756
+
757
+ ```html
758
+ <div class="project-card" mp-cursor-media="/images/preview.jpg">…</div>
759
+ <div class="video-card" mp-cursor-media="https://cdn.example.com/preview.mp4">…</div>
760
+ ```
761
+
535
762
  ---
536
763
 
537
764
  ### Lifecycle Callbacks
@@ -585,8 +812,10 @@ interface AnimationConfig {
585
812
  delay?: number; // Seconds before animation begins
586
813
  ease?: string; // Easing name string, see Easing section
587
814
  stagger?: number | StaggerVars; // Per-element stagger delay
588
- repeat?: RepeatConfig; // Loop/yoyo configuration
815
+ repeat?: number | RepeatConfig; // Repeat count shorthand or full config (see below)
589
816
  split?: SplitType; // Text splitting for per-char/word/line animation
817
+ mask?: boolean; // Wrap split elements in overflow:hidden for reveal effects
818
+ fit?: FitConfig; // FLIP-style morph toward another element
590
819
  axis?: 'x' | 'y'; // Axis binding for onMouseMove animations
591
820
 
592
821
  // Lifecycle
@@ -638,13 +867,68 @@ drawSVG // string | { start?: number; end?: n
638
867
 
639
868
  // Motion path
640
869
  path: {
641
- target: string | Element; // SVG <path> selector or element
870
+ target: string | Element | string; // SVG <path> selector, element, or raw path data (starts with M/m)
642
871
  align?: string | Element; // Align bounding box to this element
643
872
  alignAt?: [number, number]; // Origin point [x%, y%], default [50, 50]
644
873
  start?: number; // Path start (0–1), default 0
645
874
  end?: number; // Path end (0–1), default 1
646
875
  rotate?: boolean; // Auto-rotate along tangent
647
876
  }
877
+
878
+ // CSS custom properties
879
+ '--my-var' // any CSS custom property name (string)
880
+ ```
881
+
882
+ ### drawSVG
883
+
884
+ Animate the visible portion of an SVG stroke. The element must have a `stroke` and a set `stroke-dasharray` (or the SDK will compute it automatically).
885
+
886
+ | Format | Meaning |
887
+ |--------|---------|
888
+ | `"0% 100%"` | Full stroke visible |
889
+ | `"0% 0%"` | Stroke fully hidden (start position for a draw-in) |
890
+ | `"20% 80%"` | Middle portion only |
891
+ | `"50%"` | Shorthand for `"0% 50%"` |
892
+ | `"100px 500px"` | Pixel range along the stroke |
893
+ | `{ start: 20, end: 80 }` | Object form — values are **percentages 0–100**, not 0–1 |
894
+
895
+ ```ts
896
+ // Animate stroke from hidden to fully drawn
897
+ Motion('draw-path', 'path#line', {
898
+ from: { drawSVG: '0% 0%' },
899
+ to: { drawSVG: '0% 100%' },
900
+ duration: 1.2,
901
+ ease: 'power2.inOut',
902
+ }).onPageLoad();
903
+
904
+ // Object format — percentages, not 0–1
905
+ Motion('draw-partial', '#circle', {
906
+ to: { drawSVG: { start: 20, end: 80 } },
907
+ duration: 0.8,
908
+ }).play();
909
+ ```
910
+
911
+ ### path
912
+
913
+ `path.target` accepts a CSS selector, an `Element`, or raw SVG path data (a string starting with `M` or `m`):
914
+
915
+ ```ts
916
+ // Inline path data — no DOM element required
917
+ Motion('fly', '.icon', {
918
+ to: { path: { target: 'M 0 100 C 50 0 150 200 200 100', rotate: true } },
919
+ duration: 2,
920
+ }).play();
921
+ ```
922
+
923
+ ### CSS Custom Properties
924
+
925
+ CSS custom properties can be animated by passing the property name as a string key:
926
+
927
+ ```ts
928
+ Motion('theme', ':root', {
929
+ to: { '--primary-hue': 240, '--accent-opacity': 0.8 },
930
+ duration: 0.5,
931
+ }).onClick({ target: '#theme-btn' });
648
932
  ```
649
933
 
650
934
  ### StaggerVars
@@ -662,12 +946,23 @@ interface StaggerVars {
662
946
 
663
947
  ### RepeatConfig
664
948
 
949
+ The `repeat` field accepts either a plain number or a `RepeatConfig` object:
950
+
665
951
  ```ts
952
+ // Shorthand — number of additional repetitions
953
+ repeat: 3 // repeat 3 more times after the first play
954
+ repeat: -1 // repeat infinitely
955
+
956
+ // Full config
666
957
  interface RepeatConfig {
667
958
  times: number; // Number of additional repetitions (-1 = infinite)
668
959
  delay?: number; // Seconds between repetitions
669
960
  yoyo?: boolean; // Alternate direction each cycle
670
961
  }
962
+
963
+ // Examples
964
+ repeat: { times: -1, yoyo: true, delay: 0.2 } // infinite yoyo with pause between cycles
965
+ repeat: { times: 2, yoyo: true, delay: 0.5 } // 2 extra cycles, yoyo, 0.5s pause
671
966
  ```
672
967
 
673
968
  ### SplitType
@@ -682,7 +977,16 @@ type SplitType =
682
977
  | 'chars,words,lines';
683
978
  ```
684
979
 
685
- Text is split into wrapper `<span>` elements before animating. `Motion.reset()` reverts the DOM.
980
+ Text is split into wrapper `<span>` elements before animating. `Motion.reset()` reverts the DOM. Inline elements (like `<span class="accent">`) are preserved during splitting.
981
+
982
+ Split elements receive data attributes for CSS targeting:
983
+
984
+ | Attribute | Set on | Index attribute |
985
+ |-----------|--------|-----------------|
986
+ | `[data-split-char]` | each character span | `data-char-index` |
987
+ | `[data-split-word]` | each word span | `data-word-index` |
988
+ | `[data-split-line]` | each line span | `data-line-index` |
989
+ | `[data-split-mask]` | overflow wrapper (when `mask: true`) | — |
686
990
 
687
991
  ```ts
688
992
  Motion('text-reveal', '.headline', {
@@ -694,6 +998,48 @@ Motion('text-reveal', '.headline', {
694
998
  }).onPageLoad();
695
999
  ```
696
1000
 
1001
+ ### mask
1002
+
1003
+ When `mask: true` is used together with `split`, each split element (char, word, or line) is wrapped in a parent with `overflow: hidden`. This clips animated content to its natural bounds, creating a 'reveal' effect — for example, animating `y: '100%'` makes text slide up from behind an invisible edge.
1004
+
1005
+ ```ts
1006
+ Motion('reveal', 'h1', {
1007
+ split: 'lines',
1008
+ mask: true,
1009
+ from: { y: '100%' },
1010
+ to: { y: 0 },
1011
+ stagger: 0.1,
1012
+ }).onPageLoad();
1013
+ ```
1014
+
1015
+ ### FitConfig
1016
+
1017
+ FLIP-style morph animation. When `fit` is set, `from` and `to` are ignored — the SDK measures both elements' bounding rects at play time and animates the visual delta between them.
1018
+
1019
+ ```ts
1020
+ interface FitConfig {
1021
+ /** CSS selector for the target element to morph toward */
1022
+ target: string;
1023
+ /** Convert to absolute positioning during animation. Default: false */
1024
+ absolute?: boolean;
1025
+ /** Include scale changes. Default: true */
1026
+ scale?: boolean;
1027
+ /** Animate actual width/height instead of scaleX/scaleY. Default: false */
1028
+ resize?: boolean;
1029
+ }
1030
+ ```
1031
+
1032
+ - **`scale: true`** (default) — animates using `scaleX`/`scaleY`. Fast, GPU-accelerated, but can distort text, borders, and box-shadows.
1033
+ - **`resize: true`** — animates actual `width`/`height` properties instead. No visual distortion, but triggers layout reflow. Mutually exclusive with `scale`.
1034
+
1035
+ ```ts
1036
+ Motion('reorder', '.container', {
1037
+ fit: { target: '.item', resize: true },
1038
+ duration: 0.5,
1039
+ ease: 'power2.inOut',
1040
+ }).play();
1041
+ ```
1042
+
697
1043
  ---
698
1044
 
699
1045
  ## Easing
@@ -731,6 +1077,8 @@ Motion('spring-in', '.box', {
731
1077
 
732
1078
  All types are re-exported from the package entry point.
733
1079
 
1080
+ Most users won't need to import types directly — TypeScript infers everything from the `Motion()` function signature, so you get full autocomplete and type checking out of the box. These exports are provided for advanced use cases like building wrapper libraries or typing standalone config objects.
1081
+
734
1082
  ```ts
735
1083
  import type {
736
1084
  // Core
@@ -747,6 +1095,7 @@ import type {
747
1095
  RepeatConfig,
748
1096
  SplitType,
749
1097
  PathConfig,
1098
+ FitConfig,
750
1099
 
751
1100
  // Triggers
752
1101
  HoverConfig,
@@ -754,30 +1103,33 @@ import type {
754
1103
  ScrollConfig,
755
1104
  MouseMoveConfig,
756
1105
  MarkerConfig,
1106
+ PageExitConfig,
757
1107
 
758
1108
  // Gesture
759
1109
  GestureConfig,
760
1110
  GestureEvent,
761
1111
  GestureAction,
762
1112
  GestureInputType,
1113
+ TimelineAction,
763
1114
 
764
1115
  // Cursor
765
1116
  CursorConfig,
766
1117
  CursorStateVars,
767
1118
  CursorSqueezeConfig,
768
- } from '@motion/sdk';
1119
+ } from '@motion.page/sdk';
769
1120
 
770
1121
  // Namespace import
771
- import { Types } from '@motion/sdk';
1122
+ import { Types } from '@motion.page/sdk';
772
1123
  ```
773
1124
 
774
1125
  ---
775
1126
 
776
1127
  ## Browser Build
777
1128
 
778
- A pre-built IIFE bundle is generated at `dist/motion-sdk.browser.js` for direct `<script>` tag usage:
1129
+ A pre-built IIFE bundle (`motion-sdk.browser.js`) is included in the package's `dist/` folder and can be used directly via a `<script>` tag — no bundler required. You can also load it from a CDN that mirrors npm packages (e.g. jsDelivr or unpkg).
779
1130
 
780
1131
  ```html
1132
+ <!-- From your own server / CDN -->
781
1133
  <script src="motion-sdk.browser.js"></script>
782
1134
  <script>
783
1135
  // Globals are exposed on window:
@@ -791,12 +1143,6 @@ A pre-built IIFE bundle is generated at `dist/motion-sdk.browser.js` for direct
791
1143
  </script>
792
1144
  ```
793
1145
 
794
- To regenerate the browser bundle:
795
-
796
- ```bash
797
- bun run packages/sdk/scripts/build-iife.ts
798
- ```
799
-
800
1146
  ---
801
1147
 
802
1148
  ## Browser Support