agentskills.codes

GSAP animations for JARVIS HUD transitions and effects

Install

mkdir -p .claude/skills/gsap-yahav123147 && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16080" && unzip -o skill.zip -d .claude/skills/gsap-yahav123147 && rm skill.zip

Installs to .claude/skills/gsap-yahav123147

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

GSAP animations for JARVIS HUD transitions and effects
54 charsno explicit “when” trigger

About this skill

GSAP Animation Skill

File Organization: This skill uses split structure. See references/ 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

  1. TDD First: Write animation tests before implementation
  2. Performance Aware: Use transforms/opacity for GPU acceleration, avoid layout thrashing
  3. Cleanup Required: Always kill animations on component unmount
  4. Timeline Organization: Use timelines for complex sequences
  5. Easing Selection: Choose appropriate easing for HUD feel
  6. Accessibility: Respect reduced motion preferences
  7. Memory Management: Avoid memory leaks with proper cleanup

2.5 Implementation Workflow (TDD)

Step 1: Write Failing Test First

// tests/animations/panel-animation.test.ts
import { 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 motion
    Object.defineProperty(window, 'matchMedia', {
      writable: true,
      value: vi.fn().mockImplementation(query => ({
        matches: false,
        media: query
      }))
    })
  })

  afterEach(() => {
    // Verify cleanup
    gsap.globalTimeline.clear()
  })

  it('animates panel entrance with correct properties', async () => {
    const wrapper = mount(HUDPanel)

    // Wait for animation to complete
    await 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().length

    await wrapper.unmount()

    // All animations should be killed
    expect(gsap.globalTimeline.getChildren().length).toBeLessThan(childCount)
  })

  it('respects reduced motion preference', async () => {
    // Mock reduced motion enabled
    window.matchMedia = vi.fn().mockImplementation(() => ({
      matches: true
    }))

    const wrapper = mount(HUDPanel)
    const panel = wrapper.find('.hud-panel').element

    // Should set final state immediately without animation
    expect(gsap.getProperty(panel, 'opacity')).toBe(1)
  })
})

Step 2: Implement Minimum to Pass

// components/HUDPanel.vue - implement animation logic
const animation = ref<gsap.core.Tween | null>(null)

onMounted(() => {
  if (!panelRef.value) return

  if (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

// Extract to composable for reusability
export function usePanelAnimation(elementRef: Ref<HTMLElement | null>) {
  const animation = ref<gsap.core.Tween | null>(null)

  const animate = () => {
    if (!elementRef.value) return

    if (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

# Run animation tests
npm test -- --grep "Animation"

# Check for memory leaks
npm run test:memory

# Verify 60fps performance
npm run test:performance

3. Technology Stack & Versions

3.1 Recommended Versions

PackageVersionNotes
gsap^3.12.0Core library
@gsap/vue^3.12.0Vue integration
ScrollTriggerincludedScroll effects

3.2 Vue Integration

// plugins/gsap.ts
import { 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

<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 = null

onMounted(() => {
  if (!panelRef.value) return

  // ✅ Check reduced motion preference
  if (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 unmount
onUnmounted(() => {
  animation?.kill()
})
</script>

<template>
  <div ref="panelRef" class="hud-panel">
    <slot />
  </div>
</template>

4.2 Status Indicator Animation

// composables/useStatusAnimation.ts
import { 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) return

    timeline.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)'
          })
        break

      case 'warning':
        timeline.value.to(element.value, {
          backgroundColor: '#f59e0b',
          boxShadow: '0 0 10px #f59e0b',
          duration: 0.3,
          repeat: 2,
          yoyo: true
        })
        break

      case '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

<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 animations
  animations.forEach(a => a.kill())
  animations = []

  // Animate each bar
  newData.forEach((value, index) => {
    const bar = barsRef.value[index]
    if (!bar) return

    const 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">
    <div
      v-for="(_, index) in data"
      :key="index"
      ref="barsRef"
      class="w-4 bg-jarvis-primary"
    />
  </div>
</template>

4.4 Timeline Sequence

// Create complex HUD startup sequence
export function createStartupSequence(elements: {
  logo: HTMLElement
  panels: HTMLElement[]
  status: HTMLElement
}): gsap.core.Timeline {
  const tl = gsap.timeline({
    defaults: { ease: 'power2.out' }
  })

  // Logo reveal
  tl.from(elements.logo, {
    opacity: 0,
    scale: 0,
    duration: 0.8,
    ease: 'back.out(1.7)'
  })

  // Panels stagger in
  tl.from(elements.panels, {
    opacity: 0,
    x: -30,
    stagger: 0.1,
    duration: 0.5
  }, '-=0.3')

  // Status indicator
  tl.from(elements.status, {
    opacity: 0,
    y: 10,
    duration: 0.3
  }, '-=0.2')

  return tl
}

4.5 Scroll-Triggered Animation

<script setup lang="ts">
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'

const sectionRef = ref<HTMLElement | null>(null)

onMounted(() => {
  if (!sectionRef.value) return

  gsap.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

// ✅ GOOD - Use transforms for GPU acceleration
gsap.to(element, {
  x: 100,
  y: 50,
  rotation: 45,
  scale: 1.2
})

// ❌ BAD - Triggers layout recalculation
gsap.to(element, {
  left: 100,
  top: 50,
  width: '120%'
})

5.2 Accessibility

// ✅ Respect reduced motion
const prefersReducedMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches

if (prefersReducedMotion) {
  gsap.set(element, { opacity: 1 })
} else {
  gsap.from(element, { opacity: 0, duration: 0.5 })
}

6. Performance Patterns

6.1 will-change Property Usage

// Good: Apply will-change before animation
const 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-change
const animatePanelBad = (element: HTMLElement) => {
  elem

---

*Content truncated.*

Search skills

Search the agent skills registry