MCPcopy Index your code
hub / github.com/developedbyed/react-gradient-glow

github.com/developedbyed/react-gradient-glow @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
39 symbols 96 edges 20 files 0 documented · 0%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

ASCII Animation Tutorial

This tutorial explains how to create a smooth, efficient ASCII animation in Next.js using Ghostty's animation approach.

Overview

We created a high-performance ASCII animation component that: - Uses requestAnimationFrame for smooth 60fps rendering - Loads ASCII frames from static files - Applies color overlays with CSS blend modes - Respects user motion preferences - Pauses when window loses focus

Step 1: Convert Video to ASCII Frames

1.1 Create ASCII Conversion Script

Create ascii.sh with these key configurations:

# Configuration
FONT_RATIO="0.44"
LUMINANCE_THRESHOLD=15  # Adjust to remove more/less background
ASCII_CHARS=" .'\`^,:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
OUTPUT_FPS=24
OUTPUT_COLUMNS=100

1.2 Generate ASCII Frames

./ascii.sh your-video.mp4

This creates a timestamped folder with 60 .txt files containing ASCII art.

1.3 Move Frames to Public Directory

mkdir -p public/frames
cp ascii_frames_*/frame_images/frame_*.txt public/frames/

Step 2: Create Animation Manager Class

2.1 RequestAnimationFrame-Based Timing

class AnimationManager {
  private _animation: number | null = null;
  private callback: () => void;
  private lastFrame = -1;
  private frameTime = 1000 / 30; // 30fps

  constructor(callback: () => void, fps = 30) {
    this.callback = callback;
    this.frameTime = 1000 / fps;
  }

  start() {
    if (this._animation != null) return;
    this._animation = requestAnimationFrame(this.update);
  }

  pause() {
    if (this._animation == null) return;
    this.lastFrame = -1;
    cancelAnimationFrame(this._animation);
    this._animation = null;
  }

  private update = (time: number) => {
    const { lastFrame } = this;
    let delta = time - lastFrame;
    if (this.lastFrame === -1) {
      this.lastFrame = time;
    } else {
      while (delta >= this.frameTime) {
        this.callback();
        delta -= this.frameTime;
        this.lastFrame += this.frameTime;
      }
    }
    this._animation = requestAnimationFrame(this.update);
  };
}

Step 3: Create ASCII Animation Component

3.1 Component Structure

interface ASCIIAnimationProps {
  frames?: string[];
  className?: string;
  fps?: number;
  colorOverlay?: boolean;
}

export default function ASCIIAnimation({
  frames: providedFrames,
  className = "",
  fps = 24,
  colorOverlay = false,
}: ASCIIAnimationProps) {
  const [frames, setFrames] = useState<string[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [currentFrame, setCurrentFrame] = useState(0);
  const framesRef = useRef<string[]>([]);

3.2 Frame Loading Logic

useEffect(() => {
  const loadFrames = async () => {
    if (providedFrames) {
      setFrames(providedFrames);
      framesRef.current = providedFrames;
      setIsLoading(false);
      return;
    }

    try {
      const frameFiles = Array.from(
        { length: 60 },
        (_, i) => `frame_${String(i + 1).padStart(4, "0")}.txt`,
      );

      const framePromises = frameFiles.map(async (filename) => {
        const response = await fetch(`/frames/${filename}`);
        if (!response.ok) {
          throw new Error(`Failed to fetch ${filename}: ${response.status}`);
        }
        return await response.text();
      });

      const loadedFrames = await Promise.all(framePromises);
      setFrames(loadedFrames);
      framesRef.current = loadedFrames;
      setCurrentFrame(0);
    } catch (error) {
      console.error("Failed to load ASCII frames:", error);
    } finally {
      setIsLoading(false);
    }
  };

  loadFrames();
}, [providedFrames]);

3.3 Animation Management

const [animationManager] = useState(
  () =>
    new AnimationManager(() => {
      setCurrentFrame((current) => {
        if (framesRef.current.length === 0) return current;
        return (current + 1) % framesRef.current.length;
      });
    }, fps),
);

useEffect(() => {
  if (frames.length === 0) return;

  const reducedMotion =
    window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;

  if (reducedMotion) {
    return;
  }

  const handleFocus = () => animationManager.start();
  const handleBlur = () => animationManager.pause();

  window.addEventListener("focus", handleFocus);
  window.addEventListener("blur", handleBlur);

  if (document.visibilityState === "visible") {
    animationManager.start();
  }

  return () => {
    window.removeEventListener("focus", handleFocus);
    window.removeEventListener("blur", handleBlur);
    animationManager.pause();
  };
}, [animationManager, frames.length]);

3.4 Render with Color Overlay

return (





Frame: {currentFrame + 1}/{frames.length}





      {frames[currentFrame]}

      {/* Color overlay */}
      {colorOverlay && (



      )}






);

Step 4: Integrate into Page

4.1 Import and Use Component

import ASCIIAnimation from "@/components/ascii-animation";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

export default function Home() {
  return (
    <main className="flex items-center flex-col m-auto max-w-6xl px-4">
      <Card className="mb-8">
        <CardHeader>
          <CardTitle>ASCII Animation</CardTitle>
        </CardHeader>
        <CardContent>
          <ASCIIAnimation fps={30} colorOverlay={true} />
        </CardContent>
      </Card>
    </main>
  );
}

Key Performance Optimizations

1. RequestAnimationFrame Timing

  • Uses precise frame timing calculation instead of setInterval
  • Syncs with browser's refresh rate for smooth animation

2. Efficient State Management

  • Uses useRef to avoid closure issues with frame data
  • Single state variable for current frame index

3. Smart Loading

  • Loads all frames upfront, no runtime I/O
  • Fallback mechanism for missing frames

4. Performance Features

  • Respects prefers-reduced-motion
  • Pauses animation when window loses focus
  • Uses pointer-events-none for overlays

5. CSS Blend Modes

  • color-dodge blend mode for vibrant color effects
  • Radial gradients positioned at content center
  • Minimal DOM manipulation

Customization Options

  • FPS: Adjust fps prop (24, 30, 60)
  • Colors: Modify radial gradient colors
  • Blend Modes: Try color, overlay, screen, multiply
  • Threshold: Adjust LUMINANCE_THRESHOLD in ascii.sh to remove more/less background
  • Size: Modify OUTPUT_COLUMNS for different resolutions

File Structure

project/
├── ascii.sh                          # Video conversion script
├── public/frames/                     # ASCII frame files
│   ├── frame_0001.txt
│   ├── frame_0002.txt
│   └── ...
├── components/ascii-animation.tsx     # Main component
└── app/page.tsx                      # Integration

This approach delivers the same buttery-smooth performance as Ghostty's terminal animations while being highly customizable for different visual effects.

Extension points exported contracts — how you extend this code

GlowAreaProps (Interface)
(no doc)
components/glow.tsx
VideoASCIICoinProps (Interface)
(no doc)
components/video-ascii-coin.tsx
ASCIIAnimationProps (Interface)
(no doc)
components/ascii-animation.tsx
ButtonProps (Interface)
(no doc)
components/ui/button.tsx
GlowProps (Interface)
(no doc)
components/glow.tsx

Core symbols most depended-on inside this repo

cn
called by 20
lib/utils.ts
start
called by 2
components/ascii-animation.tsx
pause
called by 2
components/ascii-animation.tsx
deleteBox
called by 2
components/boxes.tsx
updateGlow
called by 1
components/glow.tsx
loadFrames
called by 1
components/video-ascii-coin.tsx
updateFPS
called by 1
components/ascii-animation.tsx
loadFrames
called by 1
components/ascii-animation.tsx

Shape

Function 28
Interface 5
Method 4
Class 2

Languages

TypeScript100%

Modules by API surface

components/ascii-animation.tsx11 symbols
components/glow.tsx7 symbols
components/video-ascii-coin.tsx4 symbols
components/boxes.tsx3 symbols
components/transition-provider.tsx2 symbols
app/ascii-animation/page.tsx2 symbols
lib/utils.ts1 symbols
components/ui/dropdown-menu.tsx1 symbols
components/ui/button.tsx1 symbols
components/theme-provider.tsx1 symbols
components/nav.tsx1 symbols
components/glow-cards.tsx1 symbols

For agents

$ claude mcp add react-gradient-glow \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact