[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6EQ0WTYWCQ2APCFQDKERN2Y":3,"skill-same:01M6EQ0WTYWCQ2APCFQDKERN2Y":95},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":50,"commitSha":51,"skillDirectory":52,"forksCount":53,"repositoryHtmlUrl":54,"repositoryHomepageUrl":55,"ownerHtmlUrl":56,"ownerBlogUrl":6,"ownerDescription":57,"updatedAt":58,"install":59,"resources":72},"01M6EQ0WTYWCQ2APCFQDKERN2Y","gsap-animations",null,"GSAP animation best practices for web design - scroll triggers, performance optimization, accessibility, responsive animations, and testing integration. Use when implementing or reviewing animations on WordPress or any web project.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fcrazyswami\u002Fgsap-animations\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"gsap animations","gsap","performance optimization","gsap animation","triggers","responsive","project","use","edit","write","read","testing","Bash","Animation","Optimization","best practices","web","performance","wordpress","design","2026-11-10 10:12:30",false,"---\nname: gsap-animations\ndescription: GSAP animation best practices for web design - scroll triggers, performance optimization, accessibility, responsive animations, and testing integration. Use when implementing or reviewing animations on WordPress or any web project.\nallowed-tools: Read, Write, Edit, Bash, Glob, Grep\n---\n\n# GSAP Animation Best Practices\n\nComprehensive guide for implementing professional, accessible, and performant animations using GSAP (GreenSock Animation Platform).\n\n## Core Principles\n\n### 1. Performance First\n- Animate `transform` and `opacity` only (GPU-accelerated)\n- Avoid animating `width`, `height`, `top`, `left`, `margin`, `padding`\n- Use `will-change` sparingly\n- Target 60fps on all devices\n\n### 2. Accessibility Always\n- Respect `prefers-reduced-motion`\n- Ensure content is visible without JavaScript\n- Don't hide critical content behind animations\n- Provide skip\u002Fpause controls for long animations\n\n### 3. Progressive Enhancement\n- Content must work without animations\n- Animations enhance, not replace, functionality\n- Test with animations disabled\n\n---\n\n## GSAP Setup\n\n### Installation\n\n```html\n\u003C!-- CDN (recommended for WordPress) -->\n\u003Cscript src=\"https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002Fgsap.min.js\">\u003C\u002Fscript>\n\u003Cscript src=\"https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002FScrollTrigger.min.js\">\u003C\u002Fscript>\n\n\u003C!-- Optional plugins -->\n\u003Cscript src=\"https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002FScrollSmoother.min.js\">\u003C\u002Fscript>\n\u003Cscript src=\"https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002FSplitText.min.js\">\u003C\u002Fscript>\n```\n\n### WordPress Enqueue\n\n```php\nfunction theme_enqueue_gsap() {\n    \u002F\u002F GSAP Core\n    wp_enqueue_script(\n        'gsap',\n        'https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002Fgsap.min.js',\n        array(),\n        '3.12.5',\n        true\n    );\n\n    \u002F\u002F ScrollTrigger\n    wp_enqueue_script(\n        'gsap-scrolltrigger',\n        'https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002FScrollTrigger.min.js',\n        array('gsap'),\n        '3.12.5',\n        true\n    );\n\n    \u002F\u002F Theme animations\n    wp_enqueue_script(\n        'theme-animations',\n        get_theme_file_uri('\u002Fassets\u002Fjs\u002Fanimations.js'),\n        array('gsap', 'gsap-scrolltrigger'),\n        filemtime(get_theme_file_path('\u002Fassets\u002Fjs\u002Fanimations.js')),\n        true\n    );\n}\nadd_action('wp_enqueue_scripts', 'theme_enqueue_gsap');\n```\n\n---\n\n## Animation Patterns\n\n### 1. Fade In on Scroll\n\n```javascript\n\u002F\u002F Basic fade in\ngsap.from('.fade-in', {\n    opacity: 0,\n    y: 50,\n    duration: 1,\n    stagger: 0.2,\n    scrollTrigger: {\n        trigger: '.fade-in',\n        start: 'top 80%',\n        toggleActions: 'play none none none'\n    }\n});\n```\n\n### 2. Staggered Elements\n\n```javascript\n\u002F\u002F Cards appearing one by one\ngsap.from('.card', {\n    opacity: 0,\n    y: 100,\n    duration: 0.8,\n    stagger: {\n        amount: 0.6,\n        from: 'start'\n    },\n    ease: 'power2.out',\n    scrollTrigger: {\n        trigger: '.cards-container',\n        start: 'top 75%'\n    }\n});\n```\n\n### 3. Parallax Effect\n\n```javascript\n\u002F\u002F Subtle parallax on images\ngsap.to('.parallax-image', {\n    yPercent: -20,\n    ease: 'none',\n    scrollTrigger: {\n        trigger: '.parallax-section',\n        start: 'top bottom',\n        end: 'bottom top',\n        scrub: true\n    }\n});\n```\n\n### 4. Text Reveal (Line by Line)\n\n```javascript\n\u002F\u002F Requires SplitText plugin (Club GreenSock)\n\u002F\u002F Or use CSS-based alternative below\n\n\u002F\u002F CSS Alternative - wrap each line in a span\ngsap.from('.reveal-line', {\n    opacity: 0,\n    y: '100%',\n    duration: 0.8,\n    stagger: 0.1,\n    ease: 'power3.out',\n    scrollTrigger: {\n        trigger: '.text-reveal',\n        start: 'top 80%'\n    }\n});\n```\n\n### 5. Curtain\u002FMask Reveal\n\n```javascript\n\u002F\u002F Image revealed by sliding mask\ngsap.to('.curtain-mask', {\n    scaleX: 0,\n    transformOrigin: 'right center',\n    duration: 1.2,\n    ease: 'power4.inOut',\n    scrollTrigger: {\n        trigger: '.curtain-container',\n        start: 'top 70%'\n    }\n});\n```\n\n### 6. Hero Animation Timeline\n\n```javascript\n\u002F\u002F Complex hero sequence\nconst heroTL = gsap.timeline({\n    defaults: { ease: 'power3.out' }\n});\n\nheroTL\n    .from('.hero-bg', { scale: 1.2, duration: 1.5 })\n    .from('.hero-title', { opacity: 0, y: 100, duration: 1 }, '-=1')\n    .from('.hero-subtitle', { opacity: 0, y: 50, duration: 0.8 }, '-=0.5')\n    .from('.hero-cta', { opacity: 0, y: 30, duration: 0.6 }, '-=0.3');\n```\n\n---\n\n## Accessibility\n\n### Respect Reduced Motion\n\n```javascript\n\u002F\u002F Check user preference\nconst prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n\u002F\u002F Option 1: Disable all animations\nif (prefersReducedMotion) {\n    gsap.globalTimeline.timeScale(0);\n    ScrollTrigger.getAll().forEach(st => st.kill());\n}\n\n\u002F\u002F Option 2: Simplified animations\nconst animationConfig = prefersReducedMotion\n    ? { duration: 0, stagger: 0 }\n    : { duration: 1, stagger: 0.2 };\n\ngsap.from('.element', {\n    opacity: 0,\n    y: prefersReducedMotion ? 0 : 50,\n    ...animationConfig\n});\n```\n\n### CSS Fallback\n\n```css\n\u002F* Ensure content visible without JS *\u002F\n.fade-in {\n    opacity: 1;\n    transform: translateY(0);\n}\n\n\u002F* Only hide if animations will run *\u002F\n.js .fade-in {\n    opacity: 0;\n    transform: translateY(50px);\n}\n\n\u002F* Respect reduced motion in CSS too *\u002F\n@media (prefers-reduced-motion: reduce) {\n    .js .fade-in {\n        opacity: 1;\n        transform: none;\n    }\n}\n```\n\n### Add JS Class to HTML\n\n```javascript\n\u002F\u002F Add at start of script\ndocument.documentElement.classList.add('js');\n```\n\n---\n\n## Responsive Animations\n\n### Breakpoint-Aware Animations\n\n```javascript\n\u002F\u002F Create responsive animations\nconst mm = gsap.matchMedia();\n\nmm.add('(min-width: 1024px)', () => {\n    \u002F\u002F Desktop animations\n    gsap.from('.hero-image', {\n        x: 100,\n        opacity: 0,\n        duration: 1.2\n    });\n\n    return () => {\n        \u002F\u002F Cleanup on breakpoint change\n    };\n});\n\nmm.add('(max-width: 1023px)', () => {\n    \u002F\u002F Mobile animations (simpler)\n    gsap.from('.hero-image', {\n        opacity: 0,\n        duration: 0.8\n    });\n});\n```\n\n### Refresh on Resize\n\n```javascript\n\u002F\u002F Recalculate ScrollTrigger on resize\nlet resizeTimer;\nwindow.addEventListener('resize', () => {\n    clearTimeout(resizeTimer);\n    resizeTimer = setTimeout(() => {\n        ScrollTrigger.refresh();\n    }, 250);\n});\n```\n\n---\n\n## Performance Optimization\n\n### 1. Use Transform Properties Only\n\n```javascript\n\u002F\u002F GOOD - GPU accelerated\ngsap.to('.element', {\n    x: 100,          \u002F\u002F transform: translateX\n    y: 50,           \u002F\u002F transform: translateY\n    rotation: 45,    \u002F\u002F transform: rotate\n    scale: 1.2,      \u002F\u002F transform: scale\n    opacity: 0.5\n});\n\n\u002F\u002F BAD - Causes layout\u002Fpaint\ngsap.to('.element', {\n    left: 100,       \u002F\u002F Triggers layout\n    width: '200px',  \u002F\u002F Triggers layout\n    marginTop: 50    \u002F\u002F Triggers layout\n});\n```\n\n### 2. Batch Similar Animations\n\n```javascript\n\u002F\u002F Use batch for many similar elements\nScrollTrigger.batch('.card', {\n    onEnter: batch => gsap.to(batch, {\n        opacity: 1,\n        y: 0,\n        stagger: 0.1\n    }),\n    start: 'top 85%'\n});\n```\n\n### 3. Kill Unused ScrollTriggers\n\n```javascript\n\u002F\u002F Cleanup when navigating (SPA) or component unmount\nfunction cleanup() {\n    ScrollTrigger.getAll().forEach(st => st.kill());\n    gsap.killTweensOf('*');\n}\n```\n\n### 4. Lazy Initialize\n\n```javascript\n\u002F\u002F Only initialize animations for visible sections\nconst observer = new IntersectionObserver((entries) => {\n    entries.forEach(entry => {\n        if (entry.isIntersecting) {\n            initSectionAnimations(entry.target);\n            observer.unobserve(entry.target);\n        }\n    });\n}, { rootMargin: '100px' });\n\ndocument.querySelectorAll('.animated-section').forEach(section => {\n    observer.observe(section);\n});\n```\n\n---\n\n## ScrollTrigger Best Practices\n\n### 1. Proper Start\u002FEnd Points\n\n```javascript\n\u002F\u002F Avoid common mistakes\nScrollTrigger.create({\n    trigger: '.section',\n    start: 'top 80%',    \u002F\u002F When top of trigger hits 80% from top of viewport\n    end: 'bottom 20%',   \u002F\u002F When bottom of trigger hits 20% from top\n    markers: true,       \u002F\u002F Debug only - remove in production!\n});\n```\n\n### 2. Pin Sections Carefully\n\n```javascript\n\u002F\u002F Pinning can cause layout issues\nScrollTrigger.create({\n    trigger: '.pinned-section',\n    start: 'top top',\n    end: '+=100%',\n    pin: true,\n    pinSpacing: true,    \u002F\u002F Usually want this true\n    anticipatePin: 1     \u002F\u002F Helps with mobile\n});\n```\n\n### 3. Handle Images Loading\n\n```javascript\n\u002F\u002F Wait for images before calculating positions\nScrollTrigger.config({\n    ignoreMobileResize: true\n});\n\nwindow.addEventListener('load', () => {\n    ScrollTrigger.refresh();\n});\n\n\u002F\u002F Or refresh after lazy images load\ndocument.querySelectorAll('img[loading=\"lazy\"]').forEach(img => {\n    img.addEventListener('load', () => ScrollTrigger.refresh());\n});\n```\n\n---\n\n## Testing Integration\n\n### Visual QA Compatibility\n\nFor the visual-qa skill to capture animations correctly:\n\n```javascript\n\u002F\u002F Expose function to complete all animations instantly\nwindow.completeAllAnimations = function() {\n    gsap.globalTimeline.progress(1);\n    ScrollTrigger.getAll().forEach(st => {\n        st.scroll(st.end);\n    });\n};\n\n\u002F\u002F Or skip animations entirely for screenshots\nif (window.location.search.includes('skip-animations')) {\n    gsap.globalTimeline.timeScale(100);\n}\n```\n\n### Playwright Testing\n\n```javascript\n\u002F\u002F In Playwright test\nawait page.evaluate(() => {\n    if (window.completeAllAnimations) {\n        window.completeAllAnimations();\n    }\n});\nawait page.waitForTimeout(500);\nawait page.screenshot({ path: 'screenshot.png', fullPage: true });\n```\n\n---\n\n## Common Animation Library\n\n### Reusable Animation Classes\n\n```javascript\n\u002F\u002F animations.js - Reusable animation library\n\nconst Animations = {\n    \u002F\u002F Initialize all animations\n    init() {\n        if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n            return;\n        }\n\n        this.fadeIn();\n        this.slideIn();\n        this.parallax();\n        this.textReveal();\n    },\n\n    fadeIn() {\n        gsap.utils.toArray('[data-animate=\"fade-in\"]').forEach(el => {\n            gsap.from(el, {\n                opacity: 0,\n                y: 50,\n                duration: 0.8,\n                scrollTrigger: {\n                    trigger: el,\n                    start: 'top 85%',\n                    once: true\n                }\n            });\n        });\n    },\n\n    slideIn() {\n        gsap.utils.toArray('[data-animate=\"slide-left\"]').forEach(el => {\n            gsap.from(el, {\n                opacity: 0,\n                x: -100,\n                duration: 1,\n                scrollTrigger: {\n                    trigger: el,\n                    start: 'top 80%',\n                    once: true\n                }\n            });\n        });\n\n        gsap.utils.toArray('[data-animate=\"slide-right\"]').forEach(el => {\n            gsap.from(el, {\n                opacity: 0,\n                x: 100,\n                duration: 1,\n                scrollTrigger: {\n                    trigger: el,\n                    start: 'top 80%',\n                    once: true\n                }\n            });\n        });\n    },\n\n    parallax() {\n        gsap.utils.toArray('[data-parallax]').forEach(el => {\n            const speed = el.dataset.parallax || 0.2;\n            gsap.to(el, {\n                yPercent: -100 * speed,\n                ease: 'none',\n                scrollTrigger: {\n                    trigger: el.parentElement,\n                    start: 'top bottom',\n                    end: 'bottom top',\n                    scrub: true\n                }\n            });\n        });\n    },\n\n    textReveal() {\n        gsap.utils.toArray('[data-animate=\"text-reveal\"]').forEach(el => {\n            const lines = el.querySelectorAll('.line');\n            gsap.from(lines, {\n                opacity: 0,\n                y: '100%',\n                duration: 0.8,\n                stagger: 0.1,\n                scrollTrigger: {\n                    trigger: el,\n                    start: 'top 80%',\n                    once: true\n                }\n            });\n        });\n    },\n\n    \u002F\u002F Refresh after dynamic content\n    refresh() {\n        ScrollTrigger.refresh();\n    },\n\n    \u002F\u002F Cleanup for SPA navigation\n    destroy() {\n        ScrollTrigger.getAll().forEach(st => st.kill());\n        gsap.killTweensOf('*');\n    }\n};\n\n\u002F\u002F Initialize on DOM ready\ndocument.addEventListener('DOMContentLoaded', () => Animations.init());\n```\n\n### HTML Usage\n\n```html\n\u003C!-- Fade in -->\n\u003Cdiv data-animate=\"fade-in\">Content\u003C\u002Fdiv>\n\n\u003C!-- Slide from left -->\n\u003Cdiv data-animate=\"slide-left\">Content\u003C\u002Fdiv>\n\n\u003C!-- Parallax (0.2 = 20% speed) -->\n\u003Cimg data-parallax=\"0.3\" src=\"image.jpg\">\n\n\u003C!-- Text reveal (requires line wrapping) -->\n\u003Cdiv data-animate=\"text-reveal\">\n    \u003Cdiv class=\"line\">First line\u003C\u002Fdiv>\n    \u003Cdiv class=\"line\">Second line\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n```\n\n---\n\n## Debugging\n\n### Enable Markers\n\n```javascript\nScrollTrigger.defaults({\n    markers: true  \u002F\u002F Shows start\u002Fend markers\n});\n```\n\n### Log Animation Events\n\n```javascript\ngsap.to('.element', {\n    x: 100,\n    onStart: () => console.log('Animation started'),\n    onComplete: () => console.log('Animation completed'),\n    onUpdate: self => console.log('Progress:', self.progress())\n});\n```\n\n### Check for Issues\n\n```javascript\n\u002F\u002F List all ScrollTriggers\nconsole.log('ScrollTriggers:', ScrollTrigger.getAll());\n\n\u002F\u002F Check if element exists\nconst el = document.querySelector('.animated-element');\nif (!el) console.warn('Animation target not found!');\n```\n\n---\n\n## Checklist\n\n### Before Launch\n\n- [ ] Remove all `markers: true`\n- [ ] Test with `prefers-reduced-motion: reduce`\n- [ ] Test on mobile devices (real devices, not just DevTools)\n- [ ] Check performance in DevTools Performance tab\n- [ ] Verify 60fps on target devices\n- [ ] Content visible without JavaScript\n- [ ] Images lazy-loaded before ScrollTrigger refresh\n- [ ] No layout thrashing (avoid animating layout properties)\n\n### Visual QA Integration\n\n- [ ] Animations complete before screenshots\n- [ ] Full-page scroll triggers all animations\n- [ ] Screenshots capture final animated state\n- [ ] Test at all viewport sizes\n\n---\n\n## Resources\n\n- [GSAP Documentation](https:\u002F\u002Fgreensock.com\u002Fdocs\u002F)\n- [ScrollTrigger Documentation](https:\u002F\u002Fgreensock.com\u002Fdocs\u002Fv3\u002FPlugins\u002FScrollTrigger)\n- [GSAP Cheat Sheet](https:\u002F\u002Fgreensock.com\u002Fcheatsheet\u002F)\n- [GreenSock Forums](https:\u002F\u002Fgreensock.com\u002Fforums\u002F)\n- [Reduced Motion Best Practices](https:\u002F\u002Fweb.dev\u002Fprefers-reduced-motion\u002F)\n","# GSAP Animation Best Practices\n\nComprehensive guide for implementing professional, accessible, and performant animations using GSAP (GreenSock Animation Platform).\n\n## Core Principles\n\n### 1. Performance First\n- Animate `transform` and `opacity` only (GPU-accelerated)\n- Avoid animating `width`, `height`, `top`, `left`, `margin`, `padding`\n- Use `will-change` sparingly\n- Target 60fps on all devices\n\n### 2. Accessibility Always\n- Respect `prefers-reduced-motion`\n- Ensure content is visible without JavaScript\n- Don't hide critical content behind animations\n- Provide skip\u002Fpause controls for long animations\n\n### 3. Progressive Enhancement\n- Content must work without animations\n- Animations enhance, not replace, functionality\n- Test with animations disabled\n\n---\n\n## GSAP Setup\n\n### Installation\n\n```html\n\u003C!-- CDN (recommended for WordPress) -->\n\u003Cscript src=\"https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002Fgsap.min.js\">\u003C\u002Fscript>\n\u003Cscript src=\"https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002FScrollTrigger.min.js\">\u003C\u002Fscript>\n\n\u003C!-- Optional plugins -->\n\u003Cscript src=\"https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002FScrollSmoother.min.js\">\u003C\u002Fscript>\n\u003Cscript src=\"https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002FSplitText.min.js\">\u003C\u002Fscript>\n```\n\n### WordPress Enqueue\n\n```php\nfunction theme_enqueue_gsap() {\n    \u002F\u002F GSAP Core\n    wp_enqueue_script(\n        'gsap',\n        'https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002Fgsap.min.js',\n        array(),\n        '3.12.5',\n        true\n    );\n\n    \u002F\u002F ScrollTrigger\n    wp_enqueue_script(\n        'gsap-scrolltrigger',\n        'https:\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fgsap\u002F3.12.5\u002FScrollTrigger.min.js',\n        array('gsap'),\n        '3.12.5',\n        true\n    );\n\n    \u002F\u002F Theme animations\n    wp_enqueue_script(\n        'theme-animations',\n        get_theme_file_uri('\u002Fassets\u002Fjs\u002Fanimations.js'),\n        array('gsap', 'gsap-scrolltrigger'),\n        filemtime(get_theme_file_path('\u002Fassets\u002Fjs\u002Fanimations.js')),\n        true\n    );\n}\nadd_action('wp_enqueue_scripts', 'theme_enqueue_gsap');\n```\n\n---\n\n## Animation Patterns\n\n### 1. Fade In on Scroll\n\n```javascript\n\u002F\u002F Basic fade in\ngsap.from('.fade-in', {\n    opacity: 0,\n    y: 50,\n    duration: 1,\n    stagger: 0.2,\n    scrollTrigger: {\n        trigger: '.fade-in',\n        start: 'top 80%',\n        toggleActions: 'play none none none'\n    }\n});\n```\n\n### 2. Staggered Elements\n\n```javascript\n\u002F\u002F Cards appearing one by one\ngsap.from('.card', {\n    opacity: 0,\n    y: 100,\n    duration: 0.8,\n    stagger: {\n        amount: 0.6,\n        from: 'start'\n    },\n    ease: 'power2.out',\n    scrollTrigger: {\n        trigger: '.cards-container',\n        start: 'top 75%'\n    }\n});\n```\n\n### 3. Parallax Effect\n\n```javascript\n\u002F\u002F Subtle parallax on images\ngsap.to('.parallax-image', {\n    yPercent: -20,\n    ease: 'none',\n    scrollTrigger: {\n        trigger: '.parallax-section',\n        start: 'top bottom',\n        end: 'bottom top',\n        scrub: true\n    }\n});\n```\n\n### 4. Text Reveal (Line by Line)\n\n```javascript\n\u002F\u002F Requires SplitText plugin (Club GreenSock)\n\u002F\u002F Or use CSS-based alternative below\n\n\u002F\u002F CSS Alternative - wrap each line in a span\ngsap.from('.reveal-line', {\n    opacity: 0,\n    y: '100%',\n    duration: 0.8,\n    stagger: 0.1,\n    ease: 'power3.out',\n    scrollTrigger: {\n        trigger: '.text-reveal',\n        start: 'top 80%'\n    }\n});\n```\n\n### 5. Curtain\u002FMask Reveal\n\n```javascript\n\u002F\u002F Image revealed by sliding mask\ngsap.to('.curtain-mask', {\n    scaleX: 0,\n    transformOrigin: 'right center',\n    duration: 1.2,\n    ease: 'power4.inOut',\n    scrollTrigger: {\n        trigger: '.curtain-container',\n        start: 'top 70%'\n    }\n});\n```\n\n### 6. Hero Animation Timeline\n\n```javascript\n\u002F\u002F Complex hero sequence\nconst heroTL = gsap.timeline({\n    defaults: { ease: 'power3.out' }\n});\n\nheroTL\n    .from('.hero-bg', { scale: 1.2, duration: 1.5 })\n    .from('.hero-title', { opacity: 0, y: 100, duration: 1 }, '-=1')\n    .from('.hero-subtitle', { opacity: 0, y: 50, duration: 0.8 }, '-=0.5')\n    .from('.hero-cta', { opacity: 0, y: 30, duration: 0.6 }, '-=0.3');\n```\n\n---\n\n## Accessibility\n\n### Respect Reduced Motion\n\n```javascript\n\u002F\u002F Check user preference\nconst prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n\u002F\u002F Option 1: Disable all animations\nif (prefersReducedMotion) {\n    gsap.globalTimeline.timeScale(0);\n    ScrollTrigger.getAll().forEach(st => st.kill());\n}\n\n\u002F\u002F Option 2: Simplified animations\nconst animationConfig = prefersReducedMotion\n    ? { duration: 0, stagger: 0 }\n    : { duration: 1, stagger: 0.2 };\n\ngsap.from('.element', {\n    opacity: 0,\n    y: prefersReducedMotion ? 0 : 50,\n    ...animationConfig\n});\n```\n\n### CSS Fallback\n\n```css\n\u002F* Ensure content visible without JS *\u002F\n.fade-in {\n    opacity: 1;\n    transform: translateY(0);\n}\n\n\u002F* Only hide if animations will run *\u002F\n.js .fade-in {\n    opacity: 0;\n    transform: translateY(50px);\n}\n\n\u002F* Respect reduced motion in CSS too *\u002F\n@media (prefers-reduced-motion: reduce) {\n    .js .fade-in {\n        opacity: 1;\n        transform: none;\n    }\n}\n```\n\n### Add JS Class to HTML\n\n```javascript\n\u002F\u002F Add at start of script\ndocument.documentElement.classList.add('js');\n```\n\n---\n\n## Responsive Animations\n\n### Breakpoint-Aware Animations\n\n```javascript\n\u002F\u002F Create responsive animations\nconst mm = gsap.matchMedia();\n\nmm.add('(min-width: 1024px)', () => {\n    \u002F\u002F Desktop animations\n    gsap.from('.hero-image', {\n        x: 100,\n        opacity: 0,\n        duration: 1.2\n    });\n\n    return () => {\n        \u002F\u002F Cleanup on breakpoint change\n    };\n});\n\nmm.add('(max-width: 1023px)', () => {\n    \u002F\u002F Mobile animations (simpler)\n    gsap.from('.hero-image', {\n        opacity: 0,\n        duration: 0.8\n    });\n});\n```\n\n### Refresh on Resize\n\n```javascript\n\u002F\u002F Recalculate ScrollTrigger on resize\nlet resizeTimer;\nwindow.addEventListener('resize', () => {\n    clearTimeout(resizeTimer);\n    resizeTimer = setTimeout(() => {\n        ScrollTrigger.refresh();\n    }, 250);\n});\n```\n\n---\n\n## Performance Optimization\n\n### 1. Use Transform Properties Only\n\n```javascript\n\u002F\u002F GOOD - GPU accelerated\ngsap.to('.element', {\n    x: 100,          \u002F\u002F transform: translateX\n    y: 50,           \u002F\u002F transform: translateY\n    rotation: 45,    \u002F\u002F transform: rotate\n    scale: 1.2,      \u002F\u002F transform: scale\n    opacity: 0.5\n});\n\n\u002F\u002F BAD - Causes layout\u002Fpaint\ngsap.to('.element', {\n    left: 100,       \u002F\u002F Triggers layout\n    width: '200px',  \u002F\u002F Triggers layout\n    marginTop: 50    \u002F\u002F Triggers layout\n});\n```\n\n### 2. Batch Similar Animations\n\n```javascript\n\u002F\u002F Use batch for many similar elements\nScrollTrigger.batch('.card', {\n    onEnter: batch => gsap.to(batch, {\n        opacity: 1,\n        y: 0,\n        stagger: 0.1\n    }),\n    start: 'top 85%'\n});\n```\n\n### 3. Kill Unused ScrollTriggers\n\n```javascript\n\u002F\u002F Cleanup when navigating (SPA) or component unmount\nfunction cleanup() {\n    ScrollTrigger.getAll().forEach(st => st.kill());\n    gsap.killTweensOf('*');\n}\n```\n\n### 4. Lazy Initialize\n\n```javascript\n\u002F\u002F Only initialize animations for visible sections\nconst observer = new IntersectionObserver((entries) => {\n    entries.forEach(entry => {\n        if (entry.isIntersecting) {\n            initSectionAnimations(entry.target);\n            observer.unobserve(entry.target);\n        }\n    });\n}, { rootMargin: '100px' });\n\ndocument.querySelectorAll('.animated-section').forEach(section => {\n    observer.observe(section);\n});\n```\n\n---\n\n## ScrollTrigger Best Practices\n\n### 1. Proper Start\u002FEnd Points\n\n```javascript\n\u002F\u002F Avoid common mistakes\nScrollTrigger.create({\n    trigger: '.section',\n    start: 'top 80%',    \u002F\u002F When top of trigger hits 80% from top of viewport\n    end: 'bottom 20%',   \u002F\u002F When bottom of trigger hits 20% from top\n    markers: true,       \u002F\u002F Debug only - remove in production!\n});\n```\n\n### 2. Pin Sections Carefully\n\n```javascript\n\u002F\u002F Pinning can cause layout issues\nScrollTrigger.create({\n    trigger: '.pinned-section',\n    start: 'top top',\n    end: '+=100%',\n    pin: true,\n    pinSpacing: true,    \u002F\u002F Usually want this true\n    anticipatePin: 1     \u002F\u002F Helps with mobile\n});\n```\n\n### 3. Handle Images Loading\n\n```javascript\n\u002F\u002F Wait for images before calculating positions\nScrollTrigger.config({\n    ignoreMobileResize: true\n});\n\nwindow.addEventListener('load', () => {\n    ScrollTrigger.refresh();\n});\n\n\u002F\u002F Or refresh after lazy images load\ndocument.querySelectorAll('img[loading=\"lazy\"]').forEach(img => {\n    img.addEventListener('load', () => ScrollTrigger.refresh());\n});\n```\n\n---\n\n## Testing Integration\n\n### Visual QA Compatibility\n\nFor the visual-qa skill to capture animations correctly:\n\n```javascript\n\u002F\u002F Expose function to complete all animations instantly\nwindow.completeAllAnimations = function() {\n    gsap.globalTimeline.progress(1);\n    ScrollTrigger.getAll().forEach(st => {\n        st.scroll(st.end);\n    });\n};\n\n\u002F\u002F Or skip animations entirely for screenshots\nif (window.location.search.includes('skip-animations')) {\n    gsap.globalTimeline.timeScale(100);\n}\n```\n\n### Playwright Testing\n\n```javascript\n\u002F\u002F In Playwright test\nawait page.evaluate(() => {\n    if (window.completeAllAnimations) {\n        window.completeAllAnimations();\n    }\n});\nawait page.waitForTimeout(500);\nawait page.screenshot({ path: 'screenshot.png', fullPage: true });\n```\n\n---\n\n## Common Animation Library\n\n### Reusable Animation Classes\n\n```javascript\n\u002F\u002F animations.js - Reusable animation library\n\nconst Animations = {\n    \u002F\u002F Initialize all animations\n    init() {\n        if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n            return;\n        }\n\n        this.fadeIn();\n        this.slideIn();\n        this.parallax();\n        this.textReveal();\n    },\n\n    fadeIn() {\n        gsap.utils.toArray('[data-animate=\"fade-in\"]').forEach(el => {\n            gsap.from(el, {\n                opacity: 0,\n                y: 50,\n                duration: 0.8,\n                scrollTrigger: {\n                    trigger: el,\n                    start: 'top 85%',\n                    once: true\n                }\n            });\n        });\n    },\n\n    slideIn() {\n        gsap.utils.toArray('[data-animate=\"slide-left\"]').forEach(el => {\n            gsap.from(el, {\n                opacity: 0,\n                x: -100,\n                duration: 1,\n                scrollTrigger: {\n                    trigger: el,\n                    start: 'top 80%',\n                    once: true\n                }\n            });\n        });\n\n        gsap.utils.toArray('[data-animate=\"slide-right\"]').forEach(el => {\n            gsap.from(el, {\n                opacity: 0,\n                x: 100,\n                duration: 1,\n                scrollTrigger: {\n                    trigger: el,\n                    start: 'top 80%',\n                    once: true\n                }\n            });\n        });\n    },\n\n    parallax() {\n        gsap.utils.toArray('[data-parallax]').forEach(el => {\n            const speed = el.dataset.parallax || 0.2;\n            gsap.to(el, {\n                yPercent: -100 * speed,\n                ease: 'none',\n                scrollTrigger: {\n                    trigger: el.parentElement,\n                    start: 'top bottom',\n                    end: 'bottom top',\n                    scrub: true\n                }\n            });\n        });\n    },\n\n    textReveal() {\n        gsap.utils.toArray('[data-animate=\"text-reveal\"]').forEach(el => {\n            const lines = el.querySelectorAll('.line');\n            gsap.from(lines, {\n                opacity: 0,\n                y: '100%',\n                duration: 0.8,\n                stagger: 0.1,\n                scrollTrigger: {\n                    trigger: el,\n                    start: 'top 80%',\n                    once: true\n                }\n            });\n        });\n    },\n\n    \u002F\u002F Refresh after dynamic content\n    refresh() {\n        ScrollTrigger.refresh();\n    },\n\n    \u002F\u002F Cleanup for SPA navigation\n    destroy() {\n        ScrollTrigger.getAll().forEach(st => st.kill());\n        gsap.killTweensOf('*');\n    }\n};\n\n\u002F\u002F Initialize on DOM ready\ndocument.addEventListener('DOMContentLoaded', () => Animations.init());\n```\n\n### HTML Usage\n\n```html\n\u003C!-- Fade in -->\n\u003Cdiv data-animate=\"fade-in\">Content\u003C\u002Fdiv>\n\n\u003C!-- Slide from left -->\n\u003Cdiv data-animate=\"slide-left\">Content\u003C\u002Fdiv>\n\n\u003C!-- Parallax (0.2 = 20% speed) -->\n\u003Cimg data-parallax=\"0.3\" src=\"image.jpg\">\n\n\u003C!-- Text reveal (requires line wrapping) -->\n\u003Cdiv data-animate=\"text-reveal\">\n    \u003Cdiv class=\"line\">First line\u003C\u002Fdiv>\n    \u003Cdiv class=\"line\">Second line\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n```\n\n---\n\n## Debugging\n\n### Enable Markers\n\n```javascript\nScrollTrigger.defaults({\n    markers: true  \u002F\u002F Shows start\u002Fend markers\n});\n```\n\n### Log Animation Events\n\n```javascript\ngsap.to('.element', {\n    x: 100,\n    onStart: () => console.log('Animation started'),\n    onComplete: () => console.log('Animation completed'),\n    onUpdate: self => console.log('Progress:', self.progress())\n});\n```\n\n### Check for Issues\n\n```javascript\n\u002F\u002F List all ScrollTriggers\nconsole.log('ScrollTriggers:', ScrollTrigger.getAll());\n\n\u002F\u002F Check if element exists\nconst el = document.querySelector('.animated-element');\nif (!el) console.warn('Animation target not found!');\n```\n\n---\n\n## Checklist\n\n### Before Launch\n\n- [ ] Remove all `markers: true`\n- [ ] Test with `prefers-reduced-motion: reduce`\n- [ ] Test on mobile devices (real devices, not just DevTools)\n- [ ] Check performance in DevTools Performance tab\n- [ ] Verify 60fps on target devices\n- [ ] Content visible without JavaScript\n- [ ] Images lazy-loaded before ScrollTrigger refresh\n- [ ] No layout thrashing (avoid animating layout properties)\n\n### Visual QA Integration\n\n- [ ] Animations complete before screenshots\n- [ ] Full-page scroll triggers all animations\n- [ ] Screenshots capture final animated state\n- [ ] Test at all viewport sizes\n\n---\n\n## Resources\n\n- [GSAP Documentation](https:\u002F\u002Fgreensock.com\u002Fdocs\u002F)\n- [ScrollTrigger Documentation](https:\u002F\u002Fgreensock.com\u002Fdocs\u002Fv3\u002FPlugins\u002FScrollTrigger)\n- [GSAP Cheat Sheet](https:\u002F\u002Fgreensock.com\u002Fcheatsheet\u002F)\n- [GreenSock Forums](https:\u002F\u002Fgreensock.com\u002Fforums\u002F)\n- [Reduced Motion Best Practices](https:\u002F\u002Fweb.dev\u002Fprefers-reduced-motion\u002F)\n",{"name":5,"description":7,"allowed-tools":49},"Read, Write, Edit, Bash, Glob, Grep","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fcrazyswami\u002Fgsap-animations",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill gsap-animations",{"tool":65,"command":66},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill gsap-animations",{"tool":68,"command":69},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill gsap-animations","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fcrazyswami\u002Fgsap-animations\u002FSKILL.md",true,[73,79,83,87,91],{"path":74,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":77,"sourceHtmlUrl":78},"references\u002Fci-troubleshooting.md","REFERENCE","参考文档",4879,"https:\u002F\u002Fgithub.com\u002Feliottbusiness\u002FDeptFlow-Agent\u002Fblob\u002Fmain\u002Fprofile\u002Fskills\u002Fgithub\u002Fgithub-pr-workflow\u002Freferences\u002Fci-troubleshooting.md",{"path":80,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":81,"sourceHtmlUrl":82},"references\u002Fconventional-commits.md",2417,"https:\u002F\u002Fgithub.com\u002Feliottbusiness\u002FDeptFlow-Agent\u002Fblob\u002Fmain\u002Fprofile\u002Fskills\u002Fgithub\u002Fgithub-pr-workflow\u002Freferences\u002Fconventional-commits.md",{"path":84,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":85,"sourceHtmlUrl":86},"skill-report.json",17311,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fcrazyswami\u002Fgsap-animations\u002Fskill-report.json",{"path":88,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":89,"sourceHtmlUrl":90},"templates\u002Fpr-body-bugfix.md",513,"https:\u002F\u002Fgithub.com\u002Feliottbusiness\u002FDeptFlow-Agent\u002Fblob\u002Fmain\u002Fprofile\u002Fskills\u002Fgithub\u002Fgithub-pr-workflow\u002Ftemplates\u002Fpr-body-bugfix.md",{"path":92,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":93,"sourceHtmlUrl":94},"templates\u002Fpr-body-feature.md",580,"https:\u002F\u002Fgithub.com\u002Feliottbusiness\u002FDeptFlow-Agent\u002Fblob\u002Fmain\u002Fprofile\u002Fskills\u002Fgithub\u002Fgithub-pr-workflow\u002Ftemplates\u002Fpr-body-feature.md",[96,108,118,127,133,140],{"skillNo":97,"name":98,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":104,"publishedAt":44},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[101],{"categoryNo":102,"categoryName":103,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[105,106,107],"model","model hierarchy","model hierarchy skill",{"skillNo":109,"name":110,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":114,"publishedAt":44},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[113],{"categoryNo":102,"categoryName":103,"taxonomyType":21,"taxonomyTypeDesc":22},[115,116,117],"MCP","zoom","zoom mcp",{"skillNo":119,"name":120,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":121,"categories":122,"keywords":123,"publishedAt":44},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[124,125,126,116],"sdk","zoom apps sdk","apps",{"skillNo":128,"name":129,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":132,"publishedAt":44},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[129],{"skillNo":134,"name":135,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":139,"publishedAt":44},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[138],{"categoryNo":102,"categoryName":103,"taxonomyType":21,"taxonomyTypeDesc":22},[135],{"skillNo":141,"name":142,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":145,"publishedAt":44},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[142]]