# Phase 2 — Cinematic Hero Animation Generate the hero section using GSAP + ScrollTrigger. Uses the brand data from Phase 1. ## Dependencies ```bash npm install gsap @gsap/react lenis ``` Register plugins once at app root (in `layout.tsx` or a client provider): ```tsx 'use client' import { useEffect } from 'react' import gsap from 'gsap' import { ScrollTrigger } from 'gsap/ScrollTrigger' export function GSAPProvider({ children }: { children: React.ReactNode }) { useEffect(() => { gsap.registerPlugin(ScrollTrigger) }, []) return <>{children} } ``` Wrap `` with `` in `layout.tsx`. ## Animation Sequence Execute these in order inside `useGSAP(() => { ... }, { scope: containerRef })`: ### 1. Hero Headline — Word Stagger Reveal ```ts // Split headline into word spans before rendering // In JSX:

{headline.split(' ').map((w, i) => {w} )}

gsap.from('.word-inner', { y: 60, opacity: 0, duration: 0.8, stagger: 0.08, ease: 'power4.out', }) ``` ### 2. Subheadline — Fade Up ```ts gsap.from('.hero-sub', { y: 30, opacity: 0, duration: 0.7, delay: 0.4, ease: 'power3.out', }) ``` ### 3. CTA Button — Spring Entrance ```ts gsap.from('.hero-cta', { scale: 0.9, opacity: 0, duration: 0.6, delay: 0.7, ease: 'back.out(1.4)', }) ``` ### 4. Background — Ken Burns (infinite) ```ts gsap.to('.hero-bg', { scale: 1.06, duration: 8, ease: 'none', repeat: -1, yoyo: true, }) ``` ### 5. ScrollTrigger — Pin + Parallax Exit ```ts ScrollTrigger.create({ trigger: containerRef.current, start: 'top top', end: '+=100%', pin: true, scrub: 0.5, onUpdate: (self) => { gsap.set('.hero-content', { y: self.progress * -80, opacity: 1 - self.progress * 1.5 }) gsap.set('.hero-bg', { scale: 1 + self.progress * 0.06 }) }, }) ``` ## Magnetic CTA Button The CTA uses Framer Motion `useMotionValue` for magnetic pull. Install: `npm install framer-motion` ```tsx 'use client' import { useRef } from 'react' import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion' export function MagneticButton({ children, href }: { children: React.ReactNode; href: string }) { const ref = useRef(null) const x = useMotionValue(0) const y = useMotionValue(0) const springX = useSpring(x, { stiffness: 150, damping: 15 }) const springY = useSpring(y, { stiffness: 150, damping: 15 }) const handleMouseMove = (e: React.MouseEvent) => { const rect = ref.current!.getBoundingClientRect() const cx = rect.left + rect.width / 2 const cy = rect.top + rect.height / 2 x.set((e.clientX - cx) * 0.3) y.set((e.clientY - cy) * 0.3) } const handleMouseLeave = () => { x.set(0) y.set(0) } return ( {children} ) } ``` ## Layout: Asymmetric Split Always: text left (55%), visual right (45%). Never centered. ```tsx
{/* Left: Text content */}
{/* Eyebrow tag */} {brand.industry} {/* Headline with word spans */}

{brand.heroHeadline.split(' ').map((word, i) => ( {word} ))}

{/* Subheadline */}

{brand.tagline}

{/* CTA */} {brand.ctaText}
{/* Right: Visual */}
``` ## CSS Variables In `globals.css`, inject brand tokens: ```css :root { --color-primary: ; --color-secondary: ; --color-accent: ; --color-bg: ; } ``` ## Mobile Collapse Below `lg:` breakpoint: - Grid becomes single column (`grid-cols-1`) - Background image moved to a decorative strip below headline - All `col-span` overrides removed - Touch targets minimum `44px`