Skip to main content

session-replay

Create a codevibing session replay — an animated canvas-based video that replays a Claude Code building session with real prompts, AI responses, and project images. Use when asked to create a session, session replay, or codevibing video for a project.

Source facts

Repository
JDerekLomas/claude-code-skills
Last source activity
February 11, 2026 at 11:28
Detected SKILL.md language
English
Stars
1
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
session-replay
description
Create a codevibing session replay — an animated canvas-based video that replays a Claude Code building session with real prompts, AI responses, and project images. Use when asked to create a session, session replay, or codevibing video for a project.
# Session Replay Creation Create cinematic canvas-animated replays of Claude Code sessions for codevibing.com. Each session is a standalone HTML file that autoplays a timeline of typed prompts, AI responses, and image reveals. ## Overview 1. Find the project and extract real prompts from Claude history 2. Copy 6-8 representative images to `public/<slug>/` 3. Write the session HTML to `public/sessions/<slug>.html` 4. Add the session to the gallery in `src/app/sessions/page.tsx` ## Step 1: Find Prompts Search Claude history JSONL files for the project: ```bash ls -lhS ~/.claude/projects/-Users-dereklomas-<project-path>/ ``` Extract human messages from the largest JSONL file(s): ```python import json fname = '<session-id>.jsonl' with open(fname) as f: for line in f: obj = json.loads(line) if obj.get('type') == 'user': msg = obj.get('message', {}) content = msg.get('content', '') if isinstance(content, list): text = ' '.join(p.get('text','') for p in content if isinstance(p, dict) and p.get('type') == 'text') else: text = str(content) text = text.strip() if len(text) > 10 and not text.startswith(('<task-', '<local-command', '<command-', '[Request interrupted')): ts = obj.get('timestamp', '') print(f'[{ts}] {text[:120]}') ``` Curate to ~15-22 prompts that tell the narrative arc. Look for: - The genesis prompt (what kicked it off) - Key creative decisions and pivots - Design critiques and iterations - Review interfaces (thumbs up/down, card review UIs, approval workflows) - Deployment moments (github, vercel, domain setup) - New websites going live (show as `ai` events: "Deployed to project.vercel.app") - The "aha" moments **Important**: Don't skip process moments like review UIs, feedback loops, and deployments — these show the human-AI collaboration workflow that makes sessions interesting. Also check git log for context: `git -C ~/path/to/project log --oneline` ## Step 2: Copy Images Pick 6-8 representative images from the project that show the visual output. Copy them to the codevibing2 public directory: ```bash mkdir -p ~/codevibing2/public/<slug>/ cp ~/project/path/to/images/*.png ~/codevibing2/public/<slug>/ ``` Choose images that: - Show different stages or categories of the project - Work well as backgrounds (title cards) and in 2x2 grids - Are visually distinct from each other ## Step 3: Write the Session HTML Create `public/sessions/<slug>.html` using this template. Customize the marked sections. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- CUSTOMIZE: title and meta tags --> <title>PROJECT_TITLE — codevibing session</title> <meta property="og:title" content="PROJECT_TITLE — SUBTITLE"> <meta property="og:description" content="Watch DESCRIPTION get built with AI"> <meta name="twitter:card" content="summary_large_image"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #08080c; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; font-family: -apple-system, sans-serif; color: white; } canvas { border-radius: 12px; max-width: 100%; height: auto; cursor: pointer; } .back { position: fixed; top: 20px; left: 20px; color: #6a6a80; text-decoration: none; font-size: 14px; z-index: 10; } .back:hover { color: #f0ece4; } </style> </head> <body> <a href="/sessions" class="back">&larr; all sessions</a> <canvas id="canvas" width="1280" height="720"></canvas> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const W = 1280, H = 720; // CUSTOMIZE: accent and gold colors to match project aesthetic const C = { bg: '#08080c', surface: '#101018', border: '#1c1c2c', text: '#f0ece4', dim: '#7a7068', prompt: '#6ee7b7', ai: '#93c5fd', code: '#fbbf24', accent: '#c4956a', // CUSTOMIZE: project accent color violet: '#a78bfa', rust: '#c67b5c', lapis: '#4a7bbd', gold: '#C4A35A', // CUSTOMIZE: project gold/secondary color }; // CUSTOMIZE: image sources from public/<slug>/ const images = {}; const imageSources = { img1: '/<slug>/image1.png', img2: '/<slug>/image2.png', // ... 6-8 images }; let imagesLoaded = 0; Object.entries(imageSources).forEach(([key, src]) => { const img = new Image(); img.onload = () => { imagesLoaded++; }; img.onerror = () => { imagesLoaded++; }; img.src = src; images[key] = img; }); // CUSTOMIZE: the entire events timeline const events = [ // --- TITLE CARD (0-5s) --- { t: 0, type: 'title-card', bgImage: 'img1', title: 'PROJECT_TITLE', subtitle: 'SUBTITLE', meta: 'built with Claude Code · DATE' }, // --- TERMINAL SESSION --- { t: 5000, type: 'terminal-start' }, { t: 5500, type: 'prompt', text: "the first real prompt from the user" }, { t: 8500, type: 'prompt', text: "second prompt" }, { t: 11000, type: 'ai', text: "AI response summary" }, // --- IMAGE REVEAL (full-screen single image) --- { t: 14000, type: 'image-reveal', key: 'img2', label: 'Image Title · Detail' }, // --- RETURN TO TERMINAL --- { t: 20000, type: 'terminal-resume' }, { t: 20500, type: 'prompt', text: "next prompt continues the story" }, // --- IMAGE GRID (2x2 grid of 4 images) --- { t: 28000, type: 'image-grid', keys: ['img1', 'img2', 'img3', 'img4'] }, // --- END TITLE CARD --- { t: 36000, type: 'title-card', bgImage: 'img3', title: 'PROJECT_TITLE', subtitle: 'project-url.vercel.app', meta: 'built with Claude Code' }, { t: 42000, type: 'end' }, ]; let startTime = null; let animFrame = null; let currentElapsed = 0; let animRunning = false; let zoom = 1, zoomTarget = 1, panY = 0, panYTarget = 0; function lerp(a, b, t) { return a + (b - a) * Math.min(Math.max(t, 0), 1); } function easeOut(t) { return 1 - Math.pow(1 - Math.min(t, 1), 3); } function drawRoundRect(x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.quadraticCurveTo(x + w, y, x + w, y + r); ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h); ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath(); } function drawImageCover(img, x, y, w, h) { if (!img || !img.complete || !img.naturalWidth) return; const imgR = img.naturalWidth / img.naturalHeight, boxR = w / h; let sx = 0, sy = 0, sw = img.naturalWidth, sh = img.naturalHeight; if (imgR > boxR) { sw = sh * boxR; sx = (img.naturalWidth - sw) / 2; } else { sh = sw / boxR; sy = (img.naturalHeight - sh) / 2; } ctx.drawImage(img, sx, sy, sw, sh, x, y, w, h); } function getActiveMode(elapsed) { const visible = events.filter(e => e.t <= elapsed); for (let i = visible.length - 1; i >= 0; i--) { const t = visible[i].type; if (t === 'title-card') return 'title-card'; if (t === 'image-reveal') return 'image-reveal'; if (t === 'image-grid') return 'image-grid'; if (t === 'terminal-resume' || t === 'terminal-start') return 'terminal'; if (['prompt','ai','code'].includes(t)) return 'terminal'; } return 'title-card'; } function drawTitleCard(elapsed) { const cards = events.filter(e => e.t <= elapsed && e.type === 'title-card'); const card = cards[cards.length - 1]; if (!card) return; const age = elapsed - card.t; const img = images[card.bgImage]; if (img && img.complete && img.naturalWidth) { ctx.globalAlpha = easeOut(Math.min(age / 1200, 1)); drawImageCover(img, 0, 0, W, H); const ov = ctx.createLinearGradient(0, 0, 0, H); ov.addColorStop(0, 'rgba(8,8,12,0.4)'); ov.addColorStop(0.4, 'rgba(8,8,12,0.3)'); ov.addColorStop(0.7, 'rgba(8,8,12,0.6)'); ov.addColorStop(1, 'rgba(8,8,12,0.85)'); ctx.fillStyle = ov; ctx.fillRect(0, 0, W, H); ctx.globalAlpha = 1; } ctx.globalAlpha = Math.max(easeOut(Math.min((age - 500) / 800, 1)), 0); const tx = 80, ty = H - 120; ctx.shadowColor = 'rgba(0,0,0,0.7)'; ctx.shadowBlur = 24; ctx.font = '700 72px -apple-system, sans-serif'; ctx.fillStyle = '#fff'; ctx.textAlign = 'left'; ctx.fillText(card.title, tx, ty); ctx.font = '300 28px -apple-system, sans-serif'; ctx.fillStyle = C.gold; ctx.shadowBlur = 12; ctx.fillText(card.subtitle, tx, ty + 42); if (card.meta) { ctx.font = '400 15px -apple-system, sans-serif'; ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.shadowBlur = 6; ctx.fillText(card.meta, tx, ty + 72); } ctx.shadowBlur = 0; ctx.globalAlpha = 1; } function drawTerminal(elapsed) { zoom = lerp(zoom, zoomTarget, 0.06); panY = lerp(panY, panYTarget, 0.06); ctx.save(); ctx.translate(W/2, H/2); ctx.scale(zoom, zoom); ctx.translate(-W/2, -H/2 + panY); const tX = 60, tY = 30, tW = W - 120, tH = H - 60; drawRoundRect(tX, tY, tW, tH, 14); ctx.fillStyle = C.surface; ctx.fill(); ctx.strokeStyle = C.border; ctx.lineWidth = 1; ctx.stroke(); drawRoundRect(tX, tY, tW, 44, 14); ctx.fillStyle = '#141420'; ctx.fill(); ctx.fillStyle = C.surface; ctx.fillRect(tX + 1, tY + 30, tW - 2, 14); const dY = tY + 22; [[tX+22,'#ff5f57'],[tX+42,'#ffbd2e'],[tX+62,'#28c840']].forEach(([dx,c]) => { ctx.beginPath(); ctx.arc(dx, dY, 6, 0, Math.PI*2); ctx.fillStyle = c; ctx.globalAlpha = 0.85; ctx.fill(); ctx.globalAlpha = 1; }); // CUSTOMIZE: terminal title bar path ctx.font = '500 13px "SF Mono", monospace'; ctx.fillStyle = C.dim; ctx.fillText('~/project-path · claude', tX + 86, dY + 4); const secs = Math.floor(elapsed / 1000);
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub