Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
ofershap avatar

Capture Demo Gif

  • 1 installs
  • 12 repo stars
  • Updated May 22, 2026
  • ofershap/readme-builder

Helps with ai & agent building tasks.

About

capture-demo-gif is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.

  • capture-demo-gif
  • AI & Agent Building
  • AI-coding skill

Capture Demo Gif by the numbers

  • 1 all-time installs (skills.sh)
  • Ranked #14,098 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ofershap/readme-builder --skill capture-demo-gif

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1
repo stars12
Last updatedMay 22, 2026
Repositoryofershap/readme-builder

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

Capture Demo GIF for a Web App

Record a scripted walkthrough of a web application as an animated GIF for README embedding. Uses headless Playwright for screenshots and ffmpeg for GIF encoding.

Prerequisites

  • Node.js 20+
  • ffmpeg installed (brew install ffmpeg)
  • Dev server running for the target app

Workflow

1. Plan the Demo Scenes

Before writing code, decide which interactions to show. A good demo GIF has 8-15 frames covering:

  • Initial state (app loaded)
  • 2-3 key interactions (click, type, navigate)
  • A result/output state
  • Total duration: 15-25 seconds

2. Write the Capture Script

Create a file at <project>/demo/capture.mjs:

import { chromium } from 'playwright';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { mkdirSync, rmSync, statSync } from 'fs';
import { execSync } from 'child_process';

const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = join(__dirname, '..');
const FRAMES_DIR = join(PROJECT_ROOT, 'demo', '.frames');
const OUTPUT = join(PROJECT_ROOT, 'assets', 'demo.gif');

const fps = 0.65;       // ~1.5s per frame
const gifWidth = 960;   // output width in px
const WIDTH = 1280;     // viewport width
const HEIGHT = 720;     // viewport height
const BASE = process.env.DEMO_URL || 'http://localhost:5173/';

let frameNum = 0;
async function snap(page, label, ms = 600) {
  await page.waitForTimeout(ms);
  frameNum++;
  const f = `frame_${String(frameNum).padStart(2, '0')}.png`;
  await page.screenshot({ path: join(FRAMES_DIR, f), type: 'png' });
  console.log(`  [${f}] ${label}`);
}

async function main() {
  mkdirSync(FRAMES_DIR, { recursive: true });

  const browser = await chromium.launch({ headless: true });
  const page = await (await browser.newContext({
    viewport: { width: WIDTH, height: HEIGHT },
    deviceScaleFactor: 2,
  })).newPage();

  await page.goto(BASE, { waitUntil: 'networkidle' });
  await page.waitForTimeout(1500);

  // --- SCENES: customize per project ---
  await snap(page, 'Initial state', 300);
  // Add interactions here: page.click(), page.fill(), page.locator()...
  // --- END SCENES ---

  console.log(`\n${frameNum} frames captured`);
  await browser.close();

  // Stitch into GIF
  const filter = [
    `scale=${gifWidth}:-1:flags=lanczos`,
    'split[s0][s1]',
    '[s0]palettegen=max_colors=128:stats_mode=diff[p]',
    '[s1][p]paletteuse=dither=bayer:bayer_scale=3',
  ].join(',');

  execSync(
    `ffmpeg -y -framerate ${fps} -i "${join(FRAMES_DIR, 'frame_%02d.png')}" -vf "${filter}" "${OUTPUT}"`,
    { stdio: 'inherit' },
  );

  const size = (statSync(OUTPUT).size / 1024).toFixed(0);
  console.log(`\n${OUTPUT} (${size} KB)`);

  rmSync(FRAMES_DIR, { recursive: true, force: true });
}

main().catch(err => { console.error(err); process.exit(1); });

3. Key Settings

SettingDefaultNotes
deviceScaleFactor2Retina-quality screenshots. Use 1 for smaller files
fps0.65Frames per second in GIF. Lower = longer per frame. 0.5 = 2s/frame, 1 = 1s/frame
gifWidth960Output GIF width. Source is 1280×720 at 2× DPR
max_colors128GIF palette size. Lower = smaller file, worse gradients
Viewport1280×720Standard 720p. Don't go smaller or UI elements get cramped

4. Common Playwright Actions for Demos

// Click by text
await page.locator('button:has-text("Save")').click();

// Click nth element in a list
await page.locator('.item-list > div').nth(2).click();

// Type into input
const input = page.locator('input[placeholder="Search"]');
await input.fill('hello world');

// Scroll to bottom
const panel = page.locator('.scrollable-area');
await panel.evaluate(el => el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }));

// Wait for animation/transition
await page.waitForTimeout(800);

// Toggle a button
await page.locator('button[title="Dark mode"]').click();

5. Install, Run, Embed

# Install playwright (don't save to package.json)
cd <project>
npm install --no-save playwright

# Start dev server in another terminal, then:
node demo/capture.mjs

Add to .gitignore:

demo/.frames

Embed in README:

![Demo](assets/demo.gif)

6. Troubleshooting

GIF too large (>2MB)?

  • Reduce gifWidth to 640
  • Increase fps (fewer seconds = fewer unique frames needed)
  • Reduce max_colors to 64
  • Remove frames that don't add value

Screenshots blank/wrong?

  • Increase waitForTimeout before first snap() -- app may not be fully rendered
  • Check if the dev server URL is correct (some apps use a base path)
  • Try headless: false to see what Playwright sees

Selectors not matching?

  • Use page.locator('text=Visible Text') for the most robust selectors
  • Use await page.pause() with headless: false to inspect the DOM
  • Avoid CSS class selectors that include Tailwind hashes

When NOT to Use This

  • Terminal/CLI tools → Use the render-remotion-animation skill with the CliDemo template instead
  • Library code demos → Use render-remotion-animation with CodeBeforeAfter template
  • Interactive web apps with drag-drop, live preview, UI flows → Use this skill

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.