@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 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.2.0 audit src --quiet"
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`.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mehshekari/gsap-motion",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "description": "Install the gsap-creative-animation Agent Skill, and audit GSAP animation for leaks, jank and accessibility failures — with npx.",
5
5
  "keywords": [
6
6
  "gsap",
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.2.0"
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. An animation that answers none of those is decoration with a
22
- frame cost, and the right move is to delete it.
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, every former Club plugin
37
- included, with 3.13 (April 2025). Check `package.json` for what the project
38
- has — `gsap`, and `@gsap/react` for React — before writing imports. Install
39
- steps for a membership, a token, an `.npmrc` or `gsap-bonus.tgz` come from
40
- stale training data: stop.
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 repository root, then for a file that
43
- `AGENTS.md`, `CLAUDE.md` or similar points to. Open
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
- no such file, or for its RTL, colour and waiver rules.
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. A whole page or site with nothing in it named — "add
84
- some animation to my homepage" — has no target. Never invent one.
85
- - **An explicit or clearly implied command:** load its reference and follow it.
86
- - **A description rather than a command** ("make this feel like Apple", "the
87
- hero is boring"): that is `animate`. Classify the intent with routing.md's
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** — the checklist at the end of [motion-design.md](reference/motion-design.md).
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 the user asked for, **write only the
131
- simpler one**. Say in Analysis which technique you declined and why, and offer
132
- the requested version in one sentence; write it only if the user still wants it
133
- after that. Never quietly substitute, and never ship both.
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**, starting points to adapt — [reveal](preset/reveal.md) ·
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
- Both scripts catch failures that are **silent**. Run them from the project
164
- root; `<skill-dir>` is where this skill is installed.
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, eased loops, an onComplete that
168
- # never fires, shared plugin ids, unregistered plugins, dev tooling left in.
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 # machine-readable
174
+ node <skill-dir>/scripts/audit-gsap.mjs src --json
173
175
 
174
- # What an SVG can actually do, before any animation is written.
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> # also flag hue literals
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 any animation. A false
181
- finding is a bug in the script, not something to work around.
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. A static import ships
185
- whatever `if` surrounds it: import them dynamically behind a `NODE_ENV` check,
186
- and delete the block once their value is baked in.
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. Real choreography overlaps, and the
90
- overlap is where an animation stops feeling like a list of steps.
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. Costs 80–120ms
109
- and makes a gesture feel intentional. Use on one element, not a group.
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 usually the difference between mechanical and alive.
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.