@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.
- package/CHANGELOG.md +106 -0
- package/CODE_OF_CONDUCT.md +26 -0
- package/CONTRIBUTING.md +100 -0
- package/LICENSE +22 -0
- package/README.md +278 -0
- package/SECURITY.md +36 -0
- package/SUPPORT.md +38 -0
- package/TRADEMARKS.md +14 -0
- package/bin/synced-motion.mjs +5 -0
- package/dist/astro.d.ts +15 -0
- package/dist/astro.js +51 -0
- package/dist/astro.js.map +1 -0
- package/dist/chunk-24KKECMK.js +281 -0
- package/dist/chunk-24KKECMK.js.map +1 -0
- package/dist/chunk-3WUVBPYX.js +162 -0
- package/dist/chunk-3WUVBPYX.js.map +1 -0
- package/dist/chunk-HCWQV65E.js +20 -0
- package/dist/chunk-HCWQV65E.js.map +1 -0
- package/dist/chunk-JET2MYEG.js +2287 -0
- package/dist/chunk-JET2MYEG.js.map +1 -0
- package/dist/chunk-KA3OPI4F.js +40 -0
- package/dist/chunk-KA3OPI4F.js.map +1 -0
- package/dist/chunk-ML7HTCZT.js +642 -0
- package/dist/chunk-ML7HTCZT.js.map +1 -0
- package/dist/chunk-NIXAEIHN.js +23 -0
- package/dist/chunk-NIXAEIHN.js.map +1 -0
- package/dist/chunk-VOIQPPQZ.js +299 -0
- package/dist/chunk-VOIQPPQZ.js.map +1 -0
- package/dist/chunk-WCG7TQSH.js +31 -0
- package/dist/chunk-WCG7TQSH.js.map +1 -0
- package/dist/cli.js +382 -0
- package/dist/cli.js.map +1 -0
- package/dist/full.d.ts +13 -0
- package/dist/full.js +201 -0
- package/dist/full.js.map +1 -0
- package/dist/index.d.ts +249 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/dist/lenis.d.ts +19 -0
- package/dist/lenis.js +7 -0
- package/dist/lenis.js.map +1 -0
- package/dist/mcp.d.ts +16 -0
- package/dist/mcp.js +82 -0
- package/dist/mcp.js.map +1 -0
- package/dist/plugins.d.ts +14 -0
- package/dist/plugins.js +17 -0
- package/dist/plugins.js.map +1 -0
- package/dist/react.d.ts +7 -0
- package/dist/react.js +32 -0
- package/dist/react.js.map +1 -0
- package/dist/recipes.d.ts +76 -0
- package/dist/recipes.js +129 -0
- package/dist/recipes.js.map +1 -0
- package/dist/styles.css +57 -0
- package/dist/styles.css.map +1 -0
- package/dist/svelte.d.ts +10 -0
- package/dist/svelte.js +30 -0
- package/dist/svelte.js.map +1 -0
- package/dist/vue.d.ts +13 -0
- package/dist/vue.js +35 -0
- package/dist/vue.js.map +1 -0
- package/dist/wordpress.d.ts +15 -0
- package/dist/wordpress.js +49 -0
- package/dist/wordpress.js.map +1 -0
- package/docs/API.md +176 -0
- package/docs/ATTRIBUTE-API.md +358 -0
- package/docs/CAPABILITIES.md +82 -0
- package/docs/INTEGRATIONS.md +92 -0
- package/docs/PERFORMANCE.md +150 -0
- package/docs/README.md +35 -0
- package/docs/RECIPE-REFERENCE.md +1358 -0
- package/docs/RECIPES.md +115 -0
- package/docs/TOOLING.md +65 -0
- package/docs/WEBFLOW-MIGRATION.md +39 -0
- 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.
|