storyboard-canvas
React Flow canvas preconfigured for storyboarding — custom shot nodes with frame image, description, duration, and camera angle, with a side panel for regeneration. Use this skill when the user says "add storyboard", "storyboard canvas", "shot list canvas", "visual storyboard", or "react flow storyboard".
What this skill does
# Storyboard Canvas
A React Flow canvas tailored for video/film storyboarding. Each shot is a draggable node showing its frame image (or placeholder), description, duration, and camera angle. Clicking a node opens a right-side panel for editing metadata, regenerating the frame with a prompt, and browsing image history.
## Prerequisites
- Next.js app with App Router (no `src/` directory)
- `@xyflow/react` installed (from the `react-flow` skill)
- shadcn/ui installed (from the `add-shadcn` skill)
- `@phosphor-icons/react` installed
## Installation
```bash
bun add @xyflow/react
bun add @phosphor-icons/react
bunx shadcn@latest add button textarea badge scroll-area
```
## What Gets Created
```
components/
└── storyboard/
├── storyboard-canvas.tsx
├── storyboard-node.tsx
└── shot-panel.tsx
lib/
└── storyboard/
└── types.ts
```
## Setup Steps
### Step 1: Create `lib/storyboard/types.ts`
```typescript
import type { Edge, Node } from "@xyflow/react"
export type ShotStatus = "pending" | "generating" | "done" | "error"
export type ShotData = {
shotNumber: number
description: string
cameraAngle: string
duration: number
imageUrl?: string
imageHistory?: string[]
status: ShotStatus
notes?: string
}
export type StoryboardShot = Node<ShotData>
export type StoryboardEdge = Edge
export type StoryboardCanvasProps = {
shots: ShotData[]
onShotsChange?: (shots: ShotData[]) => void
onRegenerateShot?: (shotId: string, prompt: string) => Promise<string>
onExportPdf?: () => void
onShareLink?: () => void
className?: string
}
```
### Step 2: Create `components/storyboard/storyboard-node.tsx`
```typescript
"use client"
import type { ShotData } from "@/lib/storyboard/types"
import { Camera } from "@phosphor-icons/react"
import { Handle, Position, type NodeProps } from "@xyflow/react"
export function StoryboardNode({ data, selected }: NodeProps<{ data: ShotData } & Record<string, unknown>>) {
const shotData = data as unknown as ShotData
return (
<div
className={[
"w-[300px] rounded-xl bg-white shadow-md border transition-all select-none",
selected ? "border-blue-500 ring-2 ring-blue-300" : "border-zinc-200",
].join(" ")}
>
<Handle type="target" position={Position.Left} className="!bg-zinc-400" />
{/* Header */}
<div className="flex items-center justify-between px-3 pt-3 pb-1">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-500 text-[11px] font-bold text-white">
{shotData.shotNumber}
</span>
<StatusDot status={shotData.status} />
</div>
{/* Frame */}
<div className="mx-3 h-[168px] overflow-hidden rounded-lg bg-zinc-100">
{shotData.status === "generating" ? (
<div className="h-full w-full animate-pulse bg-gradient-to-r from-zinc-200 via-zinc-100 to-zinc-200" />
) : shotData.imageUrl ? (
<img
src={shotData.imageUrl}
alt={`Shot ${shotData.shotNumber}`}
className="h-full w-full object-cover"
/>
) : (
<div className="flex h-full w-full flex-col items-center justify-center gap-2 text-zinc-400">
<Camera size={36} weight="thin" />
<span className="text-xs">No frame yet</span>
</div>
)}
</div>
{/* Meta */}
<div className="px-3 pb-3 pt-2">
<p className="line-clamp-2 text-[13px] leading-snug text-zinc-700">
{shotData.description || <span className="italic text-zinc-400">No description</span>}
</p>
<div className="mt-2 flex items-center gap-2">
<span className="rounded-full bg-zinc-100 px-2 py-0.5 text-[11px] font-medium text-zinc-600">
{shotData.duration}s
</span>
<span className="rounded-full bg-blue-50 px-2 py-0.5 text-[11px] font-medium text-blue-600">
{shotData.cameraAngle}
</span>
</div>
</div>
<Handle type="source" position={Position.Right} className="!bg-zinc-400" />
</div>
)
}
function StatusDot({ status }: { status: ShotData["status"] }) {
const colors: Record<ShotData["status"], string> = {
pending: "bg-zinc-300",
generating: "bg-yellow-400 animate-pulse",
done: "bg-green-400",
error: "bg-red-400",
}
return <span className={`block h-2.5 w-2.5 rounded-full ${colors[status]}`} />
}
```
### Step 3: Create `components/storyboard/shot-panel.tsx`
```typescript
"use client"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
import type { ShotData } from "@/lib/storyboard/types"
import { ArrowCounterClockwise, X } from "@phosphor-icons/react"
import { useId, useState } from "react"
type ShotPanelProps = {
shotId: string
data: ShotData
onClose: () => void
onChange: (updated: Partial<ShotData>) => void
onRegenerate?: (shotId: string, prompt: string) => Promise<string>
}
export function ShotPanel({ shotId, data, onClose, onChange, onRegenerate }: ShotPanelProps) {
const [prompt, setPrompt] = useState("")
const [isRegenerating, setIsRegenerating] = useState(false)
const historyId = useId()
async function handleRegenerate() {
if (!onRegenerate || !prompt.trim()) return
setIsRegenerating(true)
try {
const newUrl = await onRegenerate(shotId, prompt)
const history = [...(data.imageHistory ?? []), ...(data.imageUrl ? [data.imageUrl] : [])]
onChange({ imageUrl: newUrl, imageHistory: history.slice(-3), status: "done" })
setPrompt("")
} catch {
onChange({ status: "error" })
} finally {
setIsRegenerating(false)
}
}
function restoreFromHistory(url: string) {
const current = data.imageUrl
const history = (data.imageHistory ?? []).filter((u) => u !== url)
if (current) history.push(current)
onChange({ imageUrl: url, imageHistory: history.slice(-3) })
}
return (
<div className="flex h-full w-[380px] flex-col border-l border-zinc-200 bg-white">
{/* Header */}
<div className="flex items-center justify-between border-b border-zinc-100 px-5 py-4">
<div>
<p className="text-xs font-medium text-zinc-400 uppercase tracking-wide">Shot {data.shotNumber}</p>
<h2 className="text-sm font-semibold text-zinc-800">{data.cameraAngle}</h2>
</div>
<button
onClick={onClose}
className="flex h-7 w-7 items-center justify-center rounded-md text-zinc-400 hover:bg-zinc-100 hover:text-zinc-700 transition-colors"
aria-label="Close panel"
>
<X size={16} />
</button>
</div>
{/* Scrollable body */}
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-5">
{/* Description */}
<section>
<label className="mb-1.5 block text-xs font-medium text-zinc-600">Description</label>
<Textarea
value={data.description}
onChange={(e) => onChange({ description: e.target.value })}
rows={3}
className="resize-none text-sm"
placeholder="Describe the shot..."
/>
</section>
{/* Duration stepper */}
<section>
<label className="mb-1.5 block text-xs font-medium text-zinc-600">Duration</label>
<div className="flex items-center gap-3">
<button
onClick={() => onChange({ duration: Math.max(1, data.duration - 1) })}
className="flex h-8 w-8 items-center justify-center rounded-md border border-zinc-200 text-zinc-600 hover:bg-zinc-50 transition-colors font-medium"
>
−
</button>
<span className="text-sm font-semibold text-zinc-800 w-8 text-center">{data.duration}s</span>
<button
onClick={() => onChange({ duration: data.duration + 1 })}
className="flex h-8 w-8 items-center justify-center rounded-md Related in Image & Video
watch
IncludedWatch a video (URL or local path). Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls the transcript from captions (or Whisper API fallback), and hands the result to Claude so it can answer questions about what's in the video.
physical-ai-defect-image-generation
IncludedUse when the user wants to orchestrate defect image generation, run associated setup, or handle outputs on OSMO. The Day 0 path handles cold-start with USD-to-ROI, image-edit augmentation, and AnomalyGen to create initial PCBA datasets. The Day 1 path performs inference and labeling on real images. This skill helps with first-time asset setup, creation of finetuning checkpoints, and configuring deployment. Trigger keywords: defect image generation, dig workflow, dig pipeline, defect image detection workflow, aoi pipeline, aoi anomalygen, usd2roi anomalygen, day 0 pcba, day 1 pcba, day 1 real-photo alignment, day 1 manual roi, metal surface anomaly, glass defect, anomalygen finetune, setup_pcb, setup_metal, setup_glass, setup_pretrained, dig setup, dig datasets, dig pretrained checkpoint, dig image-edit endpoint.
accelint-react-best-practices
IncludedReact performance optimization and best practices. ALWAYS use this skill when working with any React code - writing components, hooks, JSX; refactoring; optimizing re-renders, memoization, state management; reviewing for performance; fixing hydration mismatches; debugging infinite re-renders, stale closures, input focus loss, animations restarting; preventing remounting; implementing transitions, lazy initialization, effect dependencies. Even simple React tasks benefit from these patterns. Covers React 19+ (useEffectEvent, Activity, ref props). Triggers - useEffect, useState, useMemo, useCallback, memo, inline components, nested components, components inside components, re-render, performance, hydration, SSR, Next.js, useDeferredValue, combined hooks.
elevenlabs-agents
IncludedBuild conversational AI voice agents with ElevenLabs Platform using React, JavaScript, React Native, or Swift SDKs. Configure agents, tools (client/server/MCP), RAG knowledge bases, multi-voice, and Scribe real-time STT. Use when: building voice chat interfaces, implementing AI phone agents with Twilio, configuring agent workflows or tools, adding RAG knowledge bases, testing with CLI "agents as code", or troubleshooting deprecated @11labs packages, Android audio cutoff, CSP violations, dynamic variables, or WebRTC config. Keywords: ElevenLabs Agents, ElevenLabs voice agents, AI voice agents, conversational AI, @elevenlabs/react, @elevenlabs/client, @elevenlabs/react-native, @elevenlabs/elevenlabs-js, @elevenlabs/agents-cli, elevenlabs SDK, voice AI, TTS, text-to-speech, ASR, speech recognition, turn-taking model, WebRTC voice, WebSocket voice, ElevenLabs conversation, agent system prompt, agent tools, agent knowledge base, RAG voice agents, multi-voice agents, pronunciation dictionary, voice speed control, elevenlabs scribe, @11labs deprecated, Android audio cutoff, CSP violation elevenlabs, dynamic variables elevenlabs, case-sensitive tool names, webhook authentication
humanizer
IncludedHumanize AI-generated text by detecting and removing patterns typical of LLM output. Rewrites text to sound natural, specific, and human. Uses 28 pattern detectors, 560+ AI vocabulary terms across 3 tiers, and statistical analysis (burstiness, type-token ratio, readability) for comprehensive detection. Use when asked to humanize text, de-AI writing, make content sound more natural/human, review writing for AI patterns, score text for AI detection, or improve AI-generated drafts. Covers content, language, style, communication, and filler categories.
generating-mermaid-diagrams
IncludedSalesforce architecture diagrams using Mermaid with ASCII fallback. Use this skill when generating text-based diagrams for Salesforce architecture, OAuth flows, ERDs, integration sequences, or Agentforce structure. TRIGGER when: user says "diagram", "visualize", "ERD", or asks for sequence diagrams, flowcharts, class diagrams, or architecture visualizations in Mermaid. DO NOT TRIGGER when: user wants PNG/SVG image output (use generating-visual-diagrams), or asks about non-Salesforce systems.