@polymorphism-tech/morph-spec 4.15.0 → 4.17.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 +2 -2
- package/bin/morph-spec.js +4 -0
- package/bin/task-manager.js +999 -962
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents/ai-agents/vector-search-expert.md +247 -233
- package/framework/agents/backend/ms-agent-expert.md +2 -1
- package/framework/agents/backend/neon-expert.md +397 -0
- package/framework/agents/frontend/gsap-animator.md +131 -0
- package/framework/agents.json +1175 -1077
- package/framework/commands/morph-preflight.md +200 -192
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +1 -1
- package/framework/rules/gsap-standards.md +13 -0
- package/framework/rules/neon-standards.md +25 -0
- package/framework/skills/level-0-meta/mcp-registry.json +1 -1
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +472 -472
- package/framework/skills/level-0-meta/morph-init/SKILL.md +30 -11
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +7 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +24 -24
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +12 -12
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +9 -9
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +143 -143
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +184 -107
- package/framework/standards/ai-agents/production.md +1 -0
- package/framework/standards/ai-agents/setup.md +3 -2
- package/framework/standards/ai-agents/workflows.md +1 -0
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +4 -4
- package/framework/standards/backend/database/postgresql/database.md +58 -78
- package/framework/standards/backend/dotnet/scalar-ui.md +172 -172
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -274
- package/framework/standards/backend/integrations/asaas/asaas-api.md +1 -1
- package/framework/standards/backend/integrations/clerk/clerk-auth.md +23 -21
- package/framework/standards/frontend/design-system/animations.md +9 -0
- package/framework/standards/frontend/gsap/gsap-core.md +463 -0
- package/framework/standards/frontend/gsap/gsap-plugins.md +294 -0
- package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +318 -0
- package/framework/standards/frontend/nextjs/motion-patterns.md +1 -0
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +47 -37
- package/framework/standards/infrastructure/docker/easypanel-deploy.md +4 -8
- package/framework/standards/infrastructure/neon/neon-pgrag.md +239 -0
- package/framework/standards/infrastructure/neon/neon-pgvector.md +236 -0
- package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +310 -0
- package/framework/standards/infrastructure/neon/neon-setup.md +186 -0
- package/framework/standards/integration/mcp/mcp-tools.md +32 -34
- package/framework/templates/README.md +18 -18
- package/framework/templates/REGISTRY.json +49 -96
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/backend/repository.cs +148 -148
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/database/vector-entity.cs +93 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +1 -1
- package/framework/templates/code/sql/{supabase-migration.sql → postgresql-migration.sql} +8 -17
- package/framework/templates/code/sql/{supabase-migration.template.sql → postgresql-migration.template.sql} +10 -13
- package/framework/templates/code/sql/{rls-policy.sql → postgresql-rls-policy.sql} +8 -8
- package/framework/templates/code/typescript/contracts.ts +167 -168
- package/framework/templates/context/CONTEXT.md +3 -3
- package/framework/templates/docs/proposal.md +6 -6
- package/framework/templates/docs/ui-components.md +1 -1
- package/framework/templates/feature/decisions.md +121 -121
- package/framework/templates/feature/recap.md +145 -145
- package/framework/templates/feature/tasks.md +199 -199
- package/framework/templates/frontend/nextjs/gsap-scroll-section.tsx.hbs +44 -0
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -67
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/docker/easypanel.template.json +46 -54
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/integrations/semantic-kernel-neon.cs +125 -0
- package/framework/workflows/README.md +1 -1
- package/framework/workflows/docs/full-morph.md +69 -69
- package/package.json +1 -1
- package/src/commands/agents/dispatch-agents.js +19 -15
- package/src/commands/project/setup-infra-cmd.js +1 -1
- package/src/commands/state/advance-phase.js +5 -0
- package/src/commands/tasks/task.js +3 -0
- package/src/core/state/state-manager.js +45 -1
- package/src/core/templates/template-registry.js +1 -66
- package/src/lib/detectors/index.js +2 -2
- package/src/lib/generators/architecture-scanner.js +640 -640
- package/src/lib/installers/mcp-installer.js +1 -1
- package/src/lib/tasks/task-classifier.js +15 -1
- package/src/lib/tasks/test-runner.js +5 -1
- package/src/lib/validators/content/content-validator.js +9 -10
- package/src/lib/validators/validation-runner.js +7 -5
- package/src/scripts/setup-infra.js +60 -30
- package/framework/agents/backend/supabase-expert.md +0 -256
- package/framework/rules/supabase-standards.md +0 -29
- package/framework/standards/infrastructure/supabase/mcp-setup.md +0 -142
- package/framework/standards/infrastructure/supabase/supabase-auth.md +0 -176
- package/framework/standards/infrastructure/supabase/supabase-custom-jwt.md +0 -206
- package/framework/standards/infrastructure/supabase/supabase-pgvector.md +0 -169
- package/framework/standards/infrastructure/supabase/supabase-rls.md +0 -184
- package/framework/standards/infrastructure/supabase/supabase-storage.md +0 -153
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
---
|
|
2
|
+
scope: frontend-nextjs-ui
|
|
3
|
+
layer: 3
|
|
4
|
+
keywords: gsap, animation, timeline, tween, useGSAP, greensock, transform
|
|
5
|
+
load_when: gsap-animator active
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# GSAP Core & React Integration
|
|
9
|
+
|
|
10
|
+
Reference guide for implementing animations in Next.js projects using GSAP and the official @gsap/react bindings. Covers core APIs, React integration via useGSAP, timeline sequencing, utility functions, performance, accessibility, and the decision boundary with Framer Motion.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Section 1: When to Use GSAP vs Framer Motion
|
|
15
|
+
|
|
16
|
+
| Need | Use | Why |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Simple enter/exit, layout animations, gestures | Framer Motion | Declarative, React-native, less boilerplate |
|
|
19
|
+
| ScrollTrigger (pin, scrub, parallax) | GSAP | No Framer equivalent for scroll-linked progress |
|
|
20
|
+
| SVG morphing, path drawing | GSAP | MorphSVG, DrawSVG plugins |
|
|
21
|
+
| Complex multi-step timelines | GSAP | Position parameter, labels, nested timelines |
|
|
22
|
+
| SplitText, scramble effects | GSAP | Premium plugins, no Framer equivalent |
|
|
23
|
+
| Drag with physics/inertia | GSAP | Draggable + InertiaPlugin |
|
|
24
|
+
| AnimatePresence (mount/unmount) | Framer Motion | GSAP has no declarative mount/unmount |
|
|
25
|
+
|
|
26
|
+
**CRITICAL RULE:** Never animate the same DOM element with both GSAP and Framer Motion simultaneously. They will fight for control of inline styles. Choose one library per element and stick with it.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Section 2: Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install gsap @gsap/react
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
GSAP 3.12+ includes Flip, Observer, and ScrollToPlugin for free. Premium plugins (SplitText, MorphSVG, DrawSVG, MotionPath, Draggable, InertiaPlugin) require a Club GreenSock license — install via `gsap@npm:@gsap/shockingly`.
|
|
37
|
+
|
|
38
|
+
### Plugin Registration
|
|
39
|
+
|
|
40
|
+
Register plugins **once** at app init (e.g., `providers.tsx` or `layout.tsx`), not inside individual components:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
'use client';
|
|
44
|
+
import gsap from 'gsap';
|
|
45
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
46
|
+
import { Flip } from 'gsap/Flip';
|
|
47
|
+
|
|
48
|
+
gsap.registerPlugin(ScrollTrigger, Flip);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Registering inside components causes redundant calls on every mount and risks race conditions if multiple components register different plugins.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Section 3: Core Animation APIs
|
|
56
|
+
|
|
57
|
+
### Tween Methods
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
'use client';
|
|
61
|
+
import gsap from 'gsap';
|
|
62
|
+
|
|
63
|
+
// Animate FROM current state TO target
|
|
64
|
+
gsap.to('.box', { x: 200, opacity: 1, duration: 0.5 });
|
|
65
|
+
|
|
66
|
+
// Animate FROM target TO current state
|
|
67
|
+
gsap.from('.box', { x: -200, opacity: 0, duration: 0.5 });
|
|
68
|
+
|
|
69
|
+
// Explicit start and end states
|
|
70
|
+
gsap.fromTo('.box',
|
|
71
|
+
{ x: -200, opacity: 0 },
|
|
72
|
+
{ x: 200, opacity: 1, duration: 0.5 }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Instantly set properties (no animation)
|
|
76
|
+
gsap.set('.box', { x: 0, opacity: 1 });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Transform Shorthand Aliases
|
|
80
|
+
|
|
81
|
+
GSAP provides shorthand aliases that map directly to CSS transforms:
|
|
82
|
+
|
|
83
|
+
| Alias | CSS Equivalent |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `x`, `y` | `translateX()`, `translateY()` |
|
|
86
|
+
| `xPercent`, `yPercent` | `translateX(%)`, `translateY(%)` |
|
|
87
|
+
| `rotation` | `rotate()` |
|
|
88
|
+
| `scale`, `scaleX`, `scaleY` | `scale()`, `scaleX()`, `scaleY()` |
|
|
89
|
+
| `skewX`, `skewY` | `skewX()`, `skewY()` |
|
|
90
|
+
|
|
91
|
+
### autoAlpha
|
|
92
|
+
|
|
93
|
+
Use `autoAlpha` instead of `opacity`. It behaves identically to `opacity` but automatically sets `visibility: hidden` when the value reaches 0, removing the element from the accessibility tree and preventing invisible click targets.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
'use client';
|
|
97
|
+
import gsap from 'gsap';
|
|
98
|
+
|
|
99
|
+
// Element becomes visibility:hidden at autoAlpha:0
|
|
100
|
+
gsap.to('.overlay', { autoAlpha: 0, duration: 0.3 });
|
|
101
|
+
|
|
102
|
+
// Reveal: sets visibility:visible then animates opacity
|
|
103
|
+
gsap.to('.overlay', { autoAlpha: 1, duration: 0.3 });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Section 4: React/Next.js Integration
|
|
109
|
+
|
|
110
|
+
The `useGSAP()` hook from `@gsap/react` is the canonical pattern for GSAP in React. It replaces manual `useEffect` + `gsap.context()` cleanup.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
'use client';
|
|
114
|
+
import { useRef } from 'react';
|
|
115
|
+
import { useGSAP } from '@gsap/react';
|
|
116
|
+
import gsap from 'gsap';
|
|
117
|
+
|
|
118
|
+
function AnimatedComponent() {
|
|
119
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
120
|
+
|
|
121
|
+
useGSAP(() => {
|
|
122
|
+
gsap.to('.box', { x: 100, rotation: 360, duration: 1 });
|
|
123
|
+
}, { scope: containerRef }); // scopes selectors to this container
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div ref={containerRef}>
|
|
127
|
+
<div className="box">Animated</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Scope Refs
|
|
134
|
+
|
|
135
|
+
**ALWAYS** pass `{ scope: containerRef }` to `useGSAP`. This scopes all string selectors (e.g., `'.box'`) to descendants of that container, preventing your animations from accidentally targeting elements in other components.
|
|
136
|
+
|
|
137
|
+
### contextSafe for Event Handlers
|
|
138
|
+
|
|
139
|
+
Animations created outside the `useGSAP` callback (e.g., in click handlers) must be wrapped with `contextSafe()` to ensure proper cleanup:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
'use client';
|
|
143
|
+
import { useRef } from 'react';
|
|
144
|
+
import { useGSAP } from '@gsap/react';
|
|
145
|
+
import gsap from 'gsap';
|
|
146
|
+
|
|
147
|
+
function ClickAnimated() {
|
|
148
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
149
|
+
|
|
150
|
+
const { contextSafe } = useGSAP({ scope: containerRef });
|
|
151
|
+
|
|
152
|
+
const handleClick = contextSafe(() => {
|
|
153
|
+
gsap.to('.box', { rotation: '+=360', duration: 0.6 });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div ref={containerRef}>
|
|
158
|
+
<div className="box" onClick={handleClick}>Click me</div>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Dependencies Array
|
|
165
|
+
|
|
166
|
+
Pass a dependencies array as the second element of the options to re-run the animation when values change:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
'use client';
|
|
170
|
+
import { useState, useRef } from 'react';
|
|
171
|
+
import { useGSAP } from '@gsap/react';
|
|
172
|
+
import gsap from 'gsap';
|
|
173
|
+
|
|
174
|
+
function ResponsiveAnimation() {
|
|
175
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
176
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
177
|
+
|
|
178
|
+
useGSAP(() => {
|
|
179
|
+
gsap.to('.panel', { height: isOpen ? 'auto' : 0, duration: 0.4 });
|
|
180
|
+
}, { scope: containerRef, dependencies: [isOpen] });
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div ref={containerRef}>
|
|
184
|
+
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
|
|
185
|
+
<div className="panel">Content</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Cleanup
|
|
192
|
+
|
|
193
|
+
`useGSAP` handles cleanup automatically. When the component unmounts, all GSAP animations created inside the callback are reverted and killed. There is no need to manually call `.kill()` or `.revert()`.
|
|
194
|
+
|
|
195
|
+
**CRITICAL:** All GSAP code MUST be in `'use client'` components. GSAP cannot run server-side — it depends on the DOM and `window` object.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Section 5: Timeline Sequencing
|
|
200
|
+
|
|
201
|
+
`gsap.timeline()` sequences multiple animations with precise timing control.
|
|
202
|
+
|
|
203
|
+
### Position Parameter Syntax
|
|
204
|
+
|
|
205
|
+
| Syntax | Meaning |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `1` | At absolute time 1 second |
|
|
208
|
+
| `"+=0.5"` | 0.5s after the end of the previous animation |
|
|
209
|
+
| `"-=0.2"` | 0.2s before the end of the previous animation (overlap) |
|
|
210
|
+
| `"reveal"` | At the label named "reveal" |
|
|
211
|
+
| `"<"` | At the start of the previous animation |
|
|
212
|
+
| `">"` | At the end of the previous animation |
|
|
213
|
+
| `"<0.2"` | 0.2s after the start of the previous animation |
|
|
214
|
+
|
|
215
|
+
### Complete Timeline Example
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
'use client';
|
|
219
|
+
import { useRef } from 'react';
|
|
220
|
+
import { useGSAP } from '@gsap/react';
|
|
221
|
+
import gsap from 'gsap';
|
|
222
|
+
import { durations } from '@/lib/design-tokens';
|
|
223
|
+
|
|
224
|
+
function HeroTimeline() {
|
|
225
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
226
|
+
|
|
227
|
+
useGSAP(() => {
|
|
228
|
+
const tl = gsap.timeline({
|
|
229
|
+
defaults: { duration: durations.base, ease: 'power2.out' },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
tl.from('.hero-bg', { autoAlpha: 0, scale: 1.1 })
|
|
233
|
+
.addLabel('reveal')
|
|
234
|
+
.from('.hero-title', { y: 40, autoAlpha: 0 }, 'reveal')
|
|
235
|
+
.from('.hero-subtitle', { y: 30, autoAlpha: 0 }, 'reveal+=0.15')
|
|
236
|
+
.from('.hero-cta', { y: 20, autoAlpha: 0 }, '>-0.1')
|
|
237
|
+
.from('.hero-badges', { autoAlpha: 0, stagger: 0.08 }, '<0.2');
|
|
238
|
+
|
|
239
|
+
// Nested timeline
|
|
240
|
+
const cardsTl = gsap.timeline({ defaults: { duration: durations.fast } });
|
|
241
|
+
cardsTl.from('.card', { y: 60, autoAlpha: 0, stagger: 0.1 });
|
|
242
|
+
|
|
243
|
+
tl.add(cardsTl, 'reveal+=0.4');
|
|
244
|
+
}, { scope: containerRef });
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<div ref={containerRef}>
|
|
248
|
+
<div className="hero-bg" />
|
|
249
|
+
<h1 className="hero-title">Title</h1>
|
|
250
|
+
<p className="hero-subtitle">Subtitle</p>
|
|
251
|
+
<button className="hero-cta">Get Started</button>
|
|
252
|
+
<div className="hero-badges">
|
|
253
|
+
<span className="card">A</span>
|
|
254
|
+
<span className="card">B</span>
|
|
255
|
+
<span className="card">C</span>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Playback Controls
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const tl = gsap.timeline({ paused: true });
|
|
266
|
+
|
|
267
|
+
tl.play(); // play from current position
|
|
268
|
+
tl.pause(); // pause at current position
|
|
269
|
+
tl.reverse(); // play in reverse
|
|
270
|
+
tl.restart(); // restart from beginning
|
|
271
|
+
tl.progress(0.5); // jump to 50%
|
|
272
|
+
tl.seek('reveal'); // jump to label
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Section 6: Utility Functions
|
|
278
|
+
|
|
279
|
+
`gsap.utils` provides composable helper functions. Omitting the last value argument returns a reusable function.
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
'use client';
|
|
283
|
+
import gsap from 'gsap';
|
|
284
|
+
|
|
285
|
+
// Convert NodeList/selector to array
|
|
286
|
+
const items = gsap.utils.toArray<HTMLElement>('.items');
|
|
287
|
+
|
|
288
|
+
// Clamp value between min and max
|
|
289
|
+
gsap.utils.clamp(0, 100, 150); // 100
|
|
290
|
+
const clamp0to100 = gsap.utils.clamp(0, 100); // reusable
|
|
291
|
+
clamp0to100(150); // 100
|
|
292
|
+
|
|
293
|
+
// Map value from one range to another
|
|
294
|
+
gsap.utils.mapRange(0, 1, 0, 100, 0.5); // 50
|
|
295
|
+
const normalize = gsap.utils.mapRange(0, 1, 0, 100); // reusable
|
|
296
|
+
|
|
297
|
+
// Snap to nearest grid value
|
|
298
|
+
gsap.utils.snap(10, 23); // 20
|
|
299
|
+
const snapTo5 = gsap.utils.snap(5); // reusable
|
|
300
|
+
|
|
301
|
+
// Interpolate between two values
|
|
302
|
+
gsap.utils.interpolate(0, 100, 0.5); // 50
|
|
303
|
+
|
|
304
|
+
// Compose utility functions into a pipeline
|
|
305
|
+
const process = gsap.utils.pipe(
|
|
306
|
+
gsap.utils.clamp(0, 100),
|
|
307
|
+
gsap.utils.snap(5)
|
|
308
|
+
);
|
|
309
|
+
process(103); // 100 (clamped), then 100 (snapped)
|
|
310
|
+
|
|
311
|
+
// Scoped selector (useful in components)
|
|
312
|
+
const q = gsap.utils.selector(containerRef.current);
|
|
313
|
+
gsap.to(q('.title'), { y: -20 });
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Section 7: Performance
|
|
319
|
+
|
|
320
|
+
- **Animate ONLY transform properties** (`x`, `y`, `scale`, `rotation`) and `opacity` — these use the GPU compositor and skip layout/paint.
|
|
321
|
+
- **AVOID layout-triggering properties** (`width`, `height`, `top`, `left`, `margin`, `padding`) — they cause full layout recalculation.
|
|
322
|
+
- Apply `will-change: transform` ONLY on elements that are actively animating, and remove it after the animation completes.
|
|
323
|
+
- Use `gsap.quickTo()` for high-frequency updates such as mouse followers and cursor tracking:
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
'use client';
|
|
327
|
+
import { useRef } from 'react';
|
|
328
|
+
import { useGSAP } from '@gsap/react';
|
|
329
|
+
import gsap from 'gsap';
|
|
330
|
+
|
|
331
|
+
function CursorFollower() {
|
|
332
|
+
const cursorRef = useRef<HTMLDivElement>(null);
|
|
333
|
+
|
|
334
|
+
useGSAP(() => {
|
|
335
|
+
const xTo = gsap.quickTo(cursorRef.current, 'x', { duration: 0.3, ease: 'power3' });
|
|
336
|
+
const yTo = gsap.quickTo(cursorRef.current, 'y', { duration: 0.3, ease: 'power3' });
|
|
337
|
+
|
|
338
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
339
|
+
xTo(e.clientX);
|
|
340
|
+
yTo(e.clientY);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
344
|
+
return () => window.removeEventListener('mousemove', handleMouseMove);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return <div ref={cursorRef} className="cursor-dot" />;
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
- Use the `stagger` property instead of creating individual tweens for each element.
|
|
352
|
+
- Batch DOM reads before writes to prevent layout thrashing.
|
|
353
|
+
- Kill off-screen animations and debounce `ScrollTrigger.refresh()` on resize.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Section 8: Accessibility
|
|
358
|
+
|
|
359
|
+
**MANDATORY:** Every animated component must respect `prefers-reduced-motion`.
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
'use client';
|
|
363
|
+
import { useRef } from 'react';
|
|
364
|
+
import { useGSAP } from '@gsap/react';
|
|
365
|
+
import gsap from 'gsap';
|
|
366
|
+
|
|
367
|
+
// Option A: gsap.matchMedia (recommended)
|
|
368
|
+
function ScrollHero() {
|
|
369
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
370
|
+
|
|
371
|
+
useGSAP(() => {
|
|
372
|
+
gsap.matchMedia().add('(prefers-reduced-motion: no-preference)', () => {
|
|
373
|
+
// Animations here are automatically reverted when media query stops matching
|
|
374
|
+
gsap.to('.hero', { y: -50, scrollTrigger: { scrub: true } });
|
|
375
|
+
});
|
|
376
|
+
}, { scope: containerRef });
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<div ref={containerRef}>
|
|
380
|
+
<div className="hero">Content</div>
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Option B: manual check
|
|
386
|
+
function ManualCheckExample() {
|
|
387
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
388
|
+
|
|
389
|
+
useGSAP(() => {
|
|
390
|
+
const prefersReducedMotion = window.matchMedia(
|
|
391
|
+
'(prefers-reduced-motion: reduce)'
|
|
392
|
+
).matches;
|
|
393
|
+
|
|
394
|
+
if (prefersReducedMotion) {
|
|
395
|
+
gsap.set('.hero', { opacity: 1 }); // skip to final state
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
gsap.from('.hero', { y: 40, autoAlpha: 0, duration: 0.6 });
|
|
400
|
+
}, { scope: containerRef });
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<div ref={containerRef}>
|
|
404
|
+
<div className="hero">Content</div>
|
|
405
|
+
</div>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
When reduced motion is active:
|
|
411
|
+
- Skip all transform animations (`x`, `y`, `scale`, `rotation`).
|
|
412
|
+
- Keep opacity-only fades — they do not cause vestibular issues.
|
|
413
|
+
- Use `gsap.set()` to jump to the final visual state so content is still visible.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Section 9: Design Token Bridge
|
|
418
|
+
|
|
419
|
+
Import from the same `@/lib/design-tokens` module that Framer Motion uses to maintain consistency across animation libraries:
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
'use client';
|
|
423
|
+
import gsap from 'gsap';
|
|
424
|
+
import { durations, easings } from '@/lib/design-tokens';
|
|
425
|
+
|
|
426
|
+
gsap.to('.element', {
|
|
427
|
+
duration: durations.base, // 0.3
|
|
428
|
+
ease: `cubic-bezier(${easings.standard.join(',')})`,
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Alternatively, use GSAP's built-in easing strings when the design token bridge is not needed:
|
|
433
|
+
|
|
434
|
+
| GSAP Ease | Behavior |
|
|
435
|
+
|---|---|
|
|
436
|
+
| `"power2.out"` | Standard deceleration |
|
|
437
|
+
| `"power2.inOut"` | Smooth start and end |
|
|
438
|
+
| `"back.out(1.7)"` | Slight overshoot |
|
|
439
|
+
| `"elastic.out(1, 0.3)"` | Spring-like bounce |
|
|
440
|
+
| `"none"` | Linear (no easing) |
|
|
441
|
+
|
|
442
|
+
> **Rule:** Never hardcode duration numbers. Import from `@/lib/design-tokens`.
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Section 10: Anti-patterns
|
|
447
|
+
|
|
448
|
+
| Anti-pattern | Correct approach |
|
|
449
|
+
|---|---|
|
|
450
|
+
| Using useEffect instead of useGSAP | Always use `useGSAP()` from `@gsap/react` — handles cleanup automatically |
|
|
451
|
+
| Forgetting scope ref | Always pass `{ scope: containerRef }` to useGSAP — prevents targeting elements in other components |
|
|
452
|
+
| No cleanup | useGSAP handles this; for manual `gsap.context()`, call `ctx.revert()` in cleanup |
|
|
453
|
+
| Animating width/height/top/left | Use `x`, `y`, `scale`, `rotation` (GPU-composited) |
|
|
454
|
+
| No prefers-reduced-motion check | Wrap animations in `gsap.matchMedia()` or check manually |
|
|
455
|
+
| Mixing GSAP + Framer Motion on same element | Choose one per element — they fight for inline styles |
|
|
456
|
+
| Registering plugins inside components | Register once at app init (`layout.tsx` or `providers.tsx`) |
|
|
457
|
+
| Using GSAP for simple enter/exit | Use Framer Motion — it is declarative and handles mount/unmount |
|
|
458
|
+
| Hardcoded durations/easings | Import from `@/lib/design-tokens` |
|
|
459
|
+
| Creating animations before DOM exists | useGSAP runs after mount — never animate in render body |
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
*MORPH-SPEC by Polymorphism Tech*
|