@mehshekari/gsap-motion 3.2.0 → 3.4.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 +3 -1
- package/bin/gsap-motion.mjs +21 -0
- package/package.json +1 -1
- package/skill/SKILL.md +44 -37
- package/skill/reference/devices.md +110 -0
- package/skill/reference/motion-design.md +8 -5
- package/skill/reference/project-rules.md +16 -0
- package/skill/reference/refine.md +112 -0
- package/skill/reference/routing.md +29 -0
- package/skill/reference/timeline.md +40 -0
- package/skill/scripts/capture-motion.mjs +519 -0
- package/skill/scripts/explain-motion.mjs +575 -0
- package/skill/scripts/lib/rules.mjs +321 -10
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ No dependencies, no install scripts, Node.js 18 or later.
|
|
|
20
20
|
| `remove` | Uninstalls it. Takes the same `--global` and `--dir`. It only ever removes a directory that holds this skill. |
|
|
21
21
|
| `audit [path...]` | Scans for leaks, per-frame cost, eased loops, shared plugin ids, unregistered plugins and shipped dev tooling. Default path `src`. `--quiet` for errors only, `--json` for scripts. |
|
|
22
22
|
| `audit-svg <file...>` | Reports what DrawSVG can draw, what MorphSVG can morph, and what will fail silently. `--morph a.svg b.svg` compares a pair; `--hues` also reports hard-coded hues. |
|
|
23
|
+
| `inspect <url>` | Watches a page animate over the DevTools Protocol and reports what it saw: a filmstrip, the timeline GSAP is running, and the layouts, style recalculations and frames the browser did. `--scroll`, `--hover` and `--click` reach the state first; `--reduced`, `--dark`, `--mobile` and `--cpu` change the conditions. Needs a Chrome on the machine; installs nothing. |
|
|
24
|
+
| `explain <file...|url>` | Maps an animation and changes nothing: where it lives, its preference branches, its beats in order with the reasons their comments give, its scroll configuration, and what the audit says. Given a URL it reads the running page instead, where the timeline is the one GSAP actually built. |
|
|
23
25
|
| `doctor` | Checks Node.js, the installed skill's version for this project and your user, your project's `gsap` and `@gsap/react`, and whether an `ANIMATION.md` exists. |
|
|
24
26
|
|
|
25
27
|
## In CI
|
|
@@ -28,7 +30,7 @@ The audit needs no install. Pin the version, so a new rule cannot fail your
|
|
|
28
30
|
build without warning:
|
|
29
31
|
|
|
30
32
|
```json
|
|
31
|
-
"lint": "eslint && npx @mehshekari/gsap-motion@3.
|
|
33
|
+
"lint": "eslint && npx @mehshekari/gsap-motion@3.4.0 audit src --quiet"
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
Or install it with `npm install --save-dev --save-exact @mehshekari/gsap-motion`.
|
package/bin/gsap-motion.mjs
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* npx @mehshekari/gsap-motion remove [--global | --dir <skills-dir>]
|
|
8
8
|
* npx @mehshekari/gsap-motion audit [path...] [--quiet] [--json]
|
|
9
9
|
* npx @mehshekari/gsap-motion audit-svg <file.svg...> [--morph] [--hues]
|
|
10
|
+
* npx @mehshekari/gsap-motion inspect <url> [--at ...] [--hover ...] [--reduced]
|
|
11
|
+
* npx @mehshekari/gsap-motion explain <file...|url>
|
|
10
12
|
* npx @mehshekari/gsap-motion doctor
|
|
11
13
|
*
|
|
12
14
|
* Installed as a dependency, the same commands run as `gsap-motion <command>`.
|
|
@@ -69,6 +71,19 @@ Commands
|
|
|
69
71
|
audit-svg <file...> What an SVG can do before you animate it
|
|
70
72
|
--morph <a> <b> Compare a morph pair
|
|
71
73
|
--hues Also report hard-coded hues
|
|
74
|
+
inspect <url> Watch a page animate and report what it did
|
|
75
|
+
--at 0,300,900 When to capture, in ms after the page settles
|
|
76
|
+
--scroll <px|sel> Scroll before capturing
|
|
77
|
+
--hover <selector> Move the pointer onto an element
|
|
78
|
+
--click <selector> Click an element
|
|
79
|
+
--reduced --dark Emulate a preference
|
|
80
|
+
--mobile 390x844 with touch input
|
|
81
|
+
--cpu 4 Throttle the CPU by this factor
|
|
82
|
+
--out <dir> Where frames go. Default .gsap-motion/inspect
|
|
83
|
+
--json Machine-readable output
|
|
84
|
+
explain <file...> Map an animation: scopes, branches, beats, triggers
|
|
85
|
+
Give it a URL instead to read the running page
|
|
86
|
+
--json Machine-readable output
|
|
72
87
|
doctor Check Node, the installed skill and your project's GSAP
|
|
73
88
|
--version Print the version
|
|
74
89
|
|
|
@@ -301,6 +316,12 @@ switch (command) {
|
|
|
301
316
|
case "audit-svg":
|
|
302
317
|
runScript("audit-svg.mjs", args);
|
|
303
318
|
break;
|
|
319
|
+
case "inspect":
|
|
320
|
+
runScript("capture-motion.mjs", args);
|
|
321
|
+
break;
|
|
322
|
+
case "explain":
|
|
323
|
+
runScript("explain-motion.mjs", args);
|
|
324
|
+
break;
|
|
304
325
|
case "doctor":
|
|
305
326
|
doctor();
|
|
306
327
|
break;
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Senior motion engineer for GSAP. Turns a visual or interaction idea
|
|
|
4
4
|
license: MIT. See LICENSE; GSAP itself is licensed separately, see NOTICE.md
|
|
5
5
|
compatibility: Any Agent Skills client; written for Claude Code. GSAP 3.13+, where every plugin is free. React guidance targets @gsap/react 2.x, React 18/19 and the Next.js App Router. The audit scripts need Node.js 18+ and nothing else.
|
|
6
6
|
metadata:
|
|
7
|
-
version: "3.
|
|
7
|
+
version: "3.4.0"
|
|
8
8
|
verified-gsap: "3.15"
|
|
9
9
|
verified-gsap-react: "2.1"
|
|
10
10
|
---
|
|
@@ -18,8 +18,8 @@ Core principles:
|
|
|
18
18
|
|
|
19
19
|
- **Motion carries meaning or it goes.** Every animation answers "what does
|
|
20
20
|
this tell the visitor?" Hierarchy, causality, continuity, state, or
|
|
21
|
-
personality.
|
|
22
|
-
|
|
21
|
+
personality. One that answers none of those is decoration with a frame cost:
|
|
22
|
+
delete it.
|
|
23
23
|
- **A timeline, not a pile of tweens.** Five independent tweens with hand-tuned
|
|
24
24
|
delays is a choreography nobody can change. One timeline with labels and
|
|
25
25
|
relative positions is the same animation you can actually retime.
|
|
@@ -33,16 +33,16 @@ Core principles:
|
|
|
33
33
|
|
|
34
34
|
## Setup
|
|
35
35
|
|
|
36
|
-
1. **Everything is free.** GSAP became 100% free
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
1. **Everything is free.** GSAP became 100% free with 3.13 (April 2025), every
|
|
37
|
+
former Club plugin included. Check `package.json` for what the project has —
|
|
38
|
+
`gsap`, and `@gsap/react` for React — before writing imports. Install steps
|
|
39
|
+
for a membership, a token, an `.npmrc` or `gsap-bonus.tgz` are stale
|
|
40
|
+
training data: stop.
|
|
41
41
|
2. **Read the project's own animation rules first. They outrank this skill.**
|
|
42
|
-
Look for `ANIMATION.md` at the
|
|
43
|
-
|
|
42
|
+
Look for `ANIMATION.md` at the root, then a file `AGENTS.md`, `CLAUDE.md` or
|
|
43
|
+
similar points to. Open
|
|
44
44
|
[reference/project-rules.md](reference/project-rules.md) only when there is
|
|
45
|
-
|
|
45
|
+
none, or for its RTL, colour and waiver rules.
|
|
46
46
|
3. **Load by the size of the request, and nothing "for completeness".**
|
|
47
47
|
- **One element, one tween or interaction** — a hover, a fade, a retime:
|
|
48
48
|
[motion-design.md](reference/motion-design.md) and the command's own
|
|
@@ -80,12 +80,11 @@ Core principles:
|
|
|
80
80
|
Routing:
|
|
81
81
|
|
|
82
82
|
- **No argument, or no target:** read [routing.md](reference/routing.md) and
|
|
83
|
-
ask what should move.
|
|
84
|
-
|
|
85
|
-
- **
|
|
86
|
-
- **A description
|
|
87
|
-
|
|
88
|
-
table, then load what that classification needs.
|
|
83
|
+
ask what should move. "Add some animation to my homepage" names no target:
|
|
84
|
+
never invent one.
|
|
85
|
+
- **A command, explicit or implied:** load its reference and follow it.
|
|
86
|
+
- **A description** ("make this feel like Apple", "the hero is boring"): that is
|
|
87
|
+
`animate`. Classify with routing.md's table, then load what that needs.
|
|
89
88
|
- **Two commands fit:** ask once. A scroll-driven text reveal is `scroll` and
|
|
90
89
|
`text` together, not a coin toss.
|
|
91
90
|
|
|
@@ -113,7 +112,8 @@ intent → precedent → targets → trigger → motion language → technique
|
|
|
113
112
|
8. **Implementation** — typed, cleaned up, responsive.
|
|
114
113
|
9. **Performance** — transform and opacity; measure before defending anything else.
|
|
115
114
|
10. **Accessibility** — the reduced-motion design, focus, keyboard.
|
|
116
|
-
11. **Validate** —
|
|
115
|
+
11. **Validate** — [refine.md](reference/refine.md): audit, fix in order,
|
|
116
|
+
re-run, then the checklist in [motion-design.md](reference/motion-design.md).
|
|
117
117
|
|
|
118
118
|
## Output format
|
|
119
119
|
|
|
@@ -126,16 +126,19 @@ For anything beyond a one-line tweak, answer in this shape, each part short:
|
|
|
126
126
|
- **Implementation** — complete code. Not pseudocode, not an outline.
|
|
127
127
|
- **Usage** — how to mount it.
|
|
128
128
|
- **Notes** — responsive behaviour, reduced motion, cost, dev-only tooling.
|
|
129
|
+
- **Verification** — one line per check with its evidence, and what you did not
|
|
130
|
+
check. Silence reads as "checked and fine".
|
|
129
131
|
|
|
130
|
-
When a simpler technique does what
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
When a simpler technique does what was asked, **write only that one**. Say in
|
|
133
|
+
Analysis what you declined and why, and offer the requested version in a
|
|
134
|
+
sentence — write it only if they still want it. Never substitute quietly, and
|
|
135
|
+
never ship both.
|
|
134
136
|
|
|
135
137
|
## Reference map
|
|
136
138
|
|
|
137
139
|
- **Craft and core** — [motion-design](reference/motion-design.md) ·
|
|
138
|
-
[core-gsap](reference/core-gsap.md) · [timeline](reference/timeline.md)
|
|
140
|
+
[core-gsap](reference/core-gsap.md) · [timeline](reference/timeline.md) ·
|
|
141
|
+
[devices](reference/devices.md) · [refine](reference/refine.md)
|
|
139
142
|
- **Domains** — [scrolltrigger](reference/scrolltrigger.md) ·
|
|
140
143
|
[svg](reference/svg.md) · [text](reference/text.md) ·
|
|
141
144
|
[interaction](reference/interaction.md) · [flip](reference/flip.md)
|
|
@@ -145,7 +148,7 @@ after that. Never quietly substitute, and never ship both.
|
|
|
145
148
|
- **Constraints** — [performance](reference/performance.md) ·
|
|
146
149
|
[accessibility](reference/accessibility.md) ·
|
|
147
150
|
[project-rules](reference/project-rules.md)
|
|
148
|
-
- **Presets
|
|
151
|
+
- **Presets** to adapt — [reveal](preset/reveal.md) ·
|
|
149
152
|
[magnetic](preset/magnetic.md) · [cursor](preset/cursor.md) ·
|
|
150
153
|
[marquee](preset/marquee.md) · [card-stack](preset/card-stack.md) ·
|
|
151
154
|
[loader](preset/loader.md) · [cinematic](preset/cinematic.md) ·
|
|
@@ -160,27 +163,31 @@ after that. Never quietly substitute, and never ship both.
|
|
|
160
163
|
|
|
161
164
|
## Tools
|
|
162
165
|
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
These catch what is **silent**. Run them from the project root; `<skill-dir>`
|
|
167
|
+
is the skill's folder.
|
|
165
168
|
|
|
166
169
|
```bash
|
|
167
|
-
# Leaks, per-event tweens, layout properties,
|
|
168
|
-
#
|
|
169
|
-
# Exit 1 on an error.
|
|
170
|
+
# Leaks, per-event and per-frame tweens, layout and paint properties, loops,
|
|
171
|
+
# plugin ids, unregistered plugins, dev tooling. Exit 1 on an error.
|
|
170
172
|
node <skill-dir>/scripts/audit-gsap.mjs [path...] # default: src
|
|
171
173
|
node <skill-dir>/scripts/audit-gsap.mjs src --quiet # errors only
|
|
172
|
-
node <skill-dir>/scripts/audit-gsap.mjs src --json
|
|
174
|
+
node <skill-dir>/scripts/audit-gsap.mjs src --json
|
|
173
175
|
|
|
174
|
-
# What an SVG can
|
|
176
|
+
# What an SVG can do, before any animation is written.
|
|
175
177
|
node <skill-dir>/scripts/audit-svg.mjs <file.svg>
|
|
176
178
|
node <skill-dir>/scripts/audit-svg.mjs --morph <a.svg> <b.svg>
|
|
177
|
-
node <skill-dir>/scripts/audit-svg.mjs --hues <file.svg>
|
|
179
|
+
node <skill-dir>/scripts/audit-svg.mjs --hues <file.svg>
|
|
180
|
+
|
|
181
|
+
# Watch a page animate: frames, timeline, browser work.
|
|
182
|
+
# Needs a Chrome; observations, not verdicts.
|
|
183
|
+
node <skill-dir>/scripts/capture-motion.mjs <url> --at 0,300,900
|
|
178
184
|
```
|
|
179
185
|
|
|
180
|
-
Run `audit-gsap` first in an `audit` and after writing
|
|
181
|
-
finding is a bug in the script,
|
|
186
|
+
Run `audit-gsap` first in an `audit` and after writing animation — as a plugin,
|
|
187
|
+
a hook already runs it on every edit. A false finding is a bug in the script,
|
|
188
|
+
not something to work around.
|
|
182
189
|
|
|
183
190
|
`MotionPathHelper`, `GSDevTools`, `markers: true` and
|
|
184
|
-
`MorphSVGPlugin.findShapeIndex()` are development-only
|
|
185
|
-
whatever `if` surrounds it
|
|
186
|
-
and delete
|
|
191
|
+
`MorphSVGPlugin.findShapeIndex()` are development-only, and a static import
|
|
192
|
+
ships whatever `if` surrounds it. Import them dynamically behind a `NODE_ENV`
|
|
193
|
+
check, and delete it once its value is baked in.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Devices
|
|
2
|
+
|
|
3
|
+
A width breakpoint is not a device. A 1024px window can be a phone in landscape,
|
|
4
|
+
a tablet with a keyboard, or a desktop browser scaled to half the screen, and
|
|
5
|
+
"mobile" has stood in for all three for so long that most animation ships one
|
|
6
|
+
guess about which it is.
|
|
7
|
+
|
|
8
|
+
Read three axes instead. They are independent, and each answers a different
|
|
9
|
+
question.
|
|
10
|
+
|
|
11
|
+
| Axis | Question | Read from |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Layout | How much room is there? | the viewport, in `matchMedia` |
|
|
14
|
+
| Input | Can the visitor hover, and how precise are they? | `(hover: hover)`, `(pointer: fine)` |
|
|
15
|
+
| Motion tier | How much motion should run, and can this device run it? | `prefers-reduced-motion`, then capability |
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const mm = gsap.matchMedia();
|
|
19
|
+
|
|
20
|
+
mm.add(
|
|
21
|
+
{
|
|
22
|
+
wide: "(min-width: 48em)",
|
|
23
|
+
hoverable: "(hover: hover) and (pointer: fine)",
|
|
24
|
+
motion: "(prefers-reduced-motion: no-preference)",
|
|
25
|
+
},
|
|
26
|
+
(context) => {
|
|
27
|
+
const { wide, hoverable, motion } = context.conditions;
|
|
28
|
+
// …one branch, three facts
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`matchMedia` re-runs the body when any condition changes and reverts what the
|
|
34
|
+
previous run created, so a visitor who rotates a phone, plugs in a mouse or
|
|
35
|
+
turns on reduced motion gets the right version without a reload.
|
|
36
|
+
|
|
37
|
+
## The device changes the technique, never the story
|
|
38
|
+
|
|
39
|
+
A scene says something — this section arrived, that item is now first, the page
|
|
40
|
+
is loading. That does not change with the screen. What changes is how much
|
|
41
|
+
motion is spent saying it.
|
|
42
|
+
|
|
43
|
+
So each beat has three versions, and the beat decides which parts of itself are
|
|
44
|
+
device-sensitive:
|
|
45
|
+
|
|
46
|
+
- **Full** — the designed version.
|
|
47
|
+
- **Light** — the same meaning, less work: a shorter travel, no blur, fewer
|
|
48
|
+
elements moving, a stagger that runs as one group.
|
|
49
|
+
- **Static** — the end state, set. Still says *what* happened, with no travel.
|
|
50
|
+
|
|
51
|
+
Only branch the techniques that actually depend on the device. A fade is a fade
|
|
52
|
+
everywhere; a magnetic cursor is not. Writing three whole timelines where one
|
|
53
|
+
beat differs is how a page ends up with two motion languages and one of them
|
|
54
|
+
untested.
|
|
55
|
+
|
|
56
|
+
## Start safe, then enhance
|
|
57
|
+
|
|
58
|
+
Begin from the version that works everywhere — the light one — and add to it
|
|
59
|
+
when an axis says there is room. The opposite order ships the expensive version
|
|
60
|
+
to the device least able to run it and then tries to claw it back.
|
|
61
|
+
|
|
62
|
+
If you measure capability at run time, **downgrade once**. A scene that keeps
|
|
63
|
+
sampling frame rate and flipping between versions spends its budget on the
|
|
64
|
+
flipping, and the visitor sees the page change its mind. Decide early, once, and
|
|
65
|
+
stay there.
|
|
66
|
+
|
|
67
|
+
Device memory and core count (`navigator.deviceMemory`, `hardwareConcurrency`)
|
|
68
|
+
are hints, not facts: they are absent on some browsers and rounded on others.
|
|
69
|
+
Use them to pick a tier, never to decide whether a thing works.
|
|
70
|
+
|
|
71
|
+
## Where to read each axis
|
|
72
|
+
|
|
73
|
+
`prefers-reduced-motion`, `hover` and `pointer` are media queries, so
|
|
74
|
+
`gsap.matchMedia` is the one place to read them — inside the animation's own
|
|
75
|
+
context, where its cleanup already is.
|
|
76
|
+
|
|
77
|
+
The trap is reading them during render. A server has no viewport and no pointer,
|
|
78
|
+
so anything that branches on them before mount is a hydration mismatch waiting
|
|
79
|
+
to happen. Each adapter says where its own safe point is:
|
|
80
|
+
|
|
81
|
+
- **React and Next** — in the `useGSAP` body, never in render. See
|
|
82
|
+
[react.md](../adapter/react.md).
|
|
83
|
+
- **Vue and Nuxt** — in `onMounted`. See [vue.md](../adapter/vue.md).
|
|
84
|
+
- **Svelte** — in `$effect` or `onMount`. See [svelte.md](../adapter/svelte.md).
|
|
85
|
+
- **Astro** — in the component script, which is already browser-only. See
|
|
86
|
+
[astro.md](../adapter/astro.md).
|
|
87
|
+
- **Vanilla** — wherever you mount. See [vanilla.md](../adapter/vanilla.md).
|
|
88
|
+
|
|
89
|
+
## What each axis is allowed to change
|
|
90
|
+
|
|
91
|
+
| Axis | Changes | Does not change |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| Layout | travel distance, stagger shape, which elements move, pinning | what the scene says |
|
|
94
|
+
| Input | hover effects, cursors, magnetic pulls, drag affordances | entrance and scroll behaviour |
|
|
95
|
+
| Motion tier | travel, loops, parallax, blur and shadow work | the end state the visitor is left in |
|
|
96
|
+
|
|
97
|
+
A hover effect with no input branch is the common failure: a tap on a touch
|
|
98
|
+
device fires `mouseenter`, nothing fires the leave, and the effect sticks. The
|
|
99
|
+
audit reports that as `ungated-hover`.
|
|
100
|
+
|
|
101
|
+
## Reduced motion is a tier, not an off switch
|
|
102
|
+
|
|
103
|
+
The static version still communicates. It sets the end state, keeps the
|
|
104
|
+
hierarchy the motion was carrying — by order, weight or a very short fade — and
|
|
105
|
+
drops travel, loops and parallax. An empty branch is a page that looks broken to
|
|
106
|
+
the people who asked for less motion.
|
|
107
|
+
|
|
108
|
+
See [accessibility.md](accessibility.md) for what "reduced" means in practice,
|
|
109
|
+
and [performance.md](performance.md) for measuring a tier rather than guessing
|
|
110
|
+
it.
|
|
@@ -86,8 +86,8 @@ radiating, start-to-end reads as reading order. In RTL, "reading order" is
|
|
|
86
86
|
|
|
87
87
|
## Overlap
|
|
88
88
|
|
|
89
|
-
Sequential beats look like a slideshow.
|
|
90
|
-
|
|
89
|
+
Sequential beats look like a slideshow. Overlap is where an animation stops
|
|
90
|
+
feeling like a list of steps.
|
|
91
91
|
|
|
92
92
|
```ts
|
|
93
93
|
tl.to(a, { ... })
|
|
@@ -105,15 +105,18 @@ finishing.
|
|
|
105
105
|
- **Weight** — a large or heavy-looking element should move slower and ease
|
|
106
106
|
harder than a small one. Same duration on a hero image and a caption reads as
|
|
107
107
|
a bug.
|
|
108
|
-
- **Anticipation** — a small counter-move before the main one
|
|
109
|
-
|
|
108
|
+
- **Anticipation** — a small counter-move before the main one: 80–120ms, on one
|
|
109
|
+
element, not a group.
|
|
110
110
|
- **Follow-through** — elements should not all stop at once. A 0.05s offset on
|
|
111
|
-
the last beat is
|
|
111
|
+
the last beat is the difference between mechanical and alive.
|
|
112
112
|
- **Contrast** — fast against slow is what creates emphasis. If everything moves
|
|
113
113
|
at 400ms, nothing is emphasised.
|
|
114
114
|
- **Restraint** — the most premium-feeling motion is usually one or two things
|
|
115
115
|
moving well, not six things moving at all.
|
|
116
116
|
|
|
117
|
+
Technique is chosen on a ladder — transition, tween, timeline, plugin, scroll
|
|
118
|
+
takeover — in [routing.md](routing.md).
|
|
119
|
+
|
|
117
120
|
## Reading a request
|
|
118
121
|
|
|
119
122
|
| They say | They usually mean |
|
|
@@ -17,6 +17,18 @@ Before writing code, look for the project's animation rules, in this order:
|
|
|
17
17
|
Read the whole file. Where it disagrees with this skill, the project wins. If a
|
|
18
18
|
project rule looks wrong, say so — do not quietly follow the skill instead.
|
|
19
19
|
|
|
20
|
+
## A motion budget
|
|
21
|
+
|
|
22
|
+
A page gets busy one reasonable addition at a time. A budget makes the cost
|
|
23
|
+
visible before the tenth one lands: how many groups may move at once, how many
|
|
24
|
+
loops may be visible, how many scroll-bound and pointer effects a viewport
|
|
25
|
+
carries, and which plugins a route may load.
|
|
26
|
+
|
|
27
|
+
Going over is allowed — with a reason stated in the answer, not by accident.
|
|
28
|
+
Before adding to a route, inventory what it already animates: that is step 2 of
|
|
29
|
+
the pipeline, and it is also how you find the animation this one should replace
|
|
30
|
+
rather than join.
|
|
31
|
+
|
|
20
32
|
## Deriving them when there is no file
|
|
21
33
|
|
|
22
34
|
A few minutes here is cheaper than a page with two motion languages.
|
|
@@ -62,6 +74,10 @@ one is short, and each section is an instruction rather than an essay:
|
|
|
62
74
|
| Stagger | `amount` (a total) or `each` (per item) |
|
|
63
75
|
| Reduced motion | what the reduced branch shows |
|
|
64
76
|
|
|
77
|
+
## Motion budget
|
|
78
|
+
<!-- Per viewport: groups moving at once, visible loops, scroll-bound effects,
|
|
79
|
+
pointer effects. Plugins per route. Going over is allowed, with a reason. -->
|
|
80
|
+
|
|
65
81
|
## Verify with
|
|
66
82
|
<!-- The commands to run before calling it done. -->
|
|
67
83
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Refining
|
|
2
|
+
|
|
3
|
+
Writing the animation is the first draft. This is what turns it into something
|
|
4
|
+
worth shipping, and it is mostly checking rather than writing.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
generate → audit → fix → re-run every check → tune → final
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The loop matters because the failures this skill exists to catch are silent: the
|
|
11
|
+
code compiles, the page looks right in a demo, and the leak shows up three
|
|
12
|
+
navigations later. Nothing downstream will tell you. So the check is part of
|
|
13
|
+
writing, not something done afterwards if there is time.
|
|
14
|
+
|
|
15
|
+
## Audit first, and mean it
|
|
16
|
+
|
|
17
|
+
Run `audit-gsap` on the file you just wrote, before reading it yourself. It
|
|
18
|
+
costs a second, it is deterministic, and it finds the mechanical failures so
|
|
19
|
+
your attention goes to the ones it cannot see.
|
|
20
|
+
|
|
21
|
+
With the plugin installed, a hook does this automatically after every edit and
|
|
22
|
+
hands the findings back — see the Tools section of SKILL.md. Without it, run the
|
|
23
|
+
script.
|
|
24
|
+
|
|
25
|
+
A finding you believe is wrong is a bug in the script. Report it; do not work
|
|
26
|
+
around it with a waiver.
|
|
27
|
+
|
|
28
|
+
When what the code says and what the page does disagree, `explain-motion.mjs`
|
|
29
|
+
reads both: the file's map, or — given a URL — the timeline GSAP actually built.
|
|
30
|
+
|
|
31
|
+
## Fix in this order
|
|
32
|
+
|
|
33
|
+
The order is not taste. A fix further down the list can be undone by a fix
|
|
34
|
+
further up it, so doing them in this order avoids fixing the same thing twice.
|
|
35
|
+
|
|
36
|
+
1. **Lifecycle and correctness** — does it clean up, does it run once, does it
|
|
37
|
+
target what you think it targets.
|
|
38
|
+
2. **Accessibility** — the reduced-motion design, focus order, anything the
|
|
39
|
+
motion hides from a keyboard.
|
|
40
|
+
3. **Performance** — layout and paint properties, per-frame allocation, work
|
|
41
|
+
that scales with the number of items.
|
|
42
|
+
4. **Composition** — beats, hierarchy, what happens at the same time.
|
|
43
|
+
5. **Rhythm** — durations, eases, overlap. Last, because everything above
|
|
44
|
+
changes it.
|
|
45
|
+
|
|
46
|
+
## Verify a claim before you fix
|
|
47
|
+
|
|
48
|
+
When a fix rests on a claim about how something behaves — "`clearProps` runs
|
|
49
|
+
after the plugin's last write", "this element is the one that moves under
|
|
50
|
+
`fill`" — check it before writing the fix. A minimal reproduction, one variable
|
|
51
|
+
at a time, and read what actually happened in the DOM.
|
|
52
|
+
|
|
53
|
+
A fix built on a guess usually works by accident, and the comment above it is
|
|
54
|
+
then wrong for everyone who reads it next.
|
|
55
|
+
|
|
56
|
+
## Watch it, when you can
|
|
57
|
+
|
|
58
|
+
A checker reads code. It cannot see a seam, a slideshow, or a hero that starts
|
|
59
|
+
so late the visitor has already scrolled past it. When a browser is on the
|
|
60
|
+
machine, run the page:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
node <skill-dir>/scripts/capture-motion.mjs http://localhost:3000 --at 0,300,900
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
It reports a filmstrip, the timeline GSAP is actually running — which is not
|
|
67
|
+
always the one in the source — and the layouts, style recalculations and frames
|
|
68
|
+
the browser did. All observations; the judgement is yours.
|
|
69
|
+
|
|
70
|
+
Reach the state first, and change the conditions that usually break things:
|
|
71
|
+
|
|
72
|
+
- `--scroll <px|selector>`, `--hover <selector>`, `--click <selector>` run in
|
|
73
|
+
the order written, before the first capture. A hover effect or a mid-page
|
|
74
|
+
ScrollTrigger is invisible without them.
|
|
75
|
+
- `--reduced`, `--dark`, `--mobile`, `--cpu 4`. `--mobile` makes input touch,
|
|
76
|
+
so it cannot be combined with `--hover`: a coarse pointer has no hover, which
|
|
77
|
+
is the thing worth knowing.
|
|
78
|
+
|
|
79
|
+
**Without a browser, report the stage as not run.** Not "fine".
|
|
80
|
+
|
|
81
|
+
## When to stop
|
|
82
|
+
|
|
83
|
+
Stop when every check is clean, or after **three rounds**, reporting what is
|
|
84
|
+
left.
|
|
85
|
+
|
|
86
|
+
Three is not a budget to spend. Most changes are right after one. Stopping on a
|
|
87
|
+
number matters for the case where a fix keeps moving the problem: the third
|
|
88
|
+
round is where you say what is still wrong and why, rather than trying a fourth
|
|
89
|
+
shape of the same idea.
|
|
90
|
+
|
|
91
|
+
**Leave what works alone.** The loop is for the animation you were asked about.
|
|
92
|
+
Retiming a neighbouring section because you noticed it is churn: it costs review
|
|
93
|
+
attention, it risks a regression nobody asked for, and it is exactly what the
|
|
94
|
+
`outcome-no-churn` eval measures.
|
|
95
|
+
|
|
96
|
+
**Depth scales with the request.** A hover retime does not need a composition
|
|
97
|
+
pass. A site intro does.
|
|
98
|
+
|
|
99
|
+
## Report what you checked
|
|
100
|
+
|
|
101
|
+
The answer's **Verification** section is one line per check, with its evidence:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
Verification
|
|
105
|
+
- audit-gsap: clean (3 warns waived: layout-property on the grid, with reasons)
|
|
106
|
+
- reduced motion: emulated, the static branch sets the end state
|
|
107
|
+
- StrictMode: mounted twice in dev, no doubled tweens
|
|
108
|
+
- not checked: frame cost on a real phone — no device to hand
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Say what you did not check, too. "Not checked" is information; silence reads as
|
|
112
|
+
"checked and fine", and that is the one thing it must never mean.
|
|
@@ -47,6 +47,21 @@ the thing you can say in a sentence — "the logo writes itself, then the dot th
|
|
|
47
47
|
drew it runs off and writes the word" — and every technique either serves that
|
|
48
48
|
sentence or is cut.
|
|
49
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
|
+
|
|
50
65
|
## Load map
|
|
51
66
|
|
|
52
67
|
The project's own rules come first, then the size tiers in SKILL.md, setup step
|
|
@@ -69,6 +84,8 @@ single hover takes [interaction](interaction.md) and nothing else from its row.
|
|
|
69
84
|
| Three.js / R3F | [three](../adapter/three.md) or [r3f](../adapter/r3f.md), [performance](performance.md) |
|
|
70
85
|
| Audit an existing animation | [performance](performance.md), [accessibility](accessibility.md), this project's adapter |
|
|
71
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) |
|
|
72
89
|
|
|
73
90
|
Do not load a domain file "for completeness". Loading `adapter/three.md` for a
|
|
74
91
|
button hover is the failure this map exists to prevent.
|
|
@@ -82,6 +99,18 @@ the mechanical failures before you spend attention on them:
|
|
|
82
99
|
node .claude/skills/gsap-creative-animation/scripts/audit-gsap.mjs <path>
|
|
83
100
|
```
|
|
84
101
|
|
|
102
|
+
**Read the map before reading the code.** `explain-motion.mjs` prints what a
|
|
103
|
+
file animates — scopes, preference branches, beats in order with the reasons
|
|
104
|
+
their comments give, and the scroll configuration — and cites the audit rather
|
|
105
|
+
than judging. Given a URL it reads the running page instead, which is the only
|
|
106
|
+
way to see the timeline GSAP actually built, after every branch and early
|
|
107
|
+
return:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
node <skill-dir>/scripts/explain-motion.mjs src/hooks/useHero.ts
|
|
111
|
+
node <skill-dir>/scripts/explain-motion.mjs http://localhost:3000
|
|
112
|
+
```
|
|
113
|
+
|
|
85
114
|
**When an animation is not visible, ask whether it was ever built.** Before
|
|
86
115
|
touching timings or transforms, check that the code which creates it actually
|
|
87
116
|
runs. Animation is routinely built behind a condition — an image decode race, a
|
|
@@ -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.
|