// Dynamic import to avoid SSR issues let gsap: any if (typeof window !== 'undefined') { gsap = require('gsap').gsap } export function splitTextIntoChars(element: HTMLElement): HTMLElement[] { const text = element.textContent || '' const chars: HTMLElement[] = [] element.innerHTML = '' for (let i = 0; i < text.length; i++) { const char = text[i] const span = document.createElement('span') span.textContent = char === ' ' ? '\u00A0' : char span.setAttribute('data-char', i.toString()) span.style.display = 'inline-block' span.style.willChange = 'transform' element.appendChild(span) chars.push(span) } return chars } export function animateSplit(element: HTMLElement, options: { delay?: number stagger?: number duration?: number ease?: string } = {}) { if (!gsap) return const { delay = 0, stagger = 0.015, duration = 0.8, ease = 'power2.out' } = options const chars = splitTextIntoChars(element) // Set initial state gsap.set(chars, { yPercent: 110, rotateZ: 5, opacity: 0, transformOrigin: 'center bottom' }) // Animate in return gsap.to(chars, { yPercent: 0, rotateZ: 0, opacity: 1, stagger, duration, ease, delay }) } export function animateWords(element: HTMLElement, options: { delay?: number stagger?: number duration?: number ease?: string } = {}) { if (!gsap) return const { delay = 0, stagger = 0.08, duration = 0.6, ease = 'power2.out' } = options const text = element.textContent || '' const words = text.split(' ') element.innerHTML = '' const wordElements = words.map(word => { const span = document.createElement('span') span.textContent = word + ' ' span.style.display = 'inline-block' span.style.willChange = 'transform' element.appendChild(span) return span }) // Set initial state gsap.set(wordElements, { yPercent: 100, opacity: 0, transformOrigin: 'center bottom' }) // Animate in return gsap.to(wordElements, { yPercent: 0, opacity: 1, stagger, duration, ease, delay }) }