@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.
Files changed (100) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +4 -0
  3. package/bin/task-manager.js +999 -962
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents/ai-agents/vector-search-expert.md +247 -233
  8. package/framework/agents/backend/ms-agent-expert.md +2 -1
  9. package/framework/agents/backend/neon-expert.md +397 -0
  10. package/framework/agents/frontend/gsap-animator.md +131 -0
  11. package/framework/agents.json +1175 -1077
  12. package/framework/commands/morph-preflight.md +200 -192
  13. package/framework/hooks/claude-code/session-start/inject-morph-context.js +1 -1
  14. package/framework/rules/gsap-standards.md +13 -0
  15. package/framework/rules/neon-standards.md +25 -0
  16. package/framework/skills/level-0-meta/mcp-registry.json +1 -1
  17. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +472 -472
  18. package/framework/skills/level-0-meta/morph-init/SKILL.md +30 -11
  19. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +5 -5
  20. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +5 -5
  21. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +7 -7
  22. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +24 -24
  23. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +12 -12
  24. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +9 -9
  25. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
  26. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
  27. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +143 -143
  28. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
  29. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  30. package/framework/standards/STANDARDS.json +184 -107
  31. package/framework/standards/ai-agents/production.md +1 -0
  32. package/framework/standards/ai-agents/setup.md +3 -2
  33. package/framework/standards/ai-agents/workflows.md +1 -0
  34. package/framework/standards/architecture/vertical-slice/vertical-slice.md +4 -4
  35. package/framework/standards/backend/database/postgresql/database.md +58 -78
  36. package/framework/standards/backend/dotnet/scalar-ui.md +172 -172
  37. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -274
  38. package/framework/standards/backend/integrations/asaas/asaas-api.md +1 -1
  39. package/framework/standards/backend/integrations/clerk/clerk-auth.md +23 -21
  40. package/framework/standards/frontend/design-system/animations.md +9 -0
  41. package/framework/standards/frontend/gsap/gsap-core.md +463 -0
  42. package/framework/standards/frontend/gsap/gsap-plugins.md +294 -0
  43. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +318 -0
  44. package/framework/standards/frontend/nextjs/motion-patterns.md +1 -0
  45. package/framework/standards/frontend/nextjs/nextjs-patterns.md +47 -37
  46. package/framework/standards/infrastructure/docker/easypanel-deploy.md +4 -8
  47. package/framework/standards/infrastructure/neon/neon-pgrag.md +239 -0
  48. package/framework/standards/infrastructure/neon/neon-pgvector.md +236 -0
  49. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +310 -0
  50. package/framework/standards/infrastructure/neon/neon-setup.md +186 -0
  51. package/framework/standards/integration/mcp/mcp-tools.md +32 -34
  52. package/framework/templates/README.md +18 -18
  53. package/framework/templates/REGISTRY.json +49 -96
  54. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  55. package/framework/templates/code/dotnet/backend/repository.cs +148 -148
  56. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
  57. package/framework/templates/code/dotnet/database/vector-entity.cs +93 -0
  58. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +1 -1
  59. package/framework/templates/code/sql/{supabase-migration.sql → postgresql-migration.sql} +8 -17
  60. package/framework/templates/code/sql/{supabase-migration.template.sql → postgresql-migration.template.sql} +10 -13
  61. package/framework/templates/code/sql/{rls-policy.sql → postgresql-rls-policy.sql} +8 -8
  62. package/framework/templates/code/typescript/contracts.ts +167 -168
  63. package/framework/templates/context/CONTEXT.md +3 -3
  64. package/framework/templates/docs/proposal.md +6 -6
  65. package/framework/templates/docs/ui-components.md +1 -1
  66. package/framework/templates/feature/decisions.md +121 -121
  67. package/framework/templates/feature/recap.md +145 -145
  68. package/framework/templates/feature/tasks.md +199 -199
  69. package/framework/templates/frontend/nextjs/gsap-scroll-section.tsx.hbs +44 -0
  70. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -67
  71. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  72. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  73. package/framework/templates/infrastructure/docker/easypanel.template.json +46 -54
  74. package/framework/templates/infrastructure/github/README.md +56 -56
  75. package/framework/templates/integrations/semantic-kernel-neon.cs +125 -0
  76. package/framework/workflows/README.md +1 -1
  77. package/framework/workflows/docs/full-morph.md +69 -69
  78. package/package.json +1 -1
  79. package/src/commands/agents/dispatch-agents.js +19 -15
  80. package/src/commands/project/setup-infra-cmd.js +1 -1
  81. package/src/commands/state/advance-phase.js +5 -0
  82. package/src/commands/tasks/task.js +3 -0
  83. package/src/core/state/state-manager.js +45 -1
  84. package/src/core/templates/template-registry.js +1 -66
  85. package/src/lib/detectors/index.js +2 -2
  86. package/src/lib/generators/architecture-scanner.js +640 -640
  87. package/src/lib/installers/mcp-installer.js +1 -1
  88. package/src/lib/tasks/task-classifier.js +15 -1
  89. package/src/lib/tasks/test-runner.js +5 -1
  90. package/src/lib/validators/content/content-validator.js +9 -10
  91. package/src/lib/validators/validation-runner.js +7 -5
  92. package/src/scripts/setup-infra.js +60 -30
  93. package/framework/agents/backend/supabase-expert.md +0 -256
  94. package/framework/rules/supabase-standards.md +0 -29
  95. package/framework/standards/infrastructure/supabase/mcp-setup.md +0 -142
  96. package/framework/standards/infrastructure/supabase/supabase-auth.md +0 -176
  97. package/framework/standards/infrastructure/supabase/supabase-custom-jwt.md +0 -206
  98. package/framework/standards/infrastructure/supabase/supabase-pgvector.md +0 -169
  99. package/framework/standards/infrastructure/supabase/supabase-rls.md +0 -184
  100. package/framework/standards/infrastructure/supabase/supabase-storage.md +0 -153
@@ -0,0 +1,294 @@
1
+ ---
2
+ scope: frontend-nextjs-gsap
3
+ layer: 3
4
+ keywords: flip, splittext, morphsvg, drawsvg, motionpath, draggable, observer, scrollto, gsap-plugins
5
+ load_when: gsap-animator active
6
+ ---
7
+
8
+ # GSAP Plugins
9
+
10
+ Reference guide for GSAP's plugin ecosystem in Next.js. Covers free plugins (Flip, Observer, ScrollToPlugin) and Club GreenSock premium plugins (SplitText, MorphSVG, DrawSVG, MotionPath, Draggable, InertiaPlugin).
11
+
12
+ ## Plugin Registration & Licensing
13
+
14
+ Free plugins (included in gsap 3.12+): Flip, Observer, ScrollToPlugin
15
+
16
+ Premium (Club GreenSock): SplitText, MorphSVG, DrawSVG, MotionPath, Draggable, InertiaPlugin, ScrambleText, CustomEase, CustomWiggle, CustomBounce, Physics2D, PhysicsProps
17
+
18
+ ```bash
19
+ # Free plugins — included with gsap
20
+ npm install gsap
21
+
22
+ # Premium plugins — requires Club GreenSock license
23
+ npm install gsap@npm:@gsap/shockingly
24
+ ```
25
+
26
+ Registration pattern (do ONCE at app init):
27
+
28
+ ```typescript
29
+ 'use client';
30
+ import gsap from 'gsap';
31
+ import { Flip } from 'gsap/Flip';
32
+ import { SplitText } from 'gsap/SplitText';
33
+ import { ScrollToPlugin } from 'gsap/ScrollToPlugin';
34
+
35
+ gsap.registerPlugin(Flip, SplitText, ScrollToPlugin);
36
+ ```
37
+
38
+ ## Flip Plugin (Free)
39
+
40
+ Layout transition animations using the FLIP technique (First, Last, Invert, Play):
41
+
42
+ ```typescript
43
+ 'use client';
44
+ import { useGSAP } from '@gsap/react';
45
+ import gsap from 'gsap';
46
+ import { Flip } from 'gsap/Flip';
47
+
48
+ function FilterableGrid({ items, filter }: { items: Item[]; filter: string }) {
49
+ const containerRef = useRef<HTMLDivElement>(null);
50
+
51
+ const applyFilter = (newFilter: string) => {
52
+ // 1. Capture current state
53
+ const state = Flip.getState('.grid-item');
54
+
55
+ // 2. Make DOM changes (reorder, show/hide)
56
+ setFilter(newFilter);
57
+
58
+ // 3. Animate from old positions to new
59
+ Flip.from(state, {
60
+ duration: 0.6,
61
+ ease: 'power2.out',
62
+ stagger: 0.04,
63
+ absolute: true, // prevents layout jumping during animation
64
+ onEnter: (elements) => gsap.fromTo(elements, { opacity: 0, scale: 0.8 }, { opacity: 1, scale: 1, duration: 0.4 }),
65
+ onLeave: (elements) => gsap.to(elements, { opacity: 0, scale: 0.8, duration: 0.3 }),
66
+ });
67
+ };
68
+ }
69
+ ```
70
+
71
+ Use cases: filterable grids, list reordering, expanding cards, shared element transitions.
72
+
73
+ ## SplitText (Premium)
74
+
75
+ Split text into chars, words, or lines for staggered reveals:
76
+
77
+ ```typescript
78
+ useGSAP(() => {
79
+ const split = SplitText.create('.hero-title', {
80
+ type: 'chars,words',
81
+ autoSplit: true, // re-splits on resize
82
+ onSplit: () => {
83
+ // Animate after split is ready
84
+ gsap.from(split.chars, {
85
+ y: 30,
86
+ opacity: 0,
87
+ stagger: 0.03,
88
+ duration: 0.5,
89
+ ease: 'power2.out',
90
+ });
91
+ },
92
+ });
93
+
94
+ // Cleanup: revert split on unmount (handled by useGSAP context)
95
+ }, { scope: containerRef });
96
+ ```
97
+
98
+ Key points:
99
+ - Use `autoSplit: true` for responsive -- re-splits when container resizes
100
+ - Use `onSplit` callback to animate AFTER split completes
101
+ - The split creates `<div>` wrappers -- this affects layout. Test carefully.
102
+ - useGSAP automatically reverts the split on unmount
103
+
104
+ ## MorphSVG (Premium)
105
+
106
+ Morph between SVG shapes:
107
+
108
+ ```typescript
109
+ gsap.to('#circle', {
110
+ morphSVG: {
111
+ shape: '#star',
112
+ type: 'rotational', // smoother for complex shapes
113
+ shapeIndex: 'auto', // auto-aligns anchor points
114
+ },
115
+ duration: 1.2,
116
+ ease: 'power2.inOut',
117
+ });
118
+
119
+ // Convert non-path SVG elements to paths first
120
+ MorphSVGPlugin.convertToPath('circle, rect, ellipse, polygon, polyline, line');
121
+ ```
122
+
123
+ Rules:
124
+ - Both shapes must be `<path>` elements (use convertToPath for others)
125
+ - Match point counts for smoother morphs
126
+ - `type: "rotational"` is smoother for most shapes
127
+
128
+ ## DrawSVG (Premium)
129
+
130
+ Animate SVG stroke drawing:
131
+
132
+ ```typescript
133
+ // Draw stroke from 0% to 100%
134
+ gsap.fromTo('.svg-path',
135
+ { drawSVG: '0%' },
136
+ { drawSVG: '100%', duration: 2, ease: 'power2.inOut' }
137
+ );
138
+
139
+ // Partial draws
140
+ gsap.to('.line', { drawSVG: '20% 80%', duration: 1 }); // middle section only
141
+
142
+ // Line drawing reveal pattern
143
+ const paths = gsap.utils.toArray<SVGPathElement>('.icon path');
144
+ gsap.from(paths, {
145
+ drawSVG: '0%',
146
+ stagger: 0.2,
147
+ duration: 1,
148
+ ease: 'power1.out',
149
+ });
150
+ ```
151
+
152
+ Requires SVG elements with visible `stroke` and `stroke-width`. Does not work on `fill`.
153
+
154
+ ## MotionPath (Premium)
155
+
156
+ Animate elements along SVG paths:
157
+
158
+ ```typescript
159
+ gsap.to('.rocket', {
160
+ motionPath: {
161
+ path: '#flight-path',
162
+ align: '#flight-path',
163
+ autoRotate: true, // element rotates to follow path direction
164
+ alignOrigin: [0.5, 0.5], // center of element on path
165
+ },
166
+ duration: 3,
167
+ ease: 'power1.inOut',
168
+ });
169
+ ```
170
+
171
+ Use MotionPathHelper (dev tool) to visually edit paths: `MotionPathHelper.create('.rocket')`.
172
+
173
+ ## ScrollToPlugin (Free)
174
+
175
+ Smooth scroll to elements or positions:
176
+
177
+ ```typescript
178
+ // Scroll to element
179
+ gsap.to(window, {
180
+ scrollTo: { y: '#contact-section', offsetY: 80 }, // 80px offset for header
181
+ duration: 1,
182
+ ease: 'power2.inOut',
183
+ });
184
+
185
+ // Scroll to position
186
+ gsap.to(window, { scrollTo: { y: 500 }, duration: 0.8 });
187
+
188
+ // Auto-kill on user scroll (prevents fighting user input)
189
+ gsap.to(window, {
190
+ scrollTo: { y: '#section', autoKill: true },
191
+ duration: 1.5,
192
+ });
193
+ ```
194
+
195
+ ## Draggable (Premium)
196
+
197
+ Make elements draggable with optional physics:
198
+
199
+ ```typescript
200
+ 'use client';
201
+ import { useGSAP } from '@gsap/react';
202
+ import gsap from 'gsap';
203
+ import { Draggable } from 'gsap/Draggable';
204
+
205
+ function DraggableCard() {
206
+ const cardRef = useRef<HTMLDivElement>(null);
207
+
208
+ useGSAP(() => {
209
+ Draggable.create(cardRef.current, {
210
+ type: 'x,y',
211
+ bounds: '.container',
212
+ edgeResistance: 0.65,
213
+ throwProps: true, // momentum (requires InertiaPlugin)
214
+ snap: {
215
+ x: (value) => Math.round(value / 100) * 100, // snap to 100px grid
216
+ y: (value) => Math.round(value / 100) * 100,
217
+ },
218
+ onDrag() { /* called every frame during drag */ },
219
+ onDragEnd() { /* called when drag completes */ },
220
+ });
221
+ }, { scope: cardRef });
222
+ }
223
+ ```
224
+
225
+ ## Observer (Free)
226
+
227
+ Normalize pointer, scroll, touch, and wheel events:
228
+
229
+ ```typescript
230
+ import { Observer } from 'gsap/Observer';
231
+
232
+ Observer.create({
233
+ target: window,
234
+ type: 'wheel,touch,pointer',
235
+ onUp: () => goToSection(currentIndex - 1),
236
+ onDown: () => goToSection(currentIndex + 1),
237
+ tolerance: 10, // minimum distance before triggering
238
+ preventDefault: true, // prevent native scroll
239
+ wheelSpeed: -1, // invert direction
240
+ });
241
+ ```
242
+
243
+ Use cases: custom scroll-jacking, swipe detection, directional gestures, fullpage scroll experiences.
244
+
245
+ ## Combining Plugins — Real-World Recipes
246
+
247
+ **Recipe 1: Scroll-triggered text reveal**
248
+
249
+ ```typescript
250
+ useGSAP(() => {
251
+ const split = SplitText.create('.reveal-text', { type: 'words', autoSplit: true });
252
+
253
+ gsap.from(split.words, {
254
+ y: 20,
255
+ opacity: 0,
256
+ stagger: 0.05,
257
+ duration: 0.5,
258
+ ease: 'power2.out',
259
+ scrollTrigger: {
260
+ trigger: '.reveal-text',
261
+ start: 'top 80%',
262
+ toggleActions: 'play none none none',
263
+ },
264
+ });
265
+ }, { scope: containerRef });
266
+ ```
267
+
268
+ **Recipe 2: Filterable grid with Flip + ScrollTrigger.refresh()**
269
+
270
+ ```typescript
271
+ const handleFilter = (category: string) => {
272
+ const state = Flip.getState('.item');
273
+ setActiveCategory(category);
274
+
275
+ Flip.from(state, {
276
+ duration: 0.5,
277
+ stagger: 0.03,
278
+ absolute: true,
279
+ onComplete: () => ScrollTrigger.refresh(), // recalculate positions after layout change
280
+ });
281
+ };
282
+ ```
283
+
284
+ **Recipe 3: SVG icon draw + morph sequence**
285
+
286
+ ```typescript
287
+ const tl = gsap.timeline({ scrollTrigger: { trigger: '.icon-section', start: 'top center' } });
288
+ tl.from('.icon path', { drawSVG: '0%', stagger: 0.1, duration: 0.8 })
289
+ .to('.icon path', { morphSVG: '#final-shape', duration: 1, ease: 'power2.inOut' }, '+=0.3');
290
+ ```
291
+
292
+ ---
293
+
294
+ *MORPH-SPEC by Polymorphism Tech*
@@ -0,0 +1,318 @@
1
+ ---
2
+ scope: frontend-nextjs-gsap
3
+ layer: 3
4
+ keywords: scrolltrigger, scroll-animation, pinning, scrubbing, parallax, scroll-driven, gsap
5
+ load_when: gsap-animator active
6
+ ---
7
+
8
+ # GSAP ScrollTrigger — Next.js
9
+
10
+ Reference guide for implementing scroll-driven animations in Next.js projects using GSAP ScrollTrigger. Covers plugin setup, scrubbing, pinning, batch processing, responsive breakpoints, SSR considerations, and mandatory cleanup patterns.
11
+
12
+ ---
13
+
14
+ ## Section 1: Plugin Registration
15
+
16
+ ```typescript
17
+ 'use client';
18
+ import gsap from 'gsap';
19
+ import { ScrollTrigger } from 'gsap/ScrollTrigger';
20
+
21
+ gsap.registerPlugin(ScrollTrigger);
22
+ ```
23
+
24
+ Register ONCE at app init. In Next.js, do this in a client-side providers file or at the top of client components. Never register inside `useEffect` or loops — repeated registration is wasted work and can cause subtle timing issues.
25
+
26
+ ---
27
+
28
+ ## Section 2: Basic ScrollTrigger
29
+
30
+ The fundamental pattern: attach a `scrollTrigger` object to any GSAP tween.
31
+
32
+ ```typescript
33
+ useGSAP(() => {
34
+ gsap.from('.card', {
35
+ y: 60,
36
+ opacity: 0,
37
+ duration: 0.8,
38
+ ease: 'power2.out',
39
+ stagger: 0.15,
40
+ scrollTrigger: {
41
+ trigger: '.cards-container',
42
+ start: 'top 80%', // trigger-top hits viewport-80%
43
+ end: 'bottom 20%',
44
+ toggleActions: 'play none none none', // onEnter onLeave onEnterBack onLeaveBack
45
+ markers: false, // true for debugging only
46
+ },
47
+ });
48
+ }, { scope: containerRef });
49
+ ```
50
+
51
+ **start/end syntax:** `"triggerPosition viewportPosition"`.
52
+
53
+ | Value | Meaning |
54
+ |-------|---------|
55
+ | `"top center"` | Trigger element's top reaches viewport center |
56
+ | `"top 80%"` | Trigger element's top reaches 80% down the viewport |
57
+ | `"top top"` | Trigger element's top reaches viewport top |
58
+ | `"bottom top"` | Trigger element's bottom reaches viewport top |
59
+
60
+ **toggleActions** accepts four space-separated values for `onEnter`, `onLeave`, `onEnterBack`, `onLeaveBack`. Each value can be: `play`, `pause`, `resume`, `reverse`, `restart`, `reset`, `complete`, `none`.
61
+
62
+ ---
63
+
64
+ ## Section 3: Scrubbing
65
+
66
+ Link animation progress directly to scroll position instead of playing on a timeline.
67
+
68
+ ```typescript
69
+ gsap.to('.parallax-bg', {
70
+ y: -200,
71
+ ease: 'none', // linear for scrub
72
+ scrollTrigger: {
73
+ trigger: '.hero-section',
74
+ start: 'top top',
75
+ end: 'bottom top',
76
+ scrub: true, // direct link (can feel jumpy)
77
+ // scrub: 0.5, // smooth scrub with 0.5s delay
78
+ },
79
+ });
80
+ ```
81
+
82
+ **Rule:** Use `ease: "none"` with scrub for linear scroll-to-progress mapping. Non-linear eases with scrub cause unexpected acceleration/deceleration that fights the user's scroll intent.
83
+
84
+ - `scrub: true` — instant 1:1 link between scroll and progress.
85
+ - `scrub: 0.5` — smoothed with 0.5 second catch-up delay. Use values between 0.3 and 1 for production.
86
+ - `scrub: 3` — heavy smoothing, good for background parallax layers.
87
+
88
+ ---
89
+
90
+ ## Section 4: Pinning
91
+
92
+ Pin an element in place while the user scrolls through content.
93
+
94
+ ```typescript
95
+ gsap.to('.pinned-content', {
96
+ x: '-300%', // horizontal scroll simulation
97
+ ease: 'none',
98
+ scrollTrigger: {
99
+ trigger: '.horizontal-section',
100
+ start: 'top top',
101
+ end: '+=3000', // pin for 3000px of scroll
102
+ pin: true,
103
+ scrub: 1,
104
+ anticipatePin: 1, // prevents jank on pin start
105
+ // pinSpacing: true, // default: adds space for pinned section
106
+ },
107
+ });
108
+ ```
109
+
110
+ **Horizontal scroll pattern:** Pin the outer container, then translate an inner track of panels by `-100% * (panelCount - 1)` using scrub. The `end: "+=Npx"` controls how much vertical scroll maps to the horizontal distance.
111
+
112
+ ```typescript
113
+ useGSAP(() => {
114
+ const panels = gsap.utils.toArray<HTMLElement>('.panel');
115
+
116
+ gsap.to(panels, {
117
+ xPercent: -100 * (panels.length - 1),
118
+ ease: 'none',
119
+ scrollTrigger: {
120
+ trigger: '.horizontal-wrapper',
121
+ start: 'top top',
122
+ end: () => `+=${panels.length * window.innerWidth}`,
123
+ pin: true,
124
+ scrub: 1,
125
+ snap: 1 / (panels.length - 1),
126
+ anticipatePin: 1,
127
+ },
128
+ });
129
+ }, { scope: containerRef });
130
+ ```
131
+
132
+ **Pinning rules:**
133
+ - Set `anticipatePin: 1` to avoid visual jank when pinning starts.
134
+ - `pinSpacing: false` prevents the extra space insertion — use only when the pinned section overlaps subsequent content intentionally.
135
+ - Never nest pinned elements inside other pinned elements.
136
+
137
+ ---
138
+
139
+ ## Section 5: Batch Processing
140
+
141
+ For staggered reveals on lists and grids, use `ScrollTrigger.batch` instead of individual triggers.
142
+
143
+ ```typescript
144
+ ScrollTrigger.batch('.grid-item', {
145
+ onEnter: (elements) => {
146
+ gsap.from(elements, {
147
+ y: 40,
148
+ opacity: 0,
149
+ stagger: 0.08,
150
+ duration: 0.6,
151
+ ease: 'power2.out',
152
+ });
153
+ },
154
+ start: 'top 85%',
155
+ });
156
+ ```
157
+
158
+ Use batch instead of individual ScrollTriggers for performance — one IntersectionObserver watches all matched elements instead of creating dozens of separate scroll listeners. This matters significantly for grids with 20+ items.
159
+
160
+ Batch also supports `onLeave`, `onEnterBack`, and `onLeaveBack` callbacks with the same signature.
161
+
162
+ ---
163
+
164
+ ## Section 6: Scroll-linked Progress
165
+
166
+ Use `onUpdate` for custom progress-based effects that go beyond simple tweens.
167
+
168
+ ```typescript
169
+ ScrollTrigger.create({
170
+ trigger: '.progress-section',
171
+ start: 'top center',
172
+ end: 'bottom center',
173
+ onUpdate: (self) => {
174
+ // self.progress is 0-1
175
+ gsap.set('.progress-bar', { scaleX: self.progress });
176
+ gsap.set('.counter', { innerText: Math.round(self.progress * 100) + '%' });
177
+ },
178
+ });
179
+ ```
180
+
181
+ **Parallax layers pattern:** Assign different scroll speeds to layered elements using a `data-depth` attribute.
182
+
183
+ ```typescript
184
+ gsap.utils.toArray<HTMLElement>('.parallax-layer').forEach((layer) => {
185
+ const depth = parseFloat(layer.dataset.depth || '0.5');
186
+ gsap.to(layer, {
187
+ y: -200 * depth,
188
+ ease: 'none',
189
+ scrollTrigger: {
190
+ trigger: layer.parentElement,
191
+ start: 'top bottom',
192
+ end: 'bottom top',
193
+ scrub: true,
194
+ },
195
+ });
196
+ });
197
+ ```
198
+
199
+ Layers with `data-depth="1"` move the full -200px, while `data-depth="0.2"` moves only -40px, creating natural depth.
200
+
201
+ ---
202
+
203
+ ## Section 7: Snap
204
+
205
+ Snap scrolling to discrete sections for a full-page slide experience.
206
+
207
+ ```typescript
208
+ ScrollTrigger.create({
209
+ trigger: '.sections-container',
210
+ start: 'top top',
211
+ end: 'bottom bottom',
212
+ snap: {
213
+ snapTo: 1 / (sectionCount - 1), // snap to each section
214
+ duration: { min: 0.2, max: 0.6 },
215
+ ease: 'power1.inOut',
216
+ },
217
+ });
218
+ ```
219
+
220
+ - `snapTo` accepts a number (interval), an array of progress values, or a function `(value) => snappedValue`.
221
+ - `duration` can be a fixed number or `{ min, max }` range — GSAP picks based on scroll velocity.
222
+ - Keep snap durations short (under 0.8s) to avoid fighting user intent.
223
+ - Combine with `pin: true` for pinned section snapping.
224
+
225
+ ---
226
+
227
+ ## Section 8: Responsive
228
+
229
+ Use `ScrollTrigger.matchMedia()` for breakpoint-aware animations.
230
+
231
+ ```typescript
232
+ ScrollTrigger.matchMedia({
233
+ // desktop
234
+ '(min-width: 768px)': () => {
235
+ gsap.to('.sidebar', {
236
+ scrollTrigger: { trigger: '.content', pin: true, scrub: true },
237
+ y: -100,
238
+ });
239
+ },
240
+ // mobile — different or no animation
241
+ '(max-width: 767px)': () => {
242
+ gsap.set('.sidebar', { clearProps: 'all' });
243
+ },
244
+ // all: always active
245
+ all: () => {
246
+ // common setup shared across breakpoints
247
+ },
248
+ });
249
+ ```
250
+
251
+ Animations created inside each breakpoint callback are automatically reverted when the media query no longer matches, then re-created if the query matches again. This prevents stale transforms from desktop persisting on mobile after a resize.
252
+
253
+ **Rules:**
254
+ - Always define both desktop and mobile cases explicitly. Omitting mobile leaves desktop transforms applied.
255
+ - Place `matchMedia` inside `useGSAP` so it is cleaned up on unmount.
256
+ - Avoid overlapping breakpoint ranges — use exclusive boundaries (767px / 768px, not both at 768px).
257
+
258
+ ---
259
+
260
+ ## Section 9: Next.js SSR Considerations
261
+
262
+ ScrollTrigger relies on the DOM, `window`, and scroll position — it cannot run server-side.
263
+
264
+ **Mandatory rules for Next.js:**
265
+
266
+ 1. All ScrollTrigger code must live in `'use client'` components.
267
+ 2. Use the `useGSAP()` hook, which only executes client-side after mount. Never use bare `useEffect` for GSAP unless wrapping in `gsap.context()`.
268
+ 3. Call `ScrollTrigger.refresh()` after any event that changes layout dimensions:
269
+ - Dynamic content loads (API data arriving, skeleton replaced with real content)
270
+ - Layout shifts (accordion opens, tab changes, collapsible sections)
271
+ - Route changes in Next.js App Router
272
+ - Images finishing load
273
+ 4. For Next.js Image components, use the `onLoad` callback to trigger a refresh:
274
+
275
+ ```typescript
276
+ <Image
277
+ src="/hero.jpg"
278
+ alt="Hero"
279
+ onLoad={() => ScrollTrigger.refresh()}
280
+ />
281
+ ```
282
+
283
+ 5. Use `ScrollTrigger.normalizeScroll(true)` for consistent cross-browser scroll behavior. This is particularly useful on iOS Safari where elastic bouncing and address bar resizing interfere with scroll calculations.
284
+ 6. Avoid `ScrollTrigger.create()` at module scope — always place it inside `useGSAP` or a `useEffect` to guarantee the DOM exists.
285
+
286
+ ---
287
+
288
+ ## Section 10: Cleanup
289
+
290
+ ```typescript
291
+ // Automatic with useGSAP — kills all animations and ScrollTriggers created in scope
292
+ useGSAP(() => {
293
+ // all ScrollTriggers here are auto-cleaned on unmount
294
+ }, { scope: containerRef });
295
+
296
+ // Manual cleanup (if not using useGSAP):
297
+ useEffect(() => {
298
+ const ctx = gsap.context(() => {
299
+ // animations + ScrollTriggers
300
+ }, containerRef);
301
+ return () => ctx.revert(); // kills everything
302
+ }, []);
303
+ ```
304
+
305
+ **CRITICAL:** Always clean up ScrollTriggers on unmount. Orphaned ScrollTriggers cause memory leaks, stale callbacks, and broken scroll behavior on subsequent route navigations.
306
+
307
+ **Refresh priority** for ordered triggers — higher priority triggers refresh first:
308
+
309
+ ```typescript
310
+ ScrollTrigger.create({ trigger: '.first', refreshPriority: 1 });
311
+ ScrollTrigger.create({ trigger: '.second', refreshPriority: -1 });
312
+ ```
313
+
314
+ Use `refreshPriority` when one trigger's position depends on another's pinning or layout effect. The default priority is 0; higher values refresh first.
315
+
316
+ ---
317
+
318
+ *MORPH-SPEC by Polymorphism Tech*
@@ -299,6 +299,7 @@ This CSS fallback catches animations outside Framer Motion (Tailwind `animate-*`
299
299
  | Forgetting `animation-fill-mode: backwards` in stagger | Will cause a flash before the animation starts |
300
300
  | `AnimatePresence` without `key` changes | Exit animations will never fire |
301
301
  | Using `framer-motion` for skeleton loaders | Use CSS `animate-pulse` — lower performance cost |
302
+ | Need ScrollTrigger, SVG morph, or complex timelines | Use GSAP — see `frontend/gsap/gsap-core.md` |
302
303
 
303
304
  ---
304
305