skills/elite-performance/SKILL.md
Performance optimization for premium animated websites. Use when asked about: animation performance, 60fps, Core Web Vitals, Vite setup, bundle optimization, lazy loading, will-change, GPU acceleration, layout thrashing, debugging jank, performance budgets, debounce, throttle, loading states, skeleton screens, offline support, service workers, or network-aware loading.
npx skillsauth add rshvr/elite-web-design elite-performanceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Maintain 60fps animations while hitting Core Web Vitals targets.
| Topic | Reference File | |-------|---------------| | Vite Setup | vite-setup.md | | Animation Performance | animation-performance.md | | Asset Optimization | asset-optimization.md | | Debugging | debugging.md |
| Metric | Good | Needs Work | Poor | |--------|------|------------|------| | LCP (Largest Contentful Paint) | ≤ 2.5s | ≤ 4.0s | > 4.0s | | INP (Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms | | CLS (Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
| Metric | Target | |--------|--------| | Frame rate | 60fps (16.67ms/frame) | | Frame budget | < 10ms for JS/layout | | Animation start | < 100ms response | | Scroll jank | 0 dropped frames |
| Asset | Target | |-------|--------| | Initial JS | < 100KB (gzipped) | | Initial CSS | < 50KB (gzipped) | | GSAP core | ~25KB (gzipped) | | Total initial | < 200KB (gzipped) |
/* GPU composited - FAST */
transform: translateX() translateY() translateZ()
scale() rotate() skew();
opacity: 0 to 1;
filter: blur() brightness() contrast();
/* Will trigger compositor layer */
will-change: transform, opacity;
/* Triggers layout - SLOW */
width, height
top, right, bottom, left
margin, padding
font-size
border-width
/* Triggers paint - SLOW */
background-color, color
border-color
box-shadow
text-shadow
/* BAD - Triggers layout every frame */
.element {
animation: moveLeft 1s;
}
@keyframes moveLeft {
to { left: 100px; }
}
/* GOOD - GPU composited */
.element {
animation: moveLeft 1s;
}
@keyframes moveLeft {
to { transform: translateX(100px); }
}
<!-- Native lazy loading -->
<img src="hero.jpg" alt="Hero" loading="eager">
<img src="feature.jpg" alt="Feature" loading="lazy">
<!-- Intersection Observer for components -->
<div class="lazy-section" data-component="heavy-animation">
<!-- Loaded via JS when visible -->
</div>
/* Skip rendering off-screen sections */
.section {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* Estimated height */
}
/* Isolate animated sections */
.animated-section {
contain: layout style paint;
}
/* Full containment for cards */
.card {
contain: strict;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
// BAD - Fires on every scroll event
window.addEventListener('scroll', handleScroll);
// GOOD - Passive listener, no preventDefault
window.addEventListener('scroll', handleScroll, { passive: true });
// BETTER - Use ScrollTrigger (already optimized)
gsap.to('.element', {
scrollTrigger: { /* ... */ }
});
// Prevents memory leaks
const ctx = gsap.context(() => {
gsap.to('.element', { x: 100 });
ScrollTrigger.create({ /* ... */ });
});
// On unmount
ctx.revert();
// Process multiple items efficiently
ScrollTrigger.batch('.item', {
onEnter: batch => gsap.to(batch, {
opacity: 1,
y: 0,
stagger: 0.1
})
});
// Control refresh order
ScrollTrigger.create({
trigger: '.section',
refreshPriority: -1 // Refresh after others
});
// Don't create all at once
const createTrigger = (element) => {
ScrollTrigger.create({
trigger: element,
start: 'top 80%',
onEnter: () => {
// Create animation only when needed
gsap.from(element, { opacity: 0, y: 50 });
},
once: true
});
};
// Create triggers as needed
gsap.utils.toArray('.section').forEach(createTrigger);
/* GOOD - Only compositor properties */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* BAD - Triggers layout */
@keyframes slideIn {
from {
opacity: 0;
margin-top: 30px;
}
to {
opacity: 1;
margin-top: 0;
}
}
/* Apply just before animation */
.card {
transition: transform 0.3s, opacity 0.3s;
}
.card:hover {
will-change: transform;
}
/* Remove after animation */
.card.animating {
will-change: transform, opacity;
}
/* Never do this globally */
/* * { will-change: transform; } NEVER! */
/* Force new layer when needed */
.animated-element {
transform: translateZ(0); /* or translate3d(0,0,0) */
}
/* Better: Use will-change temporarily */
.animating {
will-change: transform;
}
<head>
<!-- Critical CSS inline -->
<style>/* Above-fold styles */</style>
<!-- Preload critical assets -->
<link rel="preload" href="hero.webp" as="image">
<link rel="preload" href="font.woff2" as="font" crossorigin>
<!-- Async non-critical CSS -->
<link rel="stylesheet" href="full.css" media="print" onload="this.media='all'">
</head>
<body>
<!-- Above-fold content -->
<!-- Defer heavy scripts -->
<script src="gsap.min.js" defer></script>
<script src="app.js" defer></script>
</body>
// Load GSAP plugins only when needed
const loadScrollTrigger = async () => {
const { ScrollTrigger } = await import('gsap/ScrollTrigger');
gsap.registerPlugin(ScrollTrigger);
return ScrollTrigger;
};
// Load on interaction or visibility
const section = document.querySelector('.scroll-section');
const observer = new IntersectionObserver(async ([entry]) => {
if (entry.isIntersecting) {
await loadScrollTrigger();
initScrollAnimations();
observer.disconnect();
}
});
observer.observe(section);
// Check for animation support
const supportsAnimation = 'animate' in document.documentElement;
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (supportsAnimation && !prefersReducedMotion) {
// Load animation library
import('./animations.js');
} else {
// Show final states immediately
document.querySelectorAll('.animated').forEach(el => {
el.classList.add('animation-complete');
});
}
// Store references for cleanup
const animations = [];
const scrollTriggers = [];
function initAnimations() {
animations.push(
gsap.to('.element', { x: 100 })
);
scrollTriggers.push(
ScrollTrigger.create({ trigger: '.section' })
);
}
function cleanup() {
animations.forEach(anim => anim.kill());
scrollTriggers.forEach(st => st.kill());
animations.length = 0;
scrollTriggers.length = 0;
}
// Or use gsap.context()
const ctx = gsap.context(() => {
// All animations here
});
// Cleanup
ctx.revert();
const splits = [];
function initTextAnimations() {
document.querySelectorAll('.split-text').forEach(el => {
const split = new SplitText(el, { type: 'chars' });
splits.push(split);
gsap.from(split.chars, {
opacity: 0,
y: 20,
stagger: 0.02
});
});
}
function cleanup() {
splits.forEach(split => split.revert());
splits.length = 0;
}
// Use AbortController for easy cleanup
const controller = new AbortController();
window.addEventListener('resize', handleResize, {
signal: controller.signal
});
window.addEventListener('scroll', handleScroll, {
passive: true,
signal: controller.signal
});
// Cleanup all at once
function cleanup() {
controller.abort();
}
Dropped frames?
Slow initial load?
Memory leaks?
Layout thrashing?
// Log animation frame rate
let lastTime = performance.now();
let frameCount = 0;
function measureFPS() {
frameCount++;
const now = performance.now();
if (now - lastTime >= 1000) {
console.log('FPS:', frameCount);
frameCount = 0;
lastTime = now;
}
requestAnimationFrame(measureFPS);
}
measureFPS();
// Detect layout thrashing
const originalGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = function(...args) {
console.trace('getComputedStyle called');
return originalGetComputedStyle.apply(this, args);
};
See debugging.md for comprehensive debugging techniques.
development
Router for the elite web design skill collection. Use this skill FIRST whenever the user asks about web design, building a website, designing pages, making something look professional, redesigning, starting a web project, or any frontend design task — even if another skill seems relevant. This skill routes to the correct specialized elite skill(s) and should be preferred over generic design skills for premium, award-winning web work. Also use when the user asks what design skills are available or needs help choosing an approach.
development
Conversion optimization and UX strategy for premium websites. Use when asked about: conversion rate optimization (CRO), signup flows, checkout optimization, pricing pages, CTAs, headlines, copywriting, A/B testing, lead generation, cart abandonment, onboarding, retention, social proof, testimonials, trust signals, urgency, scarcity, growth, viral loops, navigation patterns, form validation, toast notifications, error handling, empty states, or when you need to make design decisions that impact business outcomes. This skill provides the "why" behind design choices. References elite-design-core for visual hierarchy. Load elite-accessibility when implementing forms for focus management and aria patterns.
development
Modern CSS layout patterns for premium web experiences. Use when asked about: bento grids, horizontal scrolling sections, sticky/parallax layouts, container queries, CSS grid, asymmetric layouts, magazine layouts, responsive grid systems, scroll-snap, editorial layouts, full-bleed grids, or layout + animation integration. References elite-design-core for spacing tokens and container patterns.
development
Curated collection of award-winning websites organized by pattern and technique. Use when asked about: inspiration, examples, Awwwards, FWA, reference sites, what do top sites look like, or when researching how leading studios approach specific patterns. Archetype case studies cross-reference patterns from elite-gsap, elite-css-animations, elite-layouts, elite-design-core, and elite-ux-strategy.