@mehshekari/gsap-motion 3.1.1 → 3.3.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/README.md +1 -1
- package/bin/gsap-motion.mjs +17 -0
- package/package.json +1 -1
- package/skill/SKILL.md +50 -43
- package/skill/adapter/astro.md +111 -0
- package/skill/adapter/next.md +99 -0
- package/skill/adapter/r3f.md +128 -0
- package/skill/adapter/react.md +133 -0
- package/skill/adapter/svelte.md +105 -0
- package/skill/adapter/three.md +141 -0
- package/skill/adapter/vanilla.md +115 -0
- package/skill/adapter/vue.md +120 -0
- package/skill/example/svg-loader.md +1 -1
- package/skill/reference/core-gsap.md +1 -1
- package/skill/reference/devices.md +110 -0
- package/skill/reference/interaction.md +183 -183
- package/skill/reference/motion-design.md +8 -5
- package/skill/reference/project-rules.md +16 -0
- package/skill/reference/refine.md +84 -0
- package/skill/reference/routing.md +136 -119
- package/skill/reference/scrolltrigger.md +1 -1
- package/skill/reference/svg.md +1 -1
- package/skill/reference/timeline.md +41 -1
- package/skill/scripts/lib/ast.mjs +7 -1
- package/skill/scripts/lib/rules.mjs +418 -13
- package/skill/scripts/lib/source.mjs +51 -3
- package/skill/reference/frameworks.md +0 -153
- package/skill/reference/react-nextjs.md +0 -234
- package/skill/reference/three-r3f.md +0 -161
|
@@ -1,119 +1,136 @@
|
|
|
1
|
-
# Routing
|
|
2
|
-
|
|
3
|
-
What to load, and what to do when the request is a feeling rather than a
|
|
4
|
-
command.
|
|
5
|
-
|
|
6
|
-
## No argument
|
|
7
|
-
|
|
8
|
-
Do not guess a target and do not invent an animation. Ask what should move, and
|
|
9
|
-
offer the menu below — grouped, so the question is easy to answer.
|
|
10
|
-
|
|
11
|
-
> What are we animating?
|
|
12
|
-
>
|
|
13
|
-
> - **Entrance** — something appearing: a hero, a section, a list, an image
|
|
14
|
-
> - **Scroll** — something driven by scroll position: a sequence, a pin, a
|
|
15
|
-
> horizontal run
|
|
16
|
-
> - **Interaction** — something answering the pointer: a button, a cursor, a
|
|
17
|
-
> card
|
|
18
|
-
> - **Continuous** — something always moving: a marquee, an orbit, ambient drift
|
|
19
|
-
> - **Transition** — moving between two states: a route, a modal, a layout
|
|
20
|
-
> - **Loading** — a loader, a progress indicator, a site intro
|
|
21
|
-
> - **Existing** — something already animated that feels wrong, costs too much,
|
|
22
|
-
> or should go
|
|
23
|
-
|
|
24
|
-
## Classifying a description
|
|
25
|
-
|
|
26
|
-
Most requests are a feeling, not a command. Classify, then route.
|
|
27
|
-
|
|
28
|
-
| What they say | What they mean | Route to |
|
|
29
|
-
|---|---|---|
|
|
30
|
-
| "make it cinematic" | slow, weighted, sequenced, one thing at a time | `intro` · [cinematic](../preset/cinematic.md) |
|
|
31
|
-
| "make it feel premium" | fewer moving parts, longer eases, no bounce | `tune` · [motion-design](motion-design.md) |
|
|
32
|
-
| "make it feel alive" | ambient continuous motion under static content | `ambient` · [marquee](../preset/marquee.md) |
|
|
33
|
-
| "make it pop / bolder" | more contrast in timing, not more elements moving | `tune` · [motion-design](motion-design.md) |
|
|
34
|
-
| "like Apple" | scroll-scrubbed, pinned, one idea per screen | `scroll` · [scroll-storytelling](../preset/scroll-storytelling.md) |
|
|
35
|
-
| "futuristic / technical" | scramble, draw-on, mechanical easing, grid reveals | `text` + `svg` |
|
|
36
|
-
| "liquid / organic" | morph, path motion, overlapping soft easing | `svg` · [loader](../preset/loader.md) |
|
|
37
|
-
| "something crazy" | layered techniques with one clear concept | see below |
|
|
38
|
-
| "smooth image reveal" | clip-path or mask, not opacity | [reveal](../preset/reveal.md) |
|
|
39
|
-
| "the hero is boring" | hierarchy problem before a motion problem | `tune`, then `reveal` |
|
|
40
|
-
| "it feels janky" | cost or trigger problem | `audit` · [performance](performance.md) |
|
|
41
|
-
|
|
42
|
-
### "Something crazy"
|
|
43
|
-
|
|
44
|
-
A fade-and-scale is a failure of nerve; twelve techniques at once is a failure
|
|
45
|
-
of taste. Pick **one concept** and let three techniques serve it. The concept is
|
|
46
|
-
the thing you can say in a sentence — "the logo writes itself, then the dot that
|
|
47
|
-
drew it runs off and writes the word" — and every technique either serves that
|
|
48
|
-
sentence or is cut.
|
|
49
|
-
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1
|
+
# Routing
|
|
2
|
+
|
|
3
|
+
What to load, and what to do when the request is a feeling rather than a
|
|
4
|
+
command.
|
|
5
|
+
|
|
6
|
+
## No argument
|
|
7
|
+
|
|
8
|
+
Do not guess a target and do not invent an animation. Ask what should move, and
|
|
9
|
+
offer the menu below — grouped, so the question is easy to answer.
|
|
10
|
+
|
|
11
|
+
> What are we animating?
|
|
12
|
+
>
|
|
13
|
+
> - **Entrance** — something appearing: a hero, a section, a list, an image
|
|
14
|
+
> - **Scroll** — something driven by scroll position: a sequence, a pin, a
|
|
15
|
+
> horizontal run
|
|
16
|
+
> - **Interaction** — something answering the pointer: a button, a cursor, a
|
|
17
|
+
> card
|
|
18
|
+
> - **Continuous** — something always moving: a marquee, an orbit, ambient drift
|
|
19
|
+
> - **Transition** — moving between two states: a route, a modal, a layout
|
|
20
|
+
> - **Loading** — a loader, a progress indicator, a site intro
|
|
21
|
+
> - **Existing** — something already animated that feels wrong, costs too much,
|
|
22
|
+
> or should go
|
|
23
|
+
|
|
24
|
+
## Classifying a description
|
|
25
|
+
|
|
26
|
+
Most requests are a feeling, not a command. Classify, then route.
|
|
27
|
+
|
|
28
|
+
| What they say | What they mean | Route to |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| "make it cinematic" | slow, weighted, sequenced, one thing at a time | `intro` · [cinematic](../preset/cinematic.md) |
|
|
31
|
+
| "make it feel premium" | fewer moving parts, longer eases, no bounce | `tune` · [motion-design](motion-design.md) |
|
|
32
|
+
| "make it feel alive" | ambient continuous motion under static content | `ambient` · [marquee](../preset/marquee.md) |
|
|
33
|
+
| "make it pop / bolder" | more contrast in timing, not more elements moving | `tune` · [motion-design](motion-design.md) |
|
|
34
|
+
| "like Apple" | scroll-scrubbed, pinned, one idea per screen | `scroll` · [scroll-storytelling](../preset/scroll-storytelling.md) |
|
|
35
|
+
| "futuristic / technical" | scramble, draw-on, mechanical easing, grid reveals | `text` + `svg` |
|
|
36
|
+
| "liquid / organic" | morph, path motion, overlapping soft easing | `svg` · [loader](../preset/loader.md) |
|
|
37
|
+
| "something crazy" | layered techniques with one clear concept | see below |
|
|
38
|
+
| "smooth image reveal" | clip-path or mask, not opacity | [reveal](../preset/reveal.md) |
|
|
39
|
+
| "the hero is boring" | hierarchy problem before a motion problem | `tune`, then `reveal` |
|
|
40
|
+
| "it feels janky" | cost or trigger problem | `audit` · [performance](performance.md) |
|
|
41
|
+
|
|
42
|
+
### "Something crazy"
|
|
43
|
+
|
|
44
|
+
A fade-and-scale is a failure of nerve; twelve techniques at once is a failure
|
|
45
|
+
of taste. Pick **one concept** and let three techniques serve it. The concept is
|
|
46
|
+
the thing you can say in a sentence — "the logo writes itself, then the dot that
|
|
47
|
+
drew it runs off and writes the word" — and every technique either serves that
|
|
48
|
+
sentence or is cut.
|
|
49
|
+
|
|
50
|
+
## The technique ladder
|
|
51
|
+
|
|
52
|
+
Start at the bottom, and each rung up says why the one below is not enough:
|
|
53
|
+
|
|
54
|
+
1. **A CSS transition** — one property, one state change, no sequencing.
|
|
55
|
+
2. **One transform tween** — it needs an ease or a duration CSS cannot say.
|
|
56
|
+
3. **A timeline** — two or more things must be ordered or overlapped.
|
|
57
|
+
4. **A plugin** — the shape needs it: draw, morph, path, split. Its weight ships
|
|
58
|
+
to every visitor.
|
|
59
|
+
5. **A scroll takeover** — the scroll position *is* the story. It takes the
|
|
60
|
+
visitor's control of the page, so it needs the strongest reason of all.
|
|
61
|
+
|
|
62
|
+
Say the rung in Analysis: "a transition would not sequence these, so this is a
|
|
63
|
+
timeline". A plugin named without that sentence is one nobody justified.
|
|
64
|
+
|
|
65
|
+
## Load map
|
|
66
|
+
|
|
67
|
+
The project's own rules come first, then the size tiers in SKILL.md, setup step
|
|
68
|
+
3: a one-element change loads [motion-design](motion-design.md) and the
|
|
69
|
+
command's own reference only. The table applies from component size up — a
|
|
70
|
+
single hover takes [interaction](interaction.md) and nothing else from its row.
|
|
71
|
+
|
|
72
|
+
| Request | Add |
|
|
73
|
+
|---|---|
|
|
74
|
+
| SVG draw / morph / path | [svg](svg.md), [timeline](timeline.md) |
|
|
75
|
+
| Scroll sequence, pin, scrub | [scrolltrigger](scrolltrigger.md), [timeline](timeline.md), [performance](performance.md) |
|
|
76
|
+
| Horizontal scroll | [scrolltrigger](scrolltrigger.md), [scroll-storytelling](../preset/scroll-storytelling.md) |
|
|
77
|
+
| Text reveal, kinetic type | [text](text.md), [timeline](timeline.md) |
|
|
78
|
+
| Magnetic, cursor, hover | [interaction](interaction.md), [performance](performance.md) |
|
|
79
|
+
| Drag, flick, inertia | [interaction](interaction.md) |
|
|
80
|
+
| Layout / shared element / modal | [flip](flip.md) |
|
|
81
|
+
| Page or route transition | [flip](flip.md), [page-transition](../preset/page-transition.md), this project's adapter |
|
|
82
|
+
| Loader, splash, intro | [svg](svg.md), [timeline](timeline.md), [accessibility](accessibility.md) |
|
|
83
|
+
| Marquee, orbit, ambient | [timeline](timeline.md), [performance](performance.md) |
|
|
84
|
+
| Three.js / R3F | [three](../adapter/three.md) or [r3f](../adapter/r3f.md), [performance](performance.md) |
|
|
85
|
+
| Audit an existing animation | [performance](performance.md), [accessibility](accessibility.md), this project's adapter |
|
|
86
|
+
| Retime / re-ease | [motion-design](motion-design.md) only |
|
|
87
|
+
| Hover, touch or reduced-motion branching | [devices](devices.md) |
|
|
88
|
+
| Fixing what an audit found | [refine](refine.md) |
|
|
89
|
+
|
|
90
|
+
Do not load a domain file "for completeness". Loading `adapter/three.md` for a
|
|
91
|
+
button hover is the failure this map exists to prevent.
|
|
92
|
+
|
|
93
|
+
## `audit`
|
|
94
|
+
|
|
95
|
+
**Run the checker first.** It is deterministic, it costs a second, and it finds
|
|
96
|
+
the mechanical failures before you spend attention on them:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
node .claude/skills/gsap-creative-animation/scripts/audit-gsap.mjs <path>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**When an animation is not visible, ask whether it was ever built.** Before
|
|
103
|
+
touching timings or transforms, check that the code which creates it actually
|
|
104
|
+
runs. Animation is routinely built behind a condition — an image decode race, a
|
|
105
|
+
`ready.then()`, a ScrollTrigger that has not fired, a `matchMedia` branch that
|
|
106
|
+
does not match, an early `return` on a missing ref — and a tween that was never
|
|
107
|
+
created looks exactly like one that is mis-timed. A typical case: an image wipe
|
|
108
|
+
added only once the image decodes inside a short budget. On a first visit the
|
|
109
|
+
image is never cached, the branch never runs, and every hypothesis about the
|
|
110
|
+
wipe's geometry is wasted effort.
|
|
111
|
+
|
|
112
|
+
Then read for what it cannot see. Check in this order — the first two are where
|
|
113
|
+
real bugs live:
|
|
114
|
+
|
|
115
|
+
1. **Lifecycle** — is every animation inside a `useGSAP`/`gsap.context` scope?
|
|
116
|
+
Are ScrollTriggers killed? Are listeners removed? Does a `matchMedia` get
|
|
117
|
+
reverted? See this project's adapter.
|
|
118
|
+
2. **Cost** — layout-triggering properties, per-frame React state, filters on
|
|
119
|
+
large areas, one ScrollTrigger per item where one would do. See
|
|
120
|
+
[performance](performance.md).
|
|
121
|
+
3. **Reduced motion** — is there a branch, and does it still communicate?
|
|
122
|
+
4. **Craft** — rhythm, easing on loops, contrast. See [motion-design](motion-design.md).
|
|
123
|
+
|
|
124
|
+
Report findings most-severe first, with the file and line. Do not rewrite
|
|
125
|
+
unless asked.
|
|
126
|
+
|
|
127
|
+
## `strip`
|
|
128
|
+
|
|
129
|
+
Removing motion is a legitimate outcome and often the right one. Cut an
|
|
130
|
+
animation when it answers none of hierarchy, causality, continuity, state, or
|
|
131
|
+
personality; when it delays content the visitor came for; when it repeats on
|
|
132
|
+
every route and stopped being noticed on the second one; or when it exists
|
|
133
|
+
because a plugin was available.
|
|
134
|
+
|
|
135
|
+
Say what you removed and what it was costing. Do not replace it with a smaller
|
|
136
|
+
animation unless the user asks.
|
|
@@ -176,7 +176,7 @@ useGSAP(() => {
|
|
|
176
176
|
|
|
177
177
|
After a route change in the App Router, the new page's layout is measured before
|
|
178
178
|
images settle. `ScrollTrigger.refresh()` on mount is usually needed. See
|
|
179
|
-
[react
|
|
179
|
+
[react.md](../adapter/react.md).
|
|
180
180
|
|
|
181
181
|
## Failures
|
|
182
182
|
|
package/skill/reference/svg.md
CHANGED
|
@@ -24,7 +24,7 @@ as JSX.
|
|
|
24
24
|
|
|
25
25
|
Without them you end up at `querySelectorAll("path")[3]`, which breaks on the
|
|
26
26
|
next export. Prefix them, and namespace per instance — see
|
|
27
|
-
[react
|
|
27
|
+
[react.md](../adapter/react.md). For a set animated together, one
|
|
28
28
|
`data-*` attribute beats a run of ids:
|
|
29
29
|
|
|
30
30
|
```tsx
|
|
@@ -4,6 +4,46 @@ A timeline is how choreography stays editable. Five tweens with hand-tuned
|
|
|
4
4
|
delays encode the same animation in a form nobody can retime without redoing
|
|
5
5
|
the arithmetic.
|
|
6
6
|
|
|
7
|
+
## Scene and beats
|
|
8
|
+
|
|
9
|
+
A scene is a set of named beats, and each beat is a label in the code. Writing
|
|
10
|
+
the beats down before the tweens is what stops a timeline becoming a pile of
|
|
11
|
+
numbers nobody can change.
|
|
12
|
+
|
|
13
|
+
Most scenes are four beats or fewer:
|
|
14
|
+
|
|
15
|
+
| Beat | Says |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Entrance | this arrived |
|
|
18
|
+
| Emphasis | this is the one that matters — **at most one per scene** |
|
|
19
|
+
| Pause | read this before the next thing moves |
|
|
20
|
+
| Exit | this is finished, or making room |
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const tl = gsap.timeline();
|
|
24
|
+
|
|
25
|
+
tl.addLabel("enter")
|
|
26
|
+
.from("[data-heading]", { yPercent: 110, duration: 0.8 })
|
|
27
|
+
.from("[data-lead]", { autoAlpha: 0, y: 18, duration: 0.6 }, "-=0.5")
|
|
28
|
+
.addLabel("emphasis")
|
|
29
|
+
.add(mark()) // a beat of its own, built elsewhere
|
|
30
|
+
.addLabel("rest");
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Two rules follow from it:
|
|
34
|
+
|
|
35
|
+
- **A beat that is more than one tween is a function returning its own
|
|
36
|
+
timeline**, added with `.add()`. The scene then reads as its beats, and the
|
|
37
|
+
beat can be retimed, reordered or dropped without touching the rest.
|
|
38
|
+
- **An ambient loop is handed off after the scene, never added inside it.** An
|
|
39
|
+
infinite child makes the parent infinite, so the scene's own `onComplete`
|
|
40
|
+
never runs and anything waiting on it waits forever — the audit reports that
|
|
41
|
+
as `never-completes`. Start the loop from a `.call()` at the end, or from the
|
|
42
|
+
scene's `onComplete`.
|
|
43
|
+
|
|
44
|
+
The numbered beats in an answer's Motion strategy and the labels in its code are
|
|
45
|
+
the same list. If they have drifted apart, the code is not what was designed.
|
|
46
|
+
|
|
7
47
|
## Position
|
|
8
48
|
|
|
9
49
|
The third argument places a child on the timeline. This is the whole API.
|
|
@@ -103,7 +143,7 @@ onMouseLeave = () => hover.reverse();
|
|
|
103
143
|
|
|
104
144
|
`reverse()` uses the same easing backwards, so the return matches the arrival
|
|
105
145
|
without a second definition. In React, anything built outside the `useGSAP`
|
|
106
|
-
body must go through `contextSafe` — see [react
|
|
146
|
+
body must go through `contextSafe` — see [react.md](../adapter/react.md).
|
|
107
147
|
|
|
108
148
|
## Holds
|
|
109
149
|
|
|
@@ -21,10 +21,16 @@ const OPTIONS = {
|
|
|
21
21
|
* `.ts` is read without JSX, because there `<T>(x) => x` is a generic and
|
|
22
22
|
* `<Type>value` a cast; every other extension is read with it. A `.js` file
|
|
23
23
|
* with JSX is common enough, and JSX never changes how plain JavaScript parses.
|
|
24
|
+
*
|
|
25
|
+
* A single-file component's `<script>` is read without JSX too: it is
|
|
26
|
+
* TypeScript as often as not, and the template where the angle brackets live is
|
|
27
|
+
* blanked out before it reaches here.
|
|
24
28
|
*/
|
|
25
29
|
const TYPESCRIPT = Parser.extend(tsPlugin());
|
|
26
30
|
const TYPESCRIPT_JSX = Parser.extend(tsPlugin({ jsx: true }));
|
|
27
31
|
|
|
32
|
+
const WITHOUT_JSX = /\.(?:[mc]?ts|vue|svelte|astro)$/;
|
|
33
|
+
|
|
28
34
|
/**
|
|
29
35
|
* Parses `source` as the grammar its `path` implies.
|
|
30
36
|
*
|
|
@@ -34,7 +40,7 @@ const TYPESCRIPT_JSX = Parser.extend(tsPlugin({ jsx: true }));
|
|
|
34
40
|
*/
|
|
35
41
|
export function parse(source, path) {
|
|
36
42
|
const comments = [];
|
|
37
|
-
const parser =
|
|
43
|
+
const parser = WITHOUT_JSX.test(path) ? TYPESCRIPT : TYPESCRIPT_JSX;
|
|
38
44
|
const ast = parser.parse(source, { ...OPTIONS, onComment: comments });
|
|
39
45
|
return { ast, comments };
|
|
40
46
|
}
|