{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pixel-sort",
  "title": "Pixel Sort",
  "description": "The scene melts into seeded vertical streaks like glitch-art pixel sorting, then the next scene's columns fall in and snap into place.",
  "dependencies": ["remotion", "@remotion/transitions"],
  "files": [
    {
      "path": "registry/remocn/pixel-sort/index.tsx",
      "content": "\"use client\";\n\nimport type {\n  TransitionPresentation,\n  TransitionPresentationComponentProps,\n} from \"@remotion/transitions\";\nimport type React from \"react\";\nimport { useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  random,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type PixelSortProps = {\n  /** Number of vertical columns the frame is sliced into. */\n  columns?: number;\n  /** Seed for per-column melt amount and timing. */\n  seed?: number | string;\n  /** Color of the bright smeared streaks. */\n  streakColor?: string;\n};\n\nexport const pixelSortDefaults: Required<PixelSortProps> = {\n  columns: 40,\n  seed: 5,\n  streakColor: \"#FFFFFF\",\n};\n\n// Outgoing columns melt over MELT; incoming ones drop in (DROP) and snap (SNAP).\nconst MELT = 0.42;\nconst DROP = 0.14;\nconst SNAP = 0.12;\nconst clamp = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\nconst meltEase = Easing.in(Easing.cubic);\nconst snapEase = Easing.out(Easing.back(1.6));\n\ntype Column = {\n  left: number;\n  width: number;\n  meltAt: number;\n  amount: number;\n  glow: number;\n  streakFrom: number;\n  snapAt: number;\n};\n\nconst PixelSortPresentation: React.FC<\n  TransitionPresentationComponentProps<PixelSortProps>\n> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n  passedProps,\n}) => {\n  const { width, height } = useVideoConfig();\n  const { columns, seed, streakColor } = {\n    ...pixelSortDefaults,\n    ...passedProps,\n  };\n  const count = Math.max(2, Math.round(columns));\n\n  const layout = useMemo<Column[]>(\n    () =>\n      Array.from({ length: count }, (_, i) => {\n        const r = (key: string) => random(`pixel-sort-${seed}-${key}-${i}`);\n        const left = Math.floor((i * width) / count);\n        const right = Math.floor(((i + 1) * width) / count);\n        return {\n          left,\n          // 1px overlap keeps resting columns seamless.\n          width: right - left + (i < count - 1 ? 1 : 0),\n          meltAt: r(\"m\") * 0.2,\n          amount: 0.25 + r(\"a\") ** 2 * 1.6,\n          glow: 0.15 + r(\"g\") * 0.75,\n          streakFrom: 10 + r(\"s\") * 50,\n          snapAt: 0.5 + r(\"t\") * (1 - 0.5 - DROP - SNAP) + DROP,\n        };\n      }),\n    [count, seed, width],\n  );\n\n  const p = presentationProgress;\n  const exiting = presentationDirection === \"exiting\";\n\n  return (\n    <AbsoluteFill>\n      {layout.map((c) => {\n        let stretch: number;\n        let shift: number;\n        let streak: number;\n        let opacity = 1;\n        let origin = \"50% 0%\";\n        if (exiting) {\n          const m = meltEase(\n            interpolate(p, [c.meltAt, c.meltAt + MELT], [0, 1], clamp),\n          );\n          // Stretch around the streak start so the column never tears open.\n          stretch = 1 + m * c.amount * 4;\n          shift = 0;\n          origin = `50% ${c.streakFrom}%`;\n          streak = m * c.glow;\n        } else {\n          const drop = interpolate(\n            p,\n            [c.snapAt - DROP, c.snapAt],\n            [0, 1],\n            clamp,\n          );\n          const snap = snapEase(\n            interpolate(p, [c.snapAt, c.snapAt + SNAP], [0, 1], clamp),\n          );\n          if (drop <= 0) return null;\n          // A smeared streak falls in from above, then snaps to the real image.\n          stretch = interpolate(snap, [0, 1], [1 + c.amount * 3, 1]);\n          shift = -(1 - Easing.out(Easing.cubic)(drop)) * height * 1.1;\n          streak = (1 - Math.min(1, Math.max(0, snap))) * Math.max(0.5, c.glow);\n          opacity = interpolate(drop, [0, 0.3], [0, 1], clamp);\n        }\n        return (\n          <div\n            key={c.left}\n            style={{\n              position: \"absolute\",\n              left: c.left,\n              top: 0,\n              width: c.width,\n              height,\n              overflow: \"hidden\",\n              opacity,\n            }}\n          >\n            <div\n              style={{\n                position: \"absolute\",\n                left: -c.left,\n                top: 0,\n                width,\n                height,\n                transformOrigin: origin,\n                transform: `translateY(${shift}px) scaleY(${stretch})`,\n              }}\n            >\n              {children}\n            </div>\n            {streak > 0.002 ? (\n              <div\n                style={{\n                  position: \"absolute\",\n                  inset: 0,\n                  opacity: streak,\n                  background: `linear-gradient(to bottom, transparent ${c.streakFrom}%, ${streakColor} ${c.streakFrom + 30}%, transparent 100%)`,\n                }}\n              />\n            ) : null}\n          </div>\n        );\n      })}\n    </AbsoluteFill>\n  );\n};\n\nexport function pixelSort(\n  props: PixelSortProps = {},\n): TransitionPresentation<PixelSortProps> {\n  return { component: PixelSortPresentation, props };\n}\n",
      "type": "registry:component",
      "target": "components/remocn/pixel-sort.tsx"
    }
  ],
  "type": "registry:component"
}
