Skip to main content
Glama
animations.ts12.4 kB
import type { AnimationIntensity, InterfaceType, AnimationSpec } from '../utils/types.js'; import { ENTRY_ANIMATIONS, SCROLL_ANIMATIONS, HOVER_ANIMATIONS, LOADING_ANIMATIONS, EASING_FUNCTIONS, TRANSITION_CHOREOGRAPHY } from '../resources/animation-patterns.js'; interface AnimationCompositionInput { interfaceType: InterfaceType; intensity: AnimationIntensity; sections?: string[]; customRequirements?: string; } function getIntensityMultiplier(intensity: AnimationIntensity): { duration: number; delay: number; complexity: number } { const multipliers = { subtle: { duration: 0.7, delay: 0.5, complexity: 1 }, moderate: { duration: 1.0, delay: 1.0, complexity: 2 }, dramatic: { duration: 1.3, delay: 1.2, complexity: 3 }, cinematic: { duration: 1.6, delay: 1.5, complexity: 4 } }; return multipliers[intensity]; } function selectEntryAnimations(intensity: AnimationIntensity): string[] { const animationsByIntensity = { subtle: ['fadeIn', 'fadeUp'], moderate: ['fadeUp', 'slideFromLeft', 'slideFromRight', 'scaleUp'], dramatic: ['scaleUp', 'blurIn', 'flipIn', 'slideFromLeft'], cinematic: ['blurIn', 'flipIn', 'bounceIn', 'scaleUp'] }; return animationsByIntensity[intensity]; } function selectHoverAnimations(intensity: AnimationIntensity): string[] { const hoverByIntensity = { subtle: ['lift'], moderate: ['lift', 'glow', 'backgroundFill'], dramatic: ['lift', 'glow', 'magneticPull', 'borderDraw'], cinematic: ['magneticPull', 'tilt3D', 'borderDraw', 'glow'] }; return hoverByIntensity[intensity]; } function selectScrollAnimations(intensity: AnimationIntensity): string[] { const scrollByIntensity = { subtle: ['revealOnScroll'], moderate: ['revealOnScroll', 'parallax'], dramatic: ['parallax', 'horizontalScroll', 'scaleOnScroll'], cinematic: ['parallax', 'horizontalScroll', 'stickyReveal', 'scaleOnScroll', 'progressiveBlur'] }; return scrollByIntensity[intensity]; } function generateSectionAnimations(interfaceType: InterfaceType, intensity: AnimationIntensity): AnimationSpec[] { const multiplier = getIntensityMultiplier(intensity); const specs: AnimationSpec[] = []; const sectionAnimations: Record<string, Partial<AnimationSpec>> = { hero: { element: 'Hero Section', trigger: 'load', type: 'Orchestrated entrance - background fades, then image scales, then text slides up, then CTA bounces', description: 'Creates dramatic first impression with choreographed reveal' }, navigation: { element: 'Navigation', trigger: 'load', type: 'Fade in with subtle slide down', description: 'Quick, unobtrusive appearance' }, features: { element: 'Feature Cards', trigger: 'scroll', type: 'Staggered fade up as they enter viewport', description: 'Sequential reveal creates rhythm and guides reading' }, testimonials: { element: 'Testimonials', trigger: 'scroll', type: 'Scale up from center with blur in', description: 'Draws attention to social proof' }, pricing: { element: 'Pricing Cards', trigger: 'scroll', type: 'Staggered slide in from sides, featured plan with extra bounce', description: 'Highlights recommended option' }, cta: { element: 'Call-to-Action', trigger: 'scroll', type: 'Pulse animation when in view, magnetic hover effect', description: 'Draws attention and encourages interaction' }, footer: { element: 'Footer', trigger: 'scroll', type: 'Simple fade in', description: 'Subtle, doesn\'t distract from content above' } }; const baseDuration = 0.5; const baseDelay = 0.1; for (const [section, config] of Object.entries(sectionAnimations)) { specs.push({ element: config.element || section, trigger: config.trigger || 'scroll', type: config.type || 'fade up', duration: `${(baseDuration * multiplier.duration).toFixed(2)}s`, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', delay: `${(baseDelay * multiplier.delay).toFixed(2)}s`, description: config.description || '' }); } return specs; } function generateCSSVariables(intensity: AnimationIntensity): string { const multiplier = getIntensityMultiplier(intensity); return ` ### CSS Custom Properties for Animations \`\`\`css :root { /* Timing */ --duration-instant: ${(0.1 * multiplier.duration).toFixed(2)}s; --duration-fast: ${(0.2 * multiplier.duration).toFixed(2)}s; --duration-normal: ${(0.3 * multiplier.duration).toFixed(2)}s; --duration-slow: ${(0.5 * multiplier.duration).toFixed(2)}s; --duration-slower: ${(0.8 * multiplier.duration).toFixed(2)}s; /* Easing */ --ease-out: cubic-bezier(0.22, 1, 0.36, 1); --ease-in: cubic-bezier(0.64, 0, 0.78, 0); --ease-in-out: cubic-bezier(0.45, 0, 0.55, 1); --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275); --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); /* Stagger delays */ --stagger-1: ${(0.05 * multiplier.delay).toFixed(2)}s; --stagger-2: ${(0.1 * multiplier.delay).toFixed(2)}s; --stagger-3: ${(0.15 * multiplier.delay).toFixed(2)}s; --stagger-4: ${(0.2 * multiplier.delay).toFixed(2)}s; --stagger-5: ${(0.25 * multiplier.delay).toFixed(2)}s; /* Transform values */ --translate-sm: 10px; --translate-md: 20px; --translate-lg: 40px; --scale-sm: 0.98; --scale-md: 0.95; --scale-lg: 0.9; } /* Reduced motion support */ @media (prefers-reduced-motion: reduce) { :root { --duration-instant: 0s; --duration-fast: 0s; --duration-normal: 0.01s; --duration-slow: 0.01s; --duration-slower: 0.01s; } *, *::before, *::after { animation-duration: 0.01s !important; transition-duration: 0.01s !important; } } \`\`\` `; } function generateKeyframeExamples(intensity: AnimationIntensity): string { return ` ### Keyframe Definitions \`\`\`css /* Fade Up */ @keyframes fadeUp { from { opacity: 0; transform: translateY(var(--translate-md)); } to { opacity: 1; transform: translateY(0); } } /* Scale In */ @keyframes scaleIn { from { opacity: 0; transform: scale(var(--scale-lg)); } to { opacity: 1; transform: scale(1); } } /* Blur In */ @keyframes blurIn { from { opacity: 0; filter: blur(10px); } to { opacity: 1; filter: blur(0); } } /* Slide From Left */ @keyframes slideFromLeft { from { opacity: 0; transform: translateX(calc(var(--translate-lg) * -1)); } to { opacity: 1; transform: translateX(0); } } /* Bounce In */ @keyframes bounceIn { 0% { opacity: 0; transform: scale(0.3); } 50% { transform: scale(1.05); } 70% { transform: scale(0.9); } 100% { opacity: 1; transform: scale(1); } } /* Pulse (for loading/attention) */ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } /* Shimmer (for skeleton loaders) */ @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } /* Float (for ambient motion) */ @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } } \`\`\` `; } export function composeAnimations(input: AnimationCompositionInput): string { const { interfaceType, intensity, sections, customRequirements } = input; const multiplier = getIntensityMultiplier(intensity); const entryAnims = selectEntryAnimations(intensity); const hoverAnims = selectHoverAnimations(intensity); const scrollAnims = selectScrollAnimations(intensity); const sectionSpecs = generateSectionAnimations(interfaceType, intensity); return `# Animation Specification ## Overview **Interface Type**: ${interfaceType.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())} **Animation Intensity**: ${intensity.toUpperCase()} **Complexity Level**: ${multiplier.complexity}/4 ${customRequirements ? `**Custom Requirements**: ${customRequirements}` : ''} --- ## Animation Philosophy For ${intensity} intensity, animations should feel: ${intensity === 'subtle' ? '- Barely noticeable but present\n- Quick and efficient\n- Professional and understated' : ''} ${intensity === 'moderate' ? '- Noticeable but not distracting\n- Purposeful and meaningful\n- Enhancing without overwhelming' : ''} ${intensity === 'dramatic' ? '- Eye-catching and impressive\n- Bold and expressive\n- Statement-making' : ''} ${intensity === 'cinematic' ? '- Theatrical and immersive\n- Story-telling through motion\n- Memorable and unique' : ''} --- ## Entry Animations ### Recommended Types ${entryAnims.map(name => { const anim = ENTRY_ANIMATIONS[name as keyof typeof ENTRY_ANIMATIONS]; return ` #### ${anim.name} - **Initial State**: \`${anim.initial}\` - **Final State**: \`${anim.final}\` - **Duration**: ${anim.duration} - **Easing**: ${anim.easing} - **Stagger**: ${anim.stagger} - **Description**: ${anim.description} `; }).join('')} --- ## Scroll Animations ### Active Effects ${scrollAnims.map(name => { const anim = SCROLL_ANIMATIONS[name as keyof typeof SCROLL_ANIMATIONS]; return ` #### ${anim.name} - **Description**: ${anim.description} - **Use Case**: ${anim.useCase} `; }).join('')} ### Implementation Notes - Use Intersection Observer for scroll-triggered animations - Trigger animations when element is 20-30% in viewport - Consider scroll velocity for parallax effects - Debounce scroll handlers for performance --- ## Hover & Interaction Effects ### Active Effects ${hoverAnims.map(name => { const anim = HOVER_ANIMATIONS[name as keyof typeof HOVER_ANIMATIONS]; return ` #### ${anim.name} - **Effect**: \`${anim.effect}\` - **Duration**: ${anim.duration} - **Description**: ${anim.description} - **Use Case**: ${anim.useCase} `; }).join('')} --- ## Loading States ### Recommended Loaders ${Object.entries(LOADING_ANIMATIONS).slice(0, 4).map(([key, anim]) => ` #### ${anim.name} - **Effect**: ${anim.effect} - **Implementation**: ${anim.implementation} - **Use Case**: ${anim.useCase} `).join('')} --- ## Section-by-Section Choreography ${sectionSpecs.map(spec => ` ### ${spec.element} - **Trigger**: ${spec.trigger} - **Animation**: ${spec.type} - **Duration**: ${spec.duration} - **Delay**: ${spec.delay} - **Purpose**: ${spec.description} `).join('')} --- ## Transition Choreography ### Staggered Reveals ${TRANSITION_CHOREOGRAPHY.staggeredFade.description} - Timing: ${TRANSITION_CHOREOGRAPHY.staggeredFade.timing} - Direction: ${TRANSITION_CHOREOGRAPHY.staggeredFade.direction} ### Page/View Transitions ${TRANSITION_CHOREOGRAPHY.exitThenEnter.description} - Timing: ${TRANSITION_CHOREOGRAPHY.exitThenEnter.timing} - Direction: ${TRANSITION_CHOREOGRAPHY.exitThenEnter.direction} --- ## Easing Reference ${Object.entries(EASING_FUNCTIONS).map(([key, easing]) => ` ### ${easing.name} - **CSS**: \`${easing.css}\` - **Feel**: ${easing.description} - **Use For**: ${easing.useCase} `).join('')} --- ${generateCSSVariables(intensity)} ${generateKeyframeExamples(intensity)} --- ## Performance Guidelines ### GPU-Accelerated Properties Only animate these for 60fps performance: - \`transform\` (translate, scale, rotate) - \`opacity\` - \`filter\` (blur, brightness) ### Avoid Animating - \`width\`, \`height\` (use transform: scale instead) - \`top\`, \`left\`, \`right\`, \`bottom\` (use transform: translate) - \`margin\`, \`padding\` (causes layout recalculation) - \`border-width\` (use box-shadow or pseudo-elements) ### Optimization Tips 1. Use \`will-change\` sparingly for complex animations 2. Use \`transform: translateZ(0)\` to force GPU layer 3. Debounce scroll-based animations 4. Use Intersection Observer, not scroll events 5. Prefer CSS animations over JavaScript when possible --- ## Accessibility: Reduced Motion Always provide reduced motion alternatives: \`\`\`css @media (prefers-reduced-motion: reduce) { /* Replace motion with instant/fade transitions */ .animated-element { animation: none; transition: opacity 0.2s ease; } /* Disable parallax and scroll-jacking */ .parallax { transform: none !important; } } \`\`\` --- *These animation specifications are designed to create a cohesive, polished feel that elevates the interface beyond typical AI-generated designs.* `; }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Nwabukin/mcp-ui-prompt-refiner'

If you have feedback or need assistance with the MCP directory API, please join our Discord server