{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "grid-pixelate-wipe", "title": "Grid Pixelate Wipe", "description": "Dissolve from one scene to the next through a deterministic grid of mask cells.", "dependencies": [ "remotion" ], "files": [ { "path": "registry/remocn/grid-pixelate-wipe/index.tsx", "content": "\"use client\";\n\nimport { type ReactNode, useMemo } from \"react\";\nimport { interpolate, useCurrentFrame, useVideoConfig } from \"remotion\";\n\nexport interface GridPixelateWipeProps {\n from?: ReactNode;\n to?: ReactNode;\n cols?: number;\n rows?: number;\n pattern?: \"wave\" | \"diagonal\" | \"spiral\";\n transitionStart?: number;\n transitionDuration?: number;\n cellFadeFrames?: number;\n speed?: number;\n className?: string;\n}\n\nconst FONT_FAMILY =\n \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\n\nfunction DefaultPanel({ label, color }: { label: string; color: string }) {\n return (\n \n {label}\n \n );\n}\n\nfunction spiralIndices(cols: number, rows: number): number[][] {\n const grid: number[][] = Array.from({ length: rows }, () =>\n Array(cols).fill(0),\n );\n let top = 0;\n let bottom = rows - 1;\n let left = 0;\n let right = cols - 1;\n let i = 0;\n while (top <= bottom && left <= right) {\n for (let x = left; x <= right; x++) grid[top][x] = i++;\n top++;\n for (let y = top; y <= bottom; y++) grid[y][right] = i++;\n right--;\n if (top <= bottom) {\n for (let x = right; x >= left; x--) grid[bottom][x] = i++;\n bottom--;\n }\n if (left <= right) {\n for (let y = bottom; y >= top; y--) grid[y][left] = i++;\n left--;\n }\n }\n return grid;\n}\n\nexport function GridPixelateWipe({\n from,\n to,\n cols = 12,\n rows = 7,\n pattern = \"wave\",\n transitionStart,\n transitionDuration = 30,\n cellFadeFrames = 4,\n speed = 1,\n className,\n}: GridPixelateWipeProps) {\n const frame = useCurrentFrame() * speed;\n const { durationInFrames } = useVideoConfig();\n\n const start =\n typeof transitionStart === \"number\"\n ? transitionStart\n : Math.floor(durationInFrames * 0.4);\n\n const delays = useMemo(() => {\n const raw: number[][] = Array.from({ length: rows }, () =>\n Array(cols).fill(0),\n );\n const spiral = pattern === \"spiral\" ? spiralIndices(cols, rows) : null;\n for (let y = 0; y < rows; y++) {\n for (let x = 0; x < cols; x++) {\n if (pattern === \"wave\") {\n raw[y][x] = Math.hypot(x - (cols - 1) / 2, y - (rows - 1) / 2);\n } else if (pattern === \"diagonal\") {\n raw[y][x] = x + y;\n } else if (spiral) {\n raw[y][x] = spiral[y][x];\n }\n }\n }\n\n let min = Infinity;\n let max = -Infinity;\n for (let y = 0; y < rows; y++) {\n for (let x = 0; x < cols; x++) {\n if (raw[y][x] < min) min = raw[y][x];\n if (raw[y][x] > max) max = raw[y][x];\n }\n }\n\n const span = Math.max(0, transitionDuration - cellFadeFrames);\n const range = max - min || 1;\n const normalized: number[][] = Array.from({ length: rows }, () =>\n Array(cols).fill(0),\n );\n for (let y = 0; y < rows; y++) {\n for (let x = 0; x < cols; x++) {\n normalized[y][x] = ((raw[y][x] - min) / range) * span;\n }\n }\n return normalized;\n }, [cols, rows, pattern, transitionDuration, cellFadeFrames]);\n\n const fromContent = from ?? ;\n const toContent = to ?? ;\n\n const cells: ReactNode[] = [];\n for (let y = 0; y < rows; y++) {\n for (let x = 0; x < cols; x++) {\n const delay = delays[y][x];\n const opacity =\n frame < start\n ? 1\n : interpolate(\n frame,\n [start + delay, start + delay + cellFadeFrames],\n [1, 0],\n {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n },\n );\n cells.push(\n ,\n );\n }\n }\n\n return (\n \n
{toContent}
\n \n {cells}\n \n \n {fromContent}\n \n \n );\n}\n", "type": "registry:component", "target": "components/remocn/grid-pixelate-wipe.tsx" } ], "type": "registry:component" }