<< All versions
Skill v1.0.0
currentLLM-judged scan90/100martinholovsky/claude-skills-generator/gsap
──Details
PublishedOctober 20, 2026 at 12:28 PM
Content Hashsha256:c357814ef94eb27e...
Git SHA19ff50596448
──Files
Files (1 file, 14.7 KB)
SKILL.md14.7 KBactive
SKILL.md · 685 lines · 14.7 KB
name: gsap description: GSAP animations for JARVIS HUD transitions and effects model: sonnet risk_level: LOW version: 1.0.0
GSAP Animation Skill
File Organization: This skill uses split structure. Seereferences/for advanced patterns.
1. Overview
This skill provides GSAP (GreenSock Animation Platform) expertise for creating smooth, professional animations in the JARVIS AI Assistant HUD.
Risk Level: LOW - Animation library with minimal security surface
Primary Use Cases:
- HUD panel entrance/exit animations
- Status indicator transitions
- Data visualization animations
- Scroll-triggered effects
- Complex timeline sequences
2. Core Responsibilities
2.1 Fundamental Principles
- TDD First: Write animation tests before implementation
- Performance Aware: Use transforms/opacity for GPU acceleration, avoid layout thrashing
- Cleanup Required: Always kill animations on component unmount
- Timeline Organization: Use timelines for complex sequences
- Easing Selection: Choose appropriate easing for HUD feel
- Accessibility: Respect reduced motion preferences
- Memory Management: Avoid memory leaks with proper cleanup
2.5 Implementation Workflow (TDD)
Step 1: Write Failing Test First
typescript
// tests/animations/panel-animation.test.tsimport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'import { mount } from '@vue/test-utils'import { gsap } from 'gsap'import HUDPanel from '~/components/HUDPanel.vue'describe('HUDPanel Animation', () => {beforeEach(() => {// Mock reduced motionObject.defineProperty(window, 'matchMedia', {writable: true,value: vi.fn().mockImplementation(query => ({matches: false,media: query}))})})afterEach(() => {// Verify cleanupgsap.globalTimeline.clear()})it('animates panel entrance with correct properties', async () => {const wrapper = mount(HUDPanel)// Wait for animation to completeawait new Promise(resolve => setTimeout(resolve, 600))const panel = wrapper.find('.hud-panel')expect(panel.exists()).toBe(true)})it('cleans up animations on unmount', async () => {const wrapper = mount(HUDPanel)const childCount = gsap.globalTimeline.getChildren().lengthawait wrapper.unmount()// All animations should be killedexpect(gsap.globalTimeline.getChildren().length).toBeLessThan(childCount)})it('respects reduced motion preference', async () => {// Mock reduced motion enabledwindow.matchMedia = vi.fn().mockImplementation(() => ({matches: true}))const wrapper = mount(HUDPanel)const panel = wrapper.find('.hud-panel').element// Should set final state immediately without animationexpect(gsap.getProperty(panel, 'opacity')).toBe(1)})})
Step 2: Implement Minimum to Pass
typescript
// components/HUDPanel.vue - implement animation logicconst animation = ref<gsap.core.Tween | null>(null)onMounted(() => {if (!panelRef.value) returnif (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {gsap.set(panelRef.value, { opacity: 1 })return}animation.value = gsap.from(panelRef.value, {opacity: 0,y: 20,duration: 0.5})})onUnmounted(() => {animation.value?.kill()})
Step 3: Refactor Following Patterns
typescript
// Extract to composable for reusabilityexport function usePanelAnimation(elementRef: Ref<HTMLElement | null>) {const animation = ref<gsap.core.Tween | null>(null)const animate = () => {if (!elementRef.value) returnif (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {gsap.set(elementRef.value, { opacity: 1 })return}animation.value = gsap.from(elementRef.value, {opacity: 0,y: 20,duration: 0.5,ease: 'power2.out'})}onMounted(animate)onUnmounted(() => animation.value?.kill())return { animation }}
Step 4: Run Full Verification
bash
# Run animation testsnpm test -- --grep "Animation"# Check for memory leaksnpm run test:memory# Verify 60fps performancenpm run test:performance
3. Technology Stack & Versions
3.1 Recommended Versions
| Package | Version | Notes | |
|---|---|---|---|
| gsap | ^3.12.0 | Core library | |
| @gsap/vue | ^3.12.0 | Vue integration | |
| ScrollTrigger | included | Scroll effects |
3.2 Vue Integration
typescript
// plugins/gsap.tsimport { gsap } from 'gsap'import { ScrollTrigger } from 'gsap/ScrollTrigger'export default defineNuxtPlugin(() => {gsap.registerPlugin(ScrollTrigger)return {provide: {gsap,ScrollTrigger}}})
4. Implementation Patterns
4.1 Panel Entrance Animation
vue
<script setup lang="ts">import { gsap } from 'gsap'import { onMounted, onUnmounted, ref } from 'vue'const panelRef = ref<HTMLElement | null>(null)let animation: gsap.core.Tween | null = nullonMounted(() => {if (!panelRef.value) return// ✅ Check reduced motion preferenceif (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {gsap.set(panelRef.value, { opacity: 1 })return}animation = gsap.from(panelRef.value, {opacity: 0,y: 20,scale: 0.95,duration: 0.5,ease: 'power2.out'})})// ✅ Cleanup on unmountonUnmounted(() => {animation?.kill()})</script><template><div ref="panelRef" class="hud-panel"><slot /></div></template>
4.2 Status Indicator Animation
typescript
// composables/useStatusAnimation.tsimport { gsap } from 'gsap'export function useStatusAnimation(element: Ref<HTMLElement | null>) {const timeline = ref<gsap.core.Timeline | null>(null)const animateStatus = (status: string) => {if (!element.value) returntimeline.value?.kill()timeline.value = gsap.timeline()switch (status) {case 'active':timeline.value.to(element.value, {scale: 1.2,duration: 0.2,ease: 'power2.out'}).to(element.value, {scale: 1,duration: 0.3,ease: 'elastic.out(1, 0.3)'})breakcase 'warning':timeline.value.to(element.value, {backgroundColor: '#f59e0b',boxShadow: '0 0 10px #f59e0b',duration: 0.3,repeat: 2,yoyo: true})breakcase 'error':timeline.value.to(element.value, {x: -5,duration: 0.05,repeat: 5,yoyo: true})break}}onUnmounted(() => {timeline.value?.kill()})return { animateStatus }}
4.3 Data Visualization Animation
vue
<script setup lang="ts">import { gsap } from 'gsap'const props = defineProps<{data: number[]}>()const barsRef = ref<HTMLElement[]>([])let animations: gsap.core.Tween[] = []watch(() => props.data, (newData) => {// Kill previous animationsanimations.forEach(a => a.kill())animations = []// Animate each barnewData.forEach((value, index) => {const bar = barsRef.value[index]if (!bar) returnconst tween = gsap.to(bar, {height: `${value}%`,duration: 0.5,delay: index * 0.05,ease: 'power2.out'})animations.push(tween)})}, { immediate: true })onUnmounted(() => {animations.forEach(a => a.kill())})</script><template><div class="flex items-end h-40 gap-1"><divv-for="(_, index) in data":key="index"ref="barsRef"class="w-4 bg-jarvis-primary"/></div></template>
4.4 Timeline Sequence
typescript
// Create complex HUD startup sequenceexport function createStartupSequence(elements: {logo: HTMLElementpanels: HTMLElement[]status: HTMLElement}): gsap.core.Timeline {const tl = gsap.timeline({defaults: { ease: 'power2.out' }})// Logo revealtl.from(elements.logo, {opacity: 0,scale: 0,duration: 0.8,ease: 'back.out(1.7)'})// Panels stagger intl.from(elements.panels, {opacity: 0,x: -30,stagger: 0.1,duration: 0.5}, '-=0.3')// Status indicatortl.from(elements.status, {opacity: 0,y: 10,duration: 0.3}, '-=0.2')return tl}
4.5 Scroll-Triggered Animation
vue
<script setup lang="ts">import { gsap } from 'gsap'import { ScrollTrigger } from 'gsap/ScrollTrigger'const sectionRef = ref<HTMLElement | null>(null)onMounted(() => {if (!sectionRef.value) returngsap.from(sectionRef.value.querySelectorAll('.animate-item'), {scrollTrigger: {trigger: sectionRef.value,start: 'top 80%',end: 'bottom 20%',toggleActions: 'play none none reverse'},opacity: 0,y: 30,stagger: 0.1,duration: 0.5})})onUnmounted(() => {ScrollTrigger.getAll().forEach(trigger => trigger.kill())})</script>
5. Quality Standards
5.1 Performance
typescript
// ✅ GOOD - Use transforms for GPU accelerationgsap.to(element, {x: 100,y: 50,rotation: 45,scale: 1.2})// ❌ BAD - Triggers layout recalculationgsap.to(element, {left: 100,top: 50,width: '120%'})
5.2 Accessibility
typescript
// ✅ Respect reduced motionconst prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matchesif (prefersReducedMotion) {gsap.set(element, { opacity: 1 })} else {gsap.from(element, { opacity: 0, duration: 0.5 })}
6. Performance Patterns
6.1 will-change Property Usage
typescript
// Good: Apply will-change before animationconst animatePanel = (element: HTMLElement) => {element.style.willChange = 'transform, opacity'gsap.to(element, {x: 100,opacity: 0.8,duration: 0.5,onComplete: () => {element.style.willChange = 'auto'}})}// Bad: Never removing will-changeconst animatePanelBad = (element: HTMLElement) => {element.style.willChange = 'transform, opacity' // Memory leak!gsap.to(element, { x: 100, opacity: 0.8 })}
6.2 Transform vs Layout Properties
typescript
// Good: Use transforms (GPU accelerated)gsap.to(element, {x: 100, // translateXy: 50, // translateYscale: 1.2, // scalerotation: 45, // rotateopacity: 0.5 // opacity})// Bad: Layout-triggering properties (CPU, causes reflow)gsap.to(element, {left: 100, // Triggers layouttop: 50, // Triggers layoutwidth: '120%', // Triggers layoutheight: 200, // Triggers layoutmargin: 10 // Triggers layout})
6.3 Timeline Reuse
typescript
// Good: Reuse timeline instanceconst timeline = gsap.timeline({ paused: true })timeline.to(element, { opacity: 1, duration: 0.3 }).to(element, { y: -20, duration: 0.5 })// Play/reverse as neededconst show = () => timeline.play()const hide = () => timeline.reverse()// Bad: Creating new timeline each timeconst showBad = () => {gsap.timeline().to(element, { opacity: 1, duration: 0.3 }).to(element, { y: -20, duration: 0.5 })}
6.4 ScrollTrigger Batching
typescript
// Good: Batch ScrollTrigger animationsScrollTrigger.batch('.animate-item', {onEnter: (elements) => {gsap.to(elements, {opacity: 1,y: 0,stagger: 0.1,overwrite: true})},onLeave: (elements) => {gsap.to(elements, {opacity: 0,y: -20,overwrite: true})}})// Bad: Individual ScrollTrigger per elementdocument.querySelectorAll('.animate-item').forEach(item => {gsap.to(item, {scrollTrigger: {trigger: item,start: 'top 80%'},opacity: 1,y: 0})})
6.5 Lazy Initialization
typescript
// Good: Initialize animations only when neededlet panelAnimation: gsap.core.Timeline | null = nullconst getPanelAnimation = () => {if (!panelAnimation) {panelAnimation = gsap.timeline({ paused: true }).from('.panel', { opacity: 0, y: 20 }).from('.panel-content', { opacity: 0, stagger: 0.1 })}return panelAnimation}const showPanel = () => getPanelAnimation().play()const hidePanel = () => getPanelAnimation().reverse()// Bad: Initialize all animations on mountonMounted(() => {// Creates timeline even if never usedconst animation1 = gsap.timeline().to('.panel1', { x: 100 })const animation2 = gsap.timeline().to('.panel2', { y: 100 })const animation3 = gsap.timeline().to('.panel3', { scale: 1.2 })})
7. Testing & Quality
7.1 Animation Testing
typescript
describe('Panel Animation', () => {it('cleans up on unmount', async () => {const wrapper = mount(HUDPanel)await wrapper.unmount()// No active GSAP animations should remainexpect(gsap.globalTimeline.getChildren().length).toBe(0)})})
8. Common Mistakes & Anti-Patterns
8.1 Critical Anti-Patterns
Never: Skip Cleanup
typescript
// ❌ MEMORY LEAKonMounted(() => {gsap.to(element, { x: 100, duration: 1 })})// ✅ PROPER CLEANUPlet tween: gsap.core.TweenonMounted(() => {tween = gsap.to(element, { x: 100, duration: 1 })})onUnmounted(() => {tween?.kill()})
Never: Animate Layout Properties
typescript
// ❌ BAD - Causes layout thrashinggsap.to(element, { width: 200, height: 100 })// ✅ GOOD - Use transformsgsap.to(element, { scaleX: 2, scaleY: 1 })
13. Pre-Implementation Checklist
Phase 1: Before Writing Code
- [ ] Write failing tests for animation behavior
- [ ] Define animation timing and easing requirements
- [ ] Identify elements that need will-change hints
- [ ] Plan cleanup strategy for all animations
- [ ] Check if reduced motion support is needed
Phase 2: During Implementation
- [ ] Use transforms/opacity only (no layout properties)
- [ ] Store animation references for cleanup
- [ ] Apply will-change before, remove after animation
- [ ] Use timelines for sequences
- [ ] Batch ScrollTrigger animations
- [ ] Implement lazy initialization for complex animations
Phase 3: Before Committing
- [ ] All tests pass (npm test -- --grep "Animation")
- [ ] All animations cleaned up on unmount
- [ ] Reduced motion preference respected
- [ ] No memory leaks (check with DevTools)
- [ ] 60fps maintained (test with performance monitor)
- [ ] ScrollTrigger instances properly killed
14. Summary
GSAP provides professional animations for JARVIS HUD:
- Cleanup: Always kill animations on unmount
- Performance: Use transforms and opacity only
- Accessibility: Respect reduced motion preference
- Organization: Use timelines for sequences
Remember: Every animation must be cleaned up to prevent memory leaks.
References:
references/advanced-patterns.md- Complex animation patterns