Create an interactive text component where each letter responds to cursor proximity with smooth variable font animations.
## Requirements
### Setup
- Use Next.js 16 with TypeScript
- Verify Tailwind CSS v4 is installed (configured via @theme inline in globals.css)
- Install: framer-motion, next/font
- The component should be a client-side React component ('use client')
### Typography & Text
- Display the text: "WHAT ?!"
- Use Science Gothic font from next/font/google with subsets: ['latin']
- Font size: clamp(5rem, 12vw, 9rem) to be responsive across all screen sizes
- Line height: 1
- Base letter spacing: 0.15em
- Word spacing: 0.8em
- Each letter is an inline-block element with select-none
### Theme Support (Dark & Light)
- Dark mode (default):
- Background: #0d001a (dark navy)
- Text color: #40FFA7 (neon cyan)
- Light mode:
- Background: #40FFA7 (neon cyan)
- Text color: #0d001a (dark navy)
- Import useTheme from ../../app/components/ThemeProvider to detect theme
- Use theme to set bgColor and textColor (as Tailwind className for text: text-[#40FFA7] or text-[#0d001a])
### Animation System
Each letter animates 5 properties based on cursor distance:
#### Influence Radius
- 300px from cursor center affects letters
- Distance-based influence: linear falloff from 1 to 0 across the radius
- Apply smoothstep easing: influence = influence² × (3 - 2 × influence)
#### Font Weight (CSS custom property --font-weight)
- Min: 100, Max: 900
- At cursor position (influence = 1): weight = 900
- Far from cursor (influence = 0): weight = 100
- Interpolate: targetWeight = 100 + (900 - 100) × influence
#### Font Stretch (CSS custom property --font-stretch)
- Min: 100%, Max: 200%
- At cursor: 200% (expanded)
- Far: 100% (normal)
- Interpolate: targetStretch = 100 + (200 - 100) × influence
#### Letter Spacing (CSS custom property --letter-spacing)
- Min: 0em, Max: 0.4em
- At cursor: 0.4em (expanded)
- Far: 0em (touching)
- Interpolate: targetLetterSpacing = 0 + (0.4 - 0) × influence
#### Italic (CSS custom property --italic as number 0-1)
- At cursor (influence = 1): italic = 0 (normal font-style)
- Far (influence = 0): italic = 1 (italic font-style)
- Inverse of influence: italicValue = 1 - influence
- In LetterSpan component: watch --italic CSS var and toggle fontStyle between 'italic' and 'normal' when value crosses 0.5
#### Skew Y (CSS custom property --skew in degrees)
- Min: 0°, Max: ±18°
- Direction depends on cursor angle relative to letter
- Calculate angle from cursor to letter: angle = atan2(dy, dx)
- Map to skew direction: skewDirection = sin(angle)
- Interpolate: targetSkew = 18 × influence × skewDirection
- Apply as transform: skewY(var(--skew, 0)deg)
### Animation & Physics
#### Hover State (cursor active)
- Spring: damping = 10, stiffness = 160
- Easing factor: 0.15 (smooth follow)
- Letters respond smoothly and quickly to cursor movement
#### Exit State (cursor leaves)
- Spring: damping = 6, stiffness = 35 (slower, more elastic)
- Easing factor: 0.05 (stretchy recovery)
- When cursor leaves (mouseRef.current = null), letters decay back to default state slowly
#### Animation Loop
- Use requestAnimationFrame to update all letters at 60fps
- For each letter:
1. Get its bounding rect (getBoundingClientRect)
2. Calculate distance to cursor center
3. Calculate influence using smoothstep falloff
4. Calculate target values for all 5 properties
5. Ease current state toward target using exponential interpolation
6. Update CSS custom properties on the element via setProperty()
- Maintain exit state object per letter to preserve animation during easing
### Component Structure
Create a LetterSpan component that:
1. Renders as a <span> with inline-block, select-none
2. Accepts props: letter (string), textColor (className), fontFamily (string from scienceGothic.style.fontFamily), forwardedRef
3. Uses useRef and useEffect to:
- Monitor the --italic CSS variable via getComputedStyle()
- Toggle fontStyle between 'italic' and 'normal' based on --italic > 0.5
- Expose span via forwardedRef so parent can collect all letter refs
4. Styles: fontSize, fontWeight, fontStretch, fontFamily, fontStyle, lineHeight, letterSpacing, wordSpacing, transform via CSS vars
Wrap with motion(LetterSpanComponent) to enable Framer Motion.
Main ResponsiveLetters component:
1. Manages containerRef, lettersRef array, mouseRef, animIdRef, aliveRef
2. Tracks cursor position via onMouseMove (set mouseRef.current = { x, y })
3. Clears cursor on onMouseLeave (set mouseRef.current = null)
4. Add onTouchStart handler for mobile (optional, touch doesn't affect animation but shows component is touch-aware)
5. Runs animation loop in useEffect that:
- Initializes exit state per letter (weight, stretch, letterSpacing, skew, italic all at default)
- Calls animate() via requestAnimationFrame
- Cleans up on unmount
### Responsive Design
- Container: flex, min-h-screen, w-full, items-center, justify-center
- Letter gaps: gap-0.5 (sm:gap-1) for mobile responsiveness
- Component must fill viewport and work on all screen sizes from 320px to 1200px
- No hardcoded widths/heights — use clamp() and Tailwind fluid utilities
### Code Structure
- Single file component exported as default function ResponsiveLetters
- All constants defined at module level (TEXT, INFLUENCE_RADIUS, MAX_WEIGHT, MIN_WEIGHT, etc.)
- Clean up all effects: cancel animIdRef on unmount, disconnect MutationObserver
- No TypeScript errors or use of 'any'
### Mobile Responsiveness
- Component supports touch (onTouchStart handler present)
- No hover-only interactions (cursor proximity is primary, but mobile still sees full animation)
- Text scales responsively via clamp()
- Gaps adjust via sm: breakpoint (gap-0.5 to gap-1)
Create this as a fully functional, copy-paste-ready component that showcases variable font animation with cursor-driven influence.