@syncedco/motion 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 (75) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/CODE_OF_CONDUCT.md +26 -0
  3. package/CONTRIBUTING.md +100 -0
  4. package/LICENSE +22 -0
  5. package/README.md +278 -0
  6. package/SECURITY.md +36 -0
  7. package/SUPPORT.md +38 -0
  8. package/TRADEMARKS.md +14 -0
  9. package/bin/synced-motion.mjs +5 -0
  10. package/dist/astro.d.ts +15 -0
  11. package/dist/astro.js +51 -0
  12. package/dist/astro.js.map +1 -0
  13. package/dist/chunk-24KKECMK.js +281 -0
  14. package/dist/chunk-24KKECMK.js.map +1 -0
  15. package/dist/chunk-3WUVBPYX.js +162 -0
  16. package/dist/chunk-3WUVBPYX.js.map +1 -0
  17. package/dist/chunk-HCWQV65E.js +20 -0
  18. package/dist/chunk-HCWQV65E.js.map +1 -0
  19. package/dist/chunk-JET2MYEG.js +2287 -0
  20. package/dist/chunk-JET2MYEG.js.map +1 -0
  21. package/dist/chunk-KA3OPI4F.js +40 -0
  22. package/dist/chunk-KA3OPI4F.js.map +1 -0
  23. package/dist/chunk-ML7HTCZT.js +642 -0
  24. package/dist/chunk-ML7HTCZT.js.map +1 -0
  25. package/dist/chunk-NIXAEIHN.js +23 -0
  26. package/dist/chunk-NIXAEIHN.js.map +1 -0
  27. package/dist/chunk-VOIQPPQZ.js +299 -0
  28. package/dist/chunk-VOIQPPQZ.js.map +1 -0
  29. package/dist/chunk-WCG7TQSH.js +31 -0
  30. package/dist/chunk-WCG7TQSH.js.map +1 -0
  31. package/dist/cli.js +382 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/full.d.ts +13 -0
  34. package/dist/full.js +201 -0
  35. package/dist/full.js.map +1 -0
  36. package/dist/index.d.ts +249 -0
  37. package/dist/index.js +183 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/lenis.d.ts +19 -0
  40. package/dist/lenis.js +7 -0
  41. package/dist/lenis.js.map +1 -0
  42. package/dist/mcp.d.ts +16 -0
  43. package/dist/mcp.js +82 -0
  44. package/dist/mcp.js.map +1 -0
  45. package/dist/plugins.d.ts +14 -0
  46. package/dist/plugins.js +17 -0
  47. package/dist/plugins.js.map +1 -0
  48. package/dist/react.d.ts +7 -0
  49. package/dist/react.js +32 -0
  50. package/dist/react.js.map +1 -0
  51. package/dist/recipes.d.ts +76 -0
  52. package/dist/recipes.js +129 -0
  53. package/dist/recipes.js.map +1 -0
  54. package/dist/styles.css +57 -0
  55. package/dist/styles.css.map +1 -0
  56. package/dist/svelte.d.ts +10 -0
  57. package/dist/svelte.js +30 -0
  58. package/dist/svelte.js.map +1 -0
  59. package/dist/vue.d.ts +13 -0
  60. package/dist/vue.js +35 -0
  61. package/dist/vue.js.map +1 -0
  62. package/dist/wordpress.d.ts +15 -0
  63. package/dist/wordpress.js +49 -0
  64. package/dist/wordpress.js.map +1 -0
  65. package/docs/API.md +176 -0
  66. package/docs/ATTRIBUTE-API.md +358 -0
  67. package/docs/CAPABILITIES.md +82 -0
  68. package/docs/INTEGRATIONS.md +92 -0
  69. package/docs/PERFORMANCE.md +150 -0
  70. package/docs/README.md +35 -0
  71. package/docs/RECIPE-REFERENCE.md +1358 -0
  72. package/docs/RECIPES.md +115 -0
  73. package/docs/TOOLING.md +65 -0
  74. package/docs/WEBFLOW-MIGRATION.md +39 -0
  75. package/package.json +167 -0
@@ -0,0 +1,358 @@
1
+ # Declarative attribute API
2
+
3
+ Hand-written guide to the most commonly used recipes, with the reasoning behind
4
+ each contract. For the complete generated list of all sixty, see
5
+ [the recipe reference](RECIPE-REFERENCE.md).
6
+
7
+ Every attribute uses the `data-motion-` prefix.
8
+
9
+ ## Reveal
10
+
11
+ ```html
12
+ <h2 class="sf-text-h2" data-motion-reveal="up">Built for motion</h2>
13
+ ```
14
+
15
+ Values: `fade`, `up`, `down`, `left`, `right`, or `scale`.
16
+
17
+ Optional attributes: `data-motion-duration`, `data-motion-delay`, `data-motion-ease`, `data-motion-start`, and `data-motion-once`.
18
+
19
+ ### Directional, scale, and clip reveals
20
+
21
+ ```html
22
+ <article data-motion-reveal-directional="left">...</article>
23
+ <figure data-motion-reveal-scale="0.94">...</figure>
24
+ <figure data-motion-reveal-clip="start">
25
+ <img data-motion-reveal-clip-content src="project.jpg" alt="Project description" />
26
+ </figure>
27
+ ```
28
+
29
+ Directional values are `left`, `right`, `up`, or `down`. Scale values are
30
+ unitless. Clip values are `start` or `end`. Each effect is applied only after
31
+ JavaScript initializes, clears its temporary presentation when complete, and
32
+ leaves the authored final state unchanged for reduced motion.
33
+
34
+ ## Stagger
35
+
36
+ ```html
37
+ <div class="sf-auto-grid" data-motion-stagger="0.08" data-motion-stagger-target=".card">
38
+ <article class="card">...</article>
39
+ <article class="card">...</article>
40
+ </div>
41
+ ```
42
+
43
+ ## Split text
44
+
45
+ ```html
46
+ <h1
47
+ data-motion-split="lines"
48
+ data-motion-stagger="0.08"
49
+ data-motion-split-mask="true"
50
+ >
51
+ Editorial motion that follows the layout.
52
+ </h1>
53
+ ```
54
+
55
+ Use `lines`, `words`, `chars`, or a comma-separated combination. Line splits automatically rebuild after font or width changes. Screen readers retain the unsplit accessible label. Masks are opt-in because tight editorial line-height can otherwise clip ascenders, descenders, and punctuation.
56
+
57
+ ### Character, typewriter, and highlight treatments
58
+
59
+ ```html
60
+ <h2 data-motion-chars-shimmer>Character rhythm</h2>
61
+ <p data-motion-typewriter data-motion-typewriter-speed="0.035">System ready</p>
62
+ <p data-motion-text-highlight>
63
+ Motion follows <span data-motion-text-highlight-mark>meaning</span>.
64
+ </p>
65
+ ```
66
+
67
+ Character and typewriter recipes use SplitText with its automatic accessible
68
+ label and revert the generated wrappers during cleanup. Add
69
+ `data-motion-typewriter-load` when a typewriter should start on load instead of on
70
+ viewport entry. The highlight recipe only scales the marked visual element;
71
+ the text remains present in document order at all times.
72
+
73
+ ## Parallax
74
+
75
+ ```html
76
+ <figure class="sf-frame" data-motion-parallax-scene>
77
+ <img data-motion-parallax="10" alt="" />
78
+ </figure>
79
+ ```
80
+
81
+ ## Scroll progress and depth stack
82
+
83
+ ```html
84
+ <article data-motion-scroll-progress>
85
+ <span data-motion-scroll-progress-meter aria-hidden="true"></span>
86
+ <output data-motion-scroll-progress-label aria-label="Reading progress"></output>
87
+ </article>
88
+
89
+ <section data-motion-scroll-depth-stack>
90
+ <article data-motion-depth-card>...</article>
91
+ <article data-motion-depth-card>...</article>
92
+ </section>
93
+ ```
94
+
95
+ The meter uses scale rather than width. The label is optional. Depth cards use
96
+ only opacity and transform and remain fully visible for reduced motion.
97
+
98
+ ## Pinned chapters and product explainers
99
+
100
+ ```html
101
+ <section data-motion-pinned-chapters>
102
+ <div data-motion-pin-target>...</div>
103
+ <article data-motion-pinned-chapter>Chapter one</article>
104
+ <figure data-motion-pinned-visual>...</figure>
105
+ </section>
106
+
107
+ <section data-motion-product-explainer>
108
+ <div data-motion-pin-target>...</div>
109
+ <article data-motion-product-step>Step one</article>
110
+ <figure data-motion-product-media>...</figure>
111
+ </section>
112
+ ```
113
+
114
+ The runtime synchronizes `data-active` and `aria-current`; consuming CSS owns
115
+ visibility and layout. Without motion, content remains in normal document flow.
116
+
117
+ ## Horizontal galleries and comparison
118
+
119
+ ```html
120
+ <section data-motion-horizontal-gallery>
121
+ <div data-motion-horizontal-pin>
122
+ <div data-motion-horizontal-viewport>
123
+ <div data-motion-horizontal-track>
124
+ <article data-motion-horizontal-card>...</article>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </section>
129
+
130
+ <figure data-motion-comparison>
131
+ <img src="before.jpg" alt="Before" />
132
+ <div data-motion-comparison-after><img src="after.jpg" alt="After" /></div>
133
+ <input data-motion-comparison-range type="range" min="0" max="100" value="50" aria-label="Compare before and after" />
134
+ </figure>
135
+ ```
136
+
137
+ Use `data-motion-horizontal-snap`, `data-motion-horizontal-feature-rail`, or
138
+ `data-motion-horizontal-logo-reel` on the root for the related recipes. Horizontal
139
+ scroll movement is linear and transform-based. The comparison recipe retains a
140
+ native keyboard-operable range input.
141
+
142
+ ## Marquee
143
+
144
+ ```html
145
+ <div data-motion-marquee data-motion-marquee-duration="24">
146
+ <div data-motion-marquee-track>
147
+ <div>Semantic motion · Fluid systems ·</div>
148
+ <div aria-hidden="true">Semantic motion · Fluid systems ·</div>
149
+ </div>
150
+ </div>
151
+ ```
152
+
153
+ The track contains two identical groups so its transform can loop seamlessly. `data-motion-marquee-duration` sets the loop duration in seconds; add `data-motion-marquee-direction="right"` to reverse it. Marquees pause while off-screen, restore their authored state during cleanup, and remain static when reduced motion is requested.
154
+
155
+ ## Scroll steps
156
+
157
+ ```html
158
+ <section data-motion-scroll-steps data-motion-end="bottom bottom">
159
+ <div data-motion-pin-target>
160
+ <nav>
161
+ <a data-motion-step-link>One</a>
162
+ <a data-motion-step-link>Two</a>
163
+ </nav>
164
+ <div data-motion-step-panel>First panel</div>
165
+ <div data-motion-step-panel>Second panel</div>
166
+ </div>
167
+ </section>
168
+ ```
169
+
170
+ The runtime only toggles `data-active` and `aria-current`. Synced Flow or project CSS owns the colors and layout.
171
+
172
+ ## Scroll exit
173
+
174
+ ```html
175
+ <section data-motion-scroll-exit-scene>
176
+ <div
177
+ data-motion-scroll-exit="down"
178
+ data-motion-exit-distance="9rem"
179
+ data-motion-exit-end="bottom 35%"
180
+ >
181
+ ...
182
+ </div>
183
+ </section>
184
+ ```
185
+
186
+ The element translates `down` or `up` and fades as the scene leaves the viewport. The motion is scrubbed, uses `rem` distances, and is disabled when reduced motion is requested.
187
+
188
+ ## Scroll statement
189
+
190
+ ```html
191
+ <section data-motion-scroll-statement data-motion-scrub="0.8">
192
+ <div data-motion-statement-pin>
193
+ <p data-motion-statement-label>A short label</p>
194
+ <h2 data-motion-statement-heading>
195
+ <span data-motion-statement-lead>The complete thought </span>
196
+ <span data-motion-statement-inline-accent>with emphasis.</span>
197
+ </h2>
198
+ <span data-motion-statement-hero-accent aria-hidden="true">emphasis</span>
199
+ <div data-motion-statement-details>
200
+ <p data-motion-statement-detail>Supporting content</p>
201
+ </div>
202
+ </div>
203
+ </section>
204
+ ```
205
+
206
+ The section pins while scrolling and begins with an oversized standalone accent. That accent contracts and crossfades into the full centered statement before the collapsed details expand and reveal in sequence. The markup remains readable without JavaScript and the pinned scrub sequence is disabled for reduced motion.
207
+
208
+ ## Scroll drift
209
+
210
+ ```html
211
+ <section data-motion-scroll-drift data-motion-scrub="0.8">
212
+ <div data-motion-drift-layer aria-hidden="true">Oversized background mark</div>
213
+ <div>Readable foreground content</div>
214
+ </section>
215
+ ```
216
+
217
+ The decorative layer moves from `x: 10%` and transparent to its resting position at ten-percent opacity while the section travels from below to above the viewport. Optional `data-motion-drift-from` and `data-motion-drift-opacity` attributes override those defaults. Reduced motion leaves the decorative layer in its authored CSS state.
218
+
219
+ ## Founder statement
220
+
221
+ ```html
222
+ <section data-motion-founder-scene data-motion-scrub="0.8">
223
+ <div class="sticky-frame">
224
+ <img src="background.avif" alt="" />
225
+ <div data-motion-founder-content>
226
+ <h2 data-motion-founder-heading>A centered statement revealed word by word.</h2>
227
+ </div>
228
+ </div>
229
+ </section>
230
+ ```
231
+
232
+ The first content layer fades in while SplitText words rise from below with the measured Union Jack AI timing, holds briefly, then moves down and fades away. The statement background crossfades into a second locally owned background while an oversized metric, label, and three detail columns scale and rise into view. The consuming site owns the sticky frame, content, and background styling. Reduced motion leaves the unsplit heading and authored layout visible.
233
+
234
+ ## Media expansion
235
+
236
+ ```html
237
+ <section data-motion-media-expand data-motion-scrub="0.8">
238
+ <div class="sticky-frame">
239
+ <p data-motion-media-expand-prompt>Keep scrolling</p>
240
+ <span data-motion-media-expand-label="start">©2026</span>
241
+ <span data-motion-media-expand-label="end">Showreel</span>
242
+ <figure data-motion-media-expand-frame>
243
+ <img src="sample.jpg" alt="Sample project artwork" />
244
+ <figcaption data-motion-media-expand-caption>Play showreel</figcaption>
245
+ </figure>
246
+ </div>
247
+ </section>
248
+ ```
249
+
250
+ The image begins as a cropped central pill, expands to the viewport edges through a scrubbed clip-path, and gently scales its contents down. One shared expansion value calculates both the media inset and label anchors on every update, keeping each label immediately outside the true mask edge. When a gutter can no longer contain a label, that label exits the viewport instead of moving behind or over the image. The prompt exits early, the media action appears midway, and the CSS-sticky frame releases to reveal the following footer or section. Reduced motion keeps the authored full media frame readable.
251
+
252
+ ## Hover media
253
+
254
+ ```html
255
+ <div data-motion-hover-group>
256
+ <a data-motion-hover-key="mission">Mission</a>
257
+ <a data-motion-hover-key="work">Work</a>
258
+ <img data-motion-hover-media="mission" alt="" />
259
+ <img data-motion-hover-media="work" alt="" />
260
+ </div>
261
+ ```
262
+
263
+ Keyboard focus and pointer hover use the same state path.
264
+
265
+ ## Menu
266
+
267
+ ```html
268
+ <nav data-motion-menu>
269
+ <button data-motion-menu-trigger aria-controls="main-menu">Menu</button>
270
+ <div id="main-menu" data-motion-menu-panel>
271
+ <a data-motion-menu-item href="/work">Work</a>
272
+ </div>
273
+ </nav>
274
+ ```
275
+
276
+ The controller owns `aria-expanded`, `aria-hidden`, Escape handling, focus containment, focus return, and scroll locking.
277
+
278
+ ## Expanding panels
279
+
280
+ ```html
281
+ <div data-motion-expand-group data-motion-expand-default="3">
282
+ <article tabindex="0" data-motion-expand-panel>...</article>
283
+ <article tabindex="0" data-motion-expand-panel>...</article>
284
+ <article tabindex="0" data-motion-expand-panel>...</article>
285
+ <article tabindex="0" data-motion-expand-panel>...</article>
286
+ </div>
287
+ ```
288
+
289
+ Pointer hover and keyboard focus toggle `data-active` on exactly one panel. The consuming site controls expansion sizes, artwork, and content visibility with CSS.
290
+
291
+ ## Counters, progress, and ambient loops
292
+
293
+ ```html
294
+ <output data-motion-counter data-motion-counter-from="0" data-motion-counter-to="120" data-motion-counter-value>120</output>
295
+ <figure data-motion-progress-ring data-motion-progress="72">
296
+ <svg viewBox="0 0 120 120"><circle data-motion-progress-ring-value cx="60" cy="60" r="48" /></svg>
297
+ <output data-motion-progress-ring-label>72%</output>
298
+ </figure>
299
+ <span aria-hidden="true" data-motion-ambient-float></span>
300
+ <div data-motion-logo-belt><div data-motion-logo-belt-track>Two identical logo groups</div></div>
301
+ ```
302
+
303
+ Counters preserve their complete output for assistive technology. Progress
304
+ rings retain a text label. Infinite ambient and logo motion pauses off-screen
305
+ and becomes static under reduced motion.
306
+
307
+ ## FLIP and layout transitions
308
+
309
+ Use `data-motion-flip-card`, `data-motion-flip-card-trigger`, and
310
+ `data-motion-flip-card-detail` for a controlled card expansion. Filtered grids use
311
+ `data-motion-flip-filter`, `data-motion-filter-control`, and `data-motion-filter-item`.
312
+ Navigation indicators use `data-motion-flip-nav`, `data-motion-flip-nav-item`, and an
313
+ inert `data-motion-flip-nav-indicator`. Accordion grids use
314
+ `data-motion-layout-accordion-grid`, `data-motion-layout-item`, and native
315
+ `data-motion-layout-trigger` buttons.
316
+
317
+ Application-owned list reorder logic remains outside Motion. Dispatch a scoped
318
+ event after declaring the mutation callback:
319
+
320
+ ```js
321
+ list.dispatchEvent(new CustomEvent('motion:reorder', {
322
+ detail: { mutate(list, items) { list.append(...items.reverse()) } },
323
+ }))
324
+ ```
325
+
326
+ Synced Motion captures the prior geometry, runs the callback synchronously,
327
+ and animates spatial continuity. Reduced motion runs only the mutation.
328
+
329
+ ## SVG drawing, morphing, and motion paths
330
+
331
+ ```html
332
+ <figure data-motion-svg-line-draw><svg><path data-motion-svg-path d="..." /></svg></figure>
333
+ <figure data-motion-svg-morph>
334
+ <svg><path data-motion-svg-morph-source d="..." /><path data-motion-svg-morph-target d="..." hidden /></svg>
335
+ <button data-motion-svg-trigger type="button" aria-pressed="false">Change shape</button>
336
+ </figure>
337
+ <figure data-motion-svg-orbit><svg><path data-motion-svg-orbit-path d="..." /><circle data-motion-svg-orbit-subject /></svg></figure>
338
+ <figure data-motion-svg-signature><svg><path d="..." /></svg></figure>
339
+ ```
340
+
341
+ `data-motion-svg-icon-state` uses the equivalent `data-motion-svg-icon-source` and
342
+ `data-motion-svg-icon-target` slots. Authored paths require visible strokes for
343
+ drawing recipes, and morph source/target paths should have compatible intent.
344
+
345
+ ## Page-load and route motion
346
+
347
+ Page entrances use `data-motion-page-load-hero` with repeated
348
+ `data-motion-page-load-item` children, or `data-motion-page-load-brand` with an optional
349
+ `data-motion-brand-mark`.
350
+
351
+ Routers keep ownership of navigation and DOM replacement. A route fade listens
352
+ for `motion:route` on `data-motion-route-fade`; pass optional `outgoing`,
353
+ `incoming`, and `complete` values in `event.detail`. A shared-media root listens
354
+ for `motion:route-shared` and requires a synchronous `detail.mutate`
355
+ callback. `data-motion-route-scroll-restore` listens for
356
+ `motion:route-complete` with `detail.top`, then refreshes ScrollTrigger.
357
+
358
+ Every route listener is root-scoped and removed by runtime cleanup.
@@ -0,0 +1,82 @@
1
+ # Synced Motion capability blueprint
2
+
3
+ Synced Motion reproduces the published-page capabilities that make Webflow useful for animated marketing sites, while keeping layout and visual identity in whatever design system the project already uses.
4
+
5
+ It is not intended to recreate Webflow's hosted visual Designer, billing platform, hosting, or collaborative SaaS interface.
6
+
7
+ ## System ownership
8
+
9
+ | Concern | Owner |
10
+ |---|---|
11
+ | Fluid layout, typography, spacing, colors | The consuming design system (Synced Flow is one option) |
12
+ | Semantic application state | Consuming application |
13
+ | Timed animation and sequencing | GSAP through Synced Motion |
14
+ | Scroll progress, pinning, scrub and triggers | ScrollTrigger |
15
+ | Optional smooth scrolling | Lenis adapter |
16
+ | Editable content | Static data or a headless CMS |
17
+ | Localization | Application router or CMS |
18
+ | Testing and personalization | Dedicated analytics/experimentation service |
19
+
20
+ ## Webflow capability mapping
21
+
22
+ | Webflow capability | Synced implementation |
23
+ |---|---|
24
+ | Designer variables | Synced Flow semantic tokens |
25
+ | Global colors | OKLCH `--sf-colour-*` tokens |
26
+ | Responsive breakpoints | Fluid values, intrinsic layout and container queries |
27
+ | Components | Semantic templates or framework components |
28
+ | Interaction timeline | GSAP timelines and reusable patterns |
29
+ | Scroll reveals | `data-motion-reveal` and ScrollTrigger |
30
+ | Stagger sequences | `data-motion-stagger` |
31
+ | Scroll-linked motion | ScrollTrigger scrub patterns |
32
+ | Pinned storytelling | `data-motion-scroll-steps` and `data-motion-pin-target` |
33
+ | Hover-driven imagery | `data-motion-hover-group` |
34
+ | Menus and overlays | Accessible `data-motion-menu` controller |
35
+ | Smooth scrolling | Optional Lenis adapter |
36
+ | Interaction element IDs | Readable semantic `data-motion-*` hooks |
37
+ | CMS Collections | JSON, Markdown or a headless CMS |
38
+ | Collection pages | Reusable application templates |
39
+ | Conditional visibility | Component or template logic |
40
+ | Localize | Locale routing and CMS localization |
41
+ | Optimize | Separate experimentation integration |
42
+
43
+ ## Included first-release patterns
44
+
45
+ - entrance and viewport reveals;
46
+ - responsive SplitText line, word, and character reveals;
47
+ - staggered child reveals;
48
+ - scroll-linked parallax;
49
+ - scrubbed directional scroll exits for hero and editorial content;
50
+ - two-stage founder narratives with local background crossfades, word reveals, oversized metrics, and staggered detail groups;
51
+ - full-height editorial offer panels with scrubbed horizontal background drift;
52
+ - pinned mission statements that contract a standalone accent into a complete headline before revealing supporting metrics;
53
+ - pinned media expansions that grow a cropped image pill into a full-viewport feature while pushing adjacent labels outward;
54
+ - pinned multi-step narratives with semantic active states;
55
+ - hover/focus-driven media switching;
56
+ - pointer and keyboard-driven expanding panel groups;
57
+ - accessible animated navigation menus;
58
+ - optional Lenis integration with the GSAP ticker;
59
+ - automatic reduced-motion handling;
60
+ - cleanup for page transitions and hot reload;
61
+ - Synced Flow semantic color integration.
62
+
63
+ ## Production 1.0 expansion
64
+
65
+ - horizontal scroll galleries;
66
+ - clip-path and mask transitions;
67
+ - cursor-follow and magnetic interactions;
68
+ - SVG drawing and morphing recipes;
69
+ - FLIP-powered layout transitions;
70
+ - route transition adapters;
71
+ - framework adapters for React, Vue, Svelte, Astro, and WordPress;
72
+ - a sixty-recipe catalog, project scaffolding CLI, and local stdio MCP server;
73
+ - a local gallery, inspector diagnostics, and performance metadata;
74
+ - CMS and localization integration examples.
75
+
76
+ ## Design rule
77
+
78
+ Motion changes presentation, not meaning. Synced Motion toggles semantic states and animates transforms or opacity. It does not inject hardcoded brand colors, typography, or layout values. This ensures that changing a Synced Flow theme cannot be undermined by stale animation values.
79
+
80
+ ## Showcase promotion rule
81
+
82
+ The example is a consumer of the package. Any timeline, scroll controller, SplitText sequence, or interaction-state controller first demonstrated in the showcase must live in `src/patterns`, be initialized by `createSyncedMotion`, be documented in the attribute API, and be exported from the package entry point. The example may own Synced Flow layout, visual tokens, and CSS transitions that present semantic states; it may not contain direct GSAP, ScrollTrigger, SplitText, or Lenis orchestration.
@@ -0,0 +1,92 @@
1
+ # Framework and CMS integrations
2
+
3
+ All adapters mount the production built-in registry after their DOM root is
4
+ available, scope recipe selectors to that root, expose refresh access, and
5
+ destroy every timeline, trigger, listener, and temporary state on disposal.
6
+ They do not own component styling or layout.
7
+
8
+ ## React
9
+
10
+ Install the optional peers `react` and `@gsap/react`, then pass a component
11
+ root ref. The returned ref points to the active motion controller after mount.
12
+
13
+ ```jsx
14
+ import { useRef } from 'react'
15
+ import { useSyncedMotion } from '@syncedco/motion/react'
16
+
17
+ export function Page() {
18
+ const root = useRef(null)
19
+ const motion = useSyncedMotion(root)
20
+ return <main ref={root}>{/* semantic recipe markup */}</main>
21
+ }
22
+ ```
23
+
24
+ The hook uses `useGSAP()` with the supplied scope and reverts the controller on
25
+ unmount or watched dependency changes. Pass a third array argument to remount
26
+ after application state changes that replace recipe markup.
27
+
28
+ ## Vue 3
29
+
30
+ Install the optional `vue` peer and pass a template ref. The composable mounts
31
+ after `nextTick()` and destroys during `onUnmounted()`.
32
+
33
+ ```vue
34
+ <script setup>
35
+ import { ref } from 'vue'
36
+ import { useSyncedMotion } from '@syncedco/motion/vue'
37
+
38
+ const root = ref()
39
+ const motion = useSyncedMotion(root)
40
+ </script>
41
+
42
+ <template><main ref="root"><!-- semantic recipe markup --></main></template>
43
+ ```
44
+
45
+ ## Svelte
46
+
47
+ The Svelte entry is an action and does not import the Svelte runtime.
48
+
49
+ ```svelte
50
+ <script>
51
+ import { syncedMotion } from '@syncedco/motion/svelte'
52
+ </script>
53
+
54
+ <main use:syncedMotion>{/* semantic recipe markup */}</main>
55
+ ```
56
+
57
+ Updating the action options destroys the prior controller before mounting the
58
+ next one. Component destruction performs the final cleanup.
59
+
60
+ ## Astro
61
+
62
+ Call the adapter from a client script. It mounts at DOM ready, destroys before
63
+ an Astro view-transition swap, and remounts on `astro:page-load`.
64
+
65
+ ```js
66
+ import { createAstroMotion } from '@syncedco/motion/astro'
67
+
68
+ const motion = createAstroMotion({ root: '#page' })
69
+ ```
70
+
71
+ ## WordPress
72
+
73
+ The WordPress adapter requires no jQuery and no WordPress JavaScript global.
74
+ It mounts at DOM ready and supports block or navigation replacements through
75
+ semantic document events.
76
+
77
+ ```js
78
+ import { createWordPressMotion } from '@syncedco/motion/wordpress'
79
+
80
+ const motion = createWordPressMotion({ root: '#page' })
81
+
82
+ // After replacing a dynamic block or page fragment:
83
+ document.dispatchEvent(new CustomEvent('motion:mount', {
84
+ detail: { root: document.querySelector('#updated-region') },
85
+ }))
86
+
87
+ // After layout-only changes:
88
+ document.dispatchEvent(new Event('motion:refresh'))
89
+ ```
90
+
91
+ Calling `motion.destroy()` removes these event listeners and destroys the
92
+ active scoped runtime.
@@ -0,0 +1,150 @@
1
+ # Bundle size and performance
2
+
3
+ Animation code runs on every page view, so what you ship matters. This page
4
+ explains what each entry point costs, how to ship less, and how the numbers are
5
+ kept honest.
6
+
7
+ ## Measured cost
8
+
9
+ | Scenario | gzip | GSAP plugins pulled in |
10
+ | --- | --- | --- |
11
+ | `createSyncedMotion()` — all sixty recipes | 16.9 kB | ScrollTrigger |
12
+ | `@syncedco/motion/full` — all sixty plus every plugin | 17.0 kB | ScrollTrigger, SplitText, Flip, DrawSVG, MorphSVG, MotionPath |
13
+ | Three individually imported recipes | 5.8 kB | ScrollTrigger |
14
+
15
+ GSAP itself is a peer dependency and is excluded from these figures — you
16
+ already ship it. The plugin column matters: those modules come out of the
17
+ `gsap` package and land in your bundle, and they are considerably larger than
18
+ this package.
19
+
20
+ Run the numbers yourself:
21
+
22
+ ```bash
23
+ npm run size:check
24
+ ```
25
+
26
+ ## Entry points
27
+
28
+ | Import | Use it when |
29
+ | --- | --- |
30
+ | `@syncedco/motion` | The default. Every recipe, GSAP and ScrollTrigger only. |
31
+ | `@syncedco/motion/full` | You want the seventeen plugin-backed recipes and don't want to think about it. |
32
+ | `@syncedco/motion/recipes` | You want to ship only the recipes this page uses. |
33
+ | `@syncedco/motion/plugins` | You want the plugin set to pass through `dependencies`. |
34
+
35
+ ## Shipping only what you use
36
+
37
+ Every recipe is an individually importable, side-effect-free export. Import the
38
+ ones you need and build a registry from them:
39
+
40
+ ```js
41
+ import { createMotionRuntime, createMotionRegistry } from '@syncedco/motion'
42
+ import { revealRise, revealStaggerCascade, marquee } from '@syncedco/motion/recipes'
43
+
44
+ const motion = createMotionRuntime({
45
+ registry: createMotionRegistry(
46
+ [revealRise, revealStaggerCascade, marquee],
47
+ { mode: 'runtime' },
48
+ ),
49
+ })
50
+ ```
51
+
52
+ `mode: 'runtime'` tells the registry to accept lean specs. See
53
+ [the recipe system](RECIPES.md#runtime-specs-and-authoring-metadata) for why
54
+ those are separate.
55
+
56
+ Recipe export names are the camelCase form of the recipe ID: `reveal-rise`
57
+ becomes `revealRise`, `pinned-steps` becomes `pinnedSteps`. The full list is in
58
+ [the recipe reference](RECIPE-REFERENCE.md).
59
+
60
+ ## Optional GSAP plugins
61
+
62
+ Forty-three of the sixty recipes need nothing beyond GSAP and ScrollTrigger.
63
+ The other seventeen need one of SplitText, Flip, DrawSVG, MorphSVG or
64
+ MotionPath. Importing all five in the core would put roughly sixty kilobytes of
65
+ GSAP into every consumer's bundle to serve a minority of recipes, so the core
66
+ does not import them.
67
+
68
+ If a page contains markup for a recipe whose plugin is missing, that recipe is
69
+ skipped and says why:
70
+
71
+ ```js
72
+ motion.inspect().skipped
73
+ // [{
74
+ // id: 'split-lines-rise',
75
+ // reason: 'missing-dependency',
76
+ // dependencies: ['SplitText'],
77
+ // fix: 'Pass { dependencies: { SplitText } } or import from "@syncedco/motion/full".'
78
+ // }]
79
+ ```
80
+
81
+ Nothing is hidden and no content disappears — the markup stays in its authored
82
+ state, which is the same thing that happens when JavaScript fails entirely.
83
+
84
+ Two ways to resolve it:
85
+
86
+ ```js
87
+ // Everything, one import.
88
+ import { createSyncedMotion } from '@syncedco/motion/full'
89
+
90
+ // Or only the plugin you actually need.
91
+ import { SplitText } from 'gsap/SplitText'
92
+ createSyncedMotion({ dependencies: { SplitText } })
93
+ ```
94
+
95
+ The diagnostic is reported per matched root, so you only hear about a missing
96
+ plugin when the page really uses that recipe.
97
+
98
+ Which recipes need what:
99
+
100
+ | Plugin | Recipes |
101
+ | --- | --- |
102
+ | SplitText | 5 |
103
+ | Flip | 6 |
104
+ | DrawSVGPlugin | 3 |
105
+ | MorphSVGPlugin | 2 |
106
+ | MotionPathPlugin | 1 |
107
+
108
+ [The recipe reference](RECIPE-REFERENCE.md) flags each one.
109
+
110
+ ## Runtime cost
111
+
112
+ - **Transforms and opacity first.** The compiler rejects declarative recipes
113
+ that animate `width`, `height`, `top`, `left`, `margin` or `padding` unless
114
+ they set `performance.allowLayout`.
115
+ - **Every recipe declares a performance class** of `low`, `medium` or `high`.
116
+ `synced-motion compose` warns when a plan contains more than two high-cost
117
+ recipes.
118
+ - **Mounting is scoped.** Each recipe queries within its own root; nothing
119
+ scans the document repeatedly at runtime.
120
+ - **Off-screen loops pause.** Marquees and belts observe intersection and stop
121
+ when they are not visible.
122
+ - **Reduced motion is resolved through `gsap.matchMedia()`**, so switching the
123
+ preference reverts and remounts cleanly instead of leaving orphaned tweens.
124
+
125
+ Inspect what mounted, what was skipped and what failed at any time:
126
+
127
+ ```js
128
+ motion.inspect()
129
+ // { active, reduced, registered, mounted, skipped, errors }
130
+ ```
131
+
132
+ ## Budgets
133
+
134
+ `scripts/check-size.mjs` links the built package into a throwaway
135
+ `node_modules`, bundles each scenario with esbuild, and fails if a gzip budget
136
+ regresses. It measures the published package rather than `./src` deliberately:
137
+ bundlers only honour the `sideEffects` field for packages inside
138
+ `node_modules`, so measuring source would overstate how well tree-shaking
139
+ works.
140
+
141
+ Budgets are part of the contract. Raise them deliberately, in a commit that
142
+ says why.
143
+
144
+ ## Keeping recipes tree-shakeable
145
+
146
+ Recipe specs are annotated `/* @__PURE__ */` so a bundler may drop the ones you
147
+ never reference. Both the spec call and the nested `setupFactory(...)` call need
148
+ the annotation — without it on the inner call, esbuild cannot prove the
149
+ initialiser is side-effect free and retains all sixty. `npm run size:check`
150
+ catches this.