{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "brick-mosaic",
  "title": "Brick Mosaic",
  "description": "Toy bricks drop in row by row from the bottom with a tiny bounce, rebuilding a pixel-art logo on a baseplate.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/brick-mosaic/index.tsx",
      "content": "\"use client\";\n\nimport { useId, useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  random,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type BrickMosaicProps = {\n  /**\n   * Pixel art to build. Either rows of characters looked up in `colors`\n   * (unknown characters and spaces leave the cell empty) or a 2D grid of CSS\n   * colors (\"\" for empty). Overrides `cols` and `rows`.\n   */\n  pattern?: string[] | string[][];\n  /** Grid size of the default image. */\n  cols?: number;\n  rows?: number;\n  /** Character to color map for string patterns. */\n  colors?: Record<string, string>;\n  /** Baseplate color behind the bricks. */\n  background?: string;\n  /** Frames between rows. */\n  rowDelay?: number;\n  speed?: number;\n};\n\nexport const brickMosaicDefaults = {\n  cols: 24,\n  rows: 14,\n  colors: {\n    \"#\": \"#E5322D\",\n    \".\": \"#FFFFFF\",\n    x: \"#111111\",\n  } as Record<string, string>,\n  background: \"#111111\",\n  rowDelay: 3,\n  speed: 1,\n};\n\nconst FALL = 7;\nconst HOP = 5;\nconst clamp = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\n\nfunction defaultPattern(cols: number, rows: number): string[] {\n  const cx = (cols - 1) / 2;\n  const cy = (rows - 1) / 2;\n  const r = Math.min(cols, rows) * 0.4;\n  return Array.from({ length: rows }, (_, y) =>\n    Array.from({ length: cols }, (_, x) =>\n      Math.hypot(x - cx, y - cy) <= r ? \"#\" : \".\",\n    ).join(\"\"),\n  );\n}\n\nexport function getBrickMosaicLength(rows: number, cols: number, rowDelay = 3) {\n  return Math.ceil((rows - 1) * rowDelay + cols * 0.12 + 1.5 + FALL + HOP);\n}\n\nexport function BrickMosaic({\n  pattern,\n  cols = brickMosaicDefaults.cols,\n  rows = brickMosaicDefaults.rows,\n  colors = brickMosaicDefaults.colors,\n  background = brickMosaicDefaults.background,\n  rowDelay = brickMosaicDefaults.rowDelay,\n  speed = brickMosaicDefaults.speed,\n}: BrickMosaicProps) {\n  const frame = useCurrentFrame();\n  const { width: W, height: H, fps } = useVideoConfig();\n  const t = (frame / fps) * 30 * speed;\n  const id = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n\n  const grid = useMemo(() => {\n    const source = pattern ?? defaultPattern(cols, rows);\n    return source.map((row) =>\n      (typeof row === \"string\" ? row.split(\"\") : row).map((cell) =>\n        typeof row === \"string\" ? (colors[cell] ?? \"\") : cell,\n      ),\n    );\n  }, [pattern, cols, rows, colors]);\n  const nRows = grid.length;\n  const nCols = Math.max(1, ...grid.map((row) => row.length));\n  const s = Math.min((W * 0.86) / nCols, (H * 0.86) / nRows);\n  const ox = (W - s * nCols) / 2;\n  const oy = (H - s * nRows) / 2;\n  const gap = s * 0.04;\n  const bevel = s * 0.09;\n  const stud = s * 0.28;\n\n  const bricks = grid.flatMap((row, r) =>\n    row.map((color, col) => {\n      if (!color) return null;\n      const fromBottom = nRows - 1 - r;\n      const start =\n        fromBottom * rowDelay + col * 0.12 + random(`brick-${r}-${col}`) * 1.5;\n      const local = t - start;\n      if (local < 0) return null;\n      let y = 0;\n      if (local < FALL) {\n        const p = local / FALL;\n        y = -s * 1.8 * (1 - p * p);\n      } else if (local < FALL + HOP) {\n        const p = (local - FALL) / HOP;\n        y = -s * 0.12 * 4 * p * (1 - p);\n      }\n      const opacity = interpolate(local, [0, 2], [0, 1], clamp);\n      const x0 = ox + col * s + gap / 2;\n      const y0 = oy + r * s + gap / 2 + y;\n      const b = s - gap;\n      return (\n        <g\n          key={`${r}-${col}`}\n          transform={`translate(${x0} ${y0})`}\n          opacity={opacity}\n        >\n          <rect width={b} height={b} rx={s * 0.06} fill={color} />\n          <path\n            d={`M 0 ${b} V 0 H ${b} L ${b - bevel} ${bevel} H ${bevel} V ${b - bevel} Z`}\n            fill=\"#FFFFFF\"\n            opacity={0.22}\n          />\n          <path\n            d={`M ${b} 0 V ${b} H 0 L ${bevel} ${b - bevel} H ${b - bevel} V ${bevel} Z`}\n            fill=\"#000000\"\n            opacity={0.22}\n          />\n          <circle\n            cx={b / 2 + s * 0.035}\n            cy={b / 2 + s * 0.05}\n            r={stud}\n            fill=\"#000000\"\n            opacity={0.28}\n          />\n          <circle cx={b / 2} cy={b / 2} r={stud} fill={color} />\n          <circle cx={b / 2} cy={b / 2} r={stud} fill={`url(#${id}-stud)`} />\n        </g>\n      );\n    }),\n  );\n\n  const landed = interpolate(t, [0, 10], [0, 1], {\n    ...clamp,\n    easing: Easing.out(Easing.cubic),\n  });\n\n  return (\n    <AbsoluteFill style={{ backgroundColor: background, overflow: \"hidden\" }}>\n      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} aria-hidden>\n        <defs>\n          <linearGradient\n            id={`${id}-stud`}\n            x1=\"0.15\"\n            y1=\"0.1\"\n            x2=\"0.85\"\n            y2=\"0.9\"\n          >\n            <stop offset=\"0\" stopColor=\"#FFFFFF\" stopOpacity={0.5} />\n            <stop offset=\"0.45\" stopColor=\"#FFFFFF\" stopOpacity={0} />\n            <stop offset=\"1\" stopColor=\"#000000\" stopOpacity={0.22} />\n          </linearGradient>\n        </defs>\n        {/* Baseplate studs the bricks snap onto. */}\n        <g fill=\"#FFFFFF\" opacity={0.07 * landed}>\n          {grid.flatMap((row, r) =>\n            row.map((_, col) => (\n              <circle\n                key={`${r}-${col}`}\n                cx={ox + (col + 0.5) * s}\n                cy={oy + (r + 0.5) * s}\n                r={stud}\n              />\n            )),\n          )}\n        </g>\n        {bricks}\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/brick-mosaic.tsx"
    }
  ],
  "type": "registry:component"
}
