# Kinetic Typography

Text as the visual. Words don't just appear — they move, transform, and tell the story. This is foundational motion design, not a trend. Every video creator needs this in their toolkit.

## Critical Self-Assessment

**Why this deserves a template:**
- ✅ Foundational technique (not just a trend — timeless)
- ✅ Massive use cases (quotes, lyrics, podcasts, explainers, ads)
- ✅ Remotion's strength (pure code, no assets needed)
- ✅ Universally applicable (works with any brand/style)
- ✅ Different from other templates (technique, not aesthetic)

**When NOT to use:**
- Product demos (need to show the product)
- Visual portfolios (need imagery)
- When the words aren't compelling enough to carry the video

---

## Core Philosophy

**The text IS the visual.** Every word earns its moment. Animation reinforces meaning.

```
Traditional video: Background → Image → Text overlay
Kinetic typography: Text IS the content → Animation IS the emotion
```

---

## Animation Archetypes

### 1. Word-by-Word Reveal
Each word enters separately, building the sentence.

```tsx
const WordByWord = ({ 
  text, 
  frame,
  startFrame = 0,
  wordsPerSecond = 3,
  fps = 30 
}: {
  text: string;
  frame: number;
  startFrame?: number;
  wordsPerSecond?: number;
  fps?: number;
}) => {
  const words = text.split(' ');
  const framesPerWord = fps / wordsPerSecond;
  
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.3em', justifyContent: 'center' }}>
      {words.map((word, i) => {
        const wordStart = startFrame + i * framesPerWord;
        const progress = Math.min(1, Math.max(0, (frame - wordStart) / 10));
        
        const opacity = progress;
        const y = interpolate(progress, [0, 1], [30, 0]);
        const scale = interpolate(progress, [0, 1], [0.8, 1]);
        
        return (
          <span
            key={i}
            style={{
              opacity,
              transform: `translateY(${y}px) scale(${scale})`,
              display: 'inline-block',
            }}
          >
            {word}
          </span>
        );
      })}
    </div>
  );
};
```

### 2. Character-by-Character (Typewriter+)
Letters appear one by one with style.

```tsx
const CharacterReveal = ({
  text,
  frame,
  startFrame = 0,
  charsPerFrame = 0.5,
  stagger = true,
}: {
  text: string;
  frame: number;
  startFrame?: number;
  charsPerFrame?: number;
  stagger?: boolean;
}) => {
  const elapsed = frame - startFrame;
  const charsToShow = Math.floor(elapsed * charsPerFrame);
  
  return (
    <span>
      {text.split('').map((char, i) => {
        const isVisible = i < charsToShow;
        const charAge = charsToShow - i; // How long this char has been visible
        
        // Stagger effect: recent chars are still animating
        const scale = stagger && charAge < 5 
          ? interpolate(charAge, [0, 5], [1.3, 1])
          : 1;
        const opacity = isVisible ? 1 : 0;
        
        return (
          <span
            key={i}
            style={{
              opacity,
              transform: `scale(${scale})`,
              display: 'inline-block',
              // Preserve spaces
              whiteSpace: char === ' ' ? 'pre' : 'normal',
            }}
          >
            {char}
          </span>
        );
      })}
    </span>
  );
};
```

### 3. Scale Punch
Key words punch in large, then settle to reading size.

```tsx
const ScalePunch = ({
  word,
  frame,
  startFrame,
  emphasis = false,
}: {
  word: string;
  frame: number;
  startFrame: number;
  emphasis?: boolean;
}) => {
  const progress = spring({
    frame: frame - startFrame,
    fps: 30,
    config: { damping: emphasis ? 8 : 12 },
  });
  
  const scale = interpolate(progress, [0, 1], [emphasis ? 3 : 1.5, 1]);
  const opacity = Math.min(1, progress * 2);
  
  return (
    <span style={{
      display: 'inline-block',
      transform: `scale(${scale})`,
      opacity,
      color: emphasis ? '#ff4444' : 'inherit',
      fontWeight: emphasis ? 900 : 'inherit',
    }}>
      {word}
    </span>
  );
};
```

### 4. Directional Slides
Words enter from different directions based on meaning.

```tsx
type Direction = 'left' | 'right' | 'up' | 'down';

const DirectionalSlide = ({
  word,
  frame,
  startFrame,
  direction,
}: {
  word: string;
  frame: number;
  startFrame: number;
  direction: Direction;
}) => {
  const progress = spring({
    frame: frame - startFrame,
    fps: 30,
    config: { damping: 15 },
  });
  
  const transforms: Record<Direction, { x: number; y: number }> = {
    left: { x: -100, y: 0 },
    right: { x: 100, y: 0 },
    up: { x: 0, y: -80 },
    down: { x: 0, y: 80 },
  };
  
  const { x, y } = transforms[direction];
  const currentX = interpolate(progress, [0, 1], [x, 0]);
  const currentY = interpolate(progress, [0, 1], [y, 0]);
  
  return (
    <span style={{
      display: 'inline-block',
      transform: `translate(${currentX}px, ${currentY}px)`,
      opacity: progress,
    }}>
      {word}
    </span>
  );
};
```

### 5. Text Mask / Reveal Wipe
Text revealed by a moving mask (left-to-right, top-to-bottom).

```tsx
const TextWipe = ({
  children,
  frame,
  startFrame,
  duration = 20,
  direction = 'left',
}: {
  children: React.ReactNode;
  frame: number;
  startFrame: number;
  duration?: number;
  direction?: 'left' | 'right' | 'top' | 'bottom';
}) => {
  const progress = interpolate(
    frame,
    [startFrame, startFrame + duration],
    [0, 100],
    { extrapolateRight: 'clamp' }
  );
  
  const clipPaths: Record<string, string> = {
    left: `inset(0 ${100 - progress}% 0 0)`,
    right: `inset(0 0 0 ${100 - progress}%)`,
    top: `inset(0 0 ${100 - progress}% 0)`,
    bottom: `inset(${100 - progress}% 0 0 0)`,
  };
  
  return (
    <div style={{ clipPath: clipPaths[direction] }}>
      {children}
    </div>
  );
};
```

---

## Semantic Animation

**Match animation to meaning.** This is what separates amateur from professional.

| Word/Concept | Animation |
|--------------|-----------|
| "STOP" | Sudden hard cut, no easing |
| "slowly" | Slow fade, long duration |
| "EXPLODE" | Scale punch from center |
| "falling" | Drop from above with gravity |
| "rise" | Float up from below |
| "whisper" | Small, fade in gently |
| "LOUD" | Large, shake effect |
| "fast" | Quick slide, short duration |
| "together" | Multiple words converge |
| "apart" | Words split and diverge |
| "spinning" | Rotation animation |

```tsx
// Example: "Everything came CRASHING down"
// "Everything came" - normal word-by-word
// "CRASHING" - scale punch + shake + red color
// "down" - drops from above with gravity
```

---

## Layout Strategies

### Centered Stack (Default)
Words stack vertically, centered. Classic, reliable.

```tsx
<div style={{
  display: 'flex',
  flexDirection: 'column',
  alignItems: 'center',
  justifyContent: 'center',
  height: '100%',
  gap: 20,
}}>
  <Word>First line</Word>
  <Word>Second line</Word>
</div>
```

### Scattered / Dynamic Positioning
Words appear at different positions, creating energy.

```tsx
const positions = [
  { x: '20%', y: '30%' },
  { x: '60%', y: '20%' },
  { x: '40%', y: '50%' },
  { x: '70%', y: '70%' },
];

words.map((word, i) => (
  <span style={{
    position: 'absolute',
    left: positions[i % positions.length].x,
    top: positions[i % positions.length].y,
    transform: 'translate(-50%, -50%)',
  }}>
    {word}
  </span>
));
```

### Split Screen
Different phrases on different sides, reveal simultaneously.

### Size Hierarchy
Important words are HUGE, context words are small.

```tsx
// "The ONE thing that MATTERS"
<span style={{ fontSize: 36 }}>The</span>
<span style={{ fontSize: 120, fontWeight: 900 }}>ONE</span>
<span style={{ fontSize: 36 }}>thing that</span>
<span style={{ fontSize: 100, fontWeight: 900 }}>MATTERS</span>
```

---

## Timing & Rhythm

Kinetic typography lives and dies by rhythm. Sync to:

1. **Voiceover:** Words appear as they're spoken
2. **Music beat:** Key words land on beats
3. **Reading pace:** ~3-4 words/second for comfortable reading

```tsx
// Voiceover sync helper
const voiceTimings = [
  { word: 'The', start: 0 },
  { word: 'future', start: 0.3 },
  { word: 'is', start: 0.8 },
  { word: 'NOW', start: 1.0 },
];

// Convert to frames
const wordFrames = voiceTimings.map(t => ({
  ...t,
  startFrame: Math.floor(t.start * fps),
}));
```

---

## Color & Emphasis

Use color sparingly for **semantic emphasis:**

- **Red/Orange:** Urgency, warning, key point
- **Green:** Positive, success, growth
- **Blue:** Calm, trust, stability
- **White on dark:** Default, readable
- **Accent color:** Brand moments, CTAs

```tsx
// Only emphasize 1-2 words per sentence
"We don't just build products. We build <FUTURES>."
// "FUTURES" gets: larger size + brand color + scale punch
```

---

## Background Treatment

Keep backgrounds minimal — text is the star.

**Options:**
1. Solid color (safest)
2. Subtle gradient (adds depth)
3. Slow-moving abstract (doesn't compete)
4. Blurred imagery (context without distraction)

**Never:** Busy backgrounds that compete with text.

---

## Common Mistakes

❌ **Every word animates the same way** → Monotonous
✅ Vary animation based on meaning and rhythm

❌ **Too fast** → Unreadable
✅ Test: can you read it at 1x speed without pausing?

❌ **No hierarchy** → Everything looks the same importance
✅ Size, color, animation all signal what matters

❌ **Animation for animation's sake** → Distracting
✅ Every movement should reinforce the message

❌ **Ignoring the audio** → Feels disconnected
✅ Sync to voiceover/music beats

---

## Quality Checklist

Before rendering:
- [ ] Can I read every word comfortably at 1x speed?
- [ ] Do key words have distinct emphasis?
- [ ] Does animation reinforce meaning (not just decorate)?
- [ ] Is there rhythm and variation (not monotonous)?
- [ ] Does the background let text breathe?
- [ ] Are transitions between phrases smooth?
- [ ] Does it sync to audio (if applicable)?

---

*Reference: Apple keynote title cards, Spotify Wrapped, TED talk animations, lyric videos*
