{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gooey-morph",
  "title": "Gooey Morph",
  "description": "Bars fly in Tetris-style, land as a row, and melt into a word through a gooey blur-and-threshold morph.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/gooey-morph/index.tsx",
      "content": "\"use client\";\n\nimport { useId } from \"react\";\nimport { Easing, interpolate, useCurrentFrame } from \"remotion\";\n\nexport interface GooeyMorphProps {\n  className?: string;\n  width?: number;\n  height?: number;\n  barWidth?: number;\n  barHeight?: number;\n  barStartPositions?: [number, number][];\n  barRestPositions?: [number, number][];\n  barEntryFrames?: number[];\n  barTravelFrames?: number;\n  word?: string;\n  fontFamily?: string;\n  fontSize?: number;\n  tracking?: number;\n  wordCenterX?: number;\n  wordBaselineY?: number;\n  blurRadius?: number;\n  blurIterations?: number;\n  alphaInputBlack?: number;\n  alphaInputWhite?: number;\n  morphStartFrame?: number;\n  morphPeakFrame?: number;\n  morphEndFrame?: number;\n  displaceAmount?: number;\n  displaceSize?: number;\n  displaceComplexity?: number;\n  displaceEvolutionPerSecond?: number;\n  fill?: string;\n}\n\nfunction boxBlurSigma(radius: number, iterations: number): number {\n  const w = 2 * radius + 1;\n  return Math.sqrt((iterations * (w * w - 1)) / 12);\n}\n\nexport function GooeyMorph({\n  className,\n  width = 1920,\n  height = 1080,\n  barWidth = 213,\n  barHeight = 181,\n  barStartPositions = [\n    [956.9, 764.9],\n    [297.5, 799.9],\n    [738.2, 257.9],\n    [1617.9, 302.9],\n  ],\n  barRestPositions = [\n    [634.9, 538.9],\n    [852.5, 538.9],\n    [1070.2, 538.9],\n    [1287.9, 538.9],\n  ],\n  barEntryFrames = [8, 0, 4, 13],\n  barTravelFrames = 30,\n  word = \"HELLO\",\n  fontFamily = \"sans-serif\",\n  fontSize = 237,\n  tracking = 0,\n  wordCenterX = 961.4,\n  wordBaselineY = 601.3,\n  blurRadius = 6,\n  blurIterations = 6,\n  alphaInputBlack = 0.4588,\n  alphaInputWhite = 0.5098,\n  morphStartFrame = 50,\n  morphPeakFrame = 63,\n  morphEndFrame = 76,\n  displaceAmount = 20,\n  displaceSize = 19,\n  displaceComplexity = 1,\n  displaceEvolutionPerSecond = 50,\n  fill = \"#ffffff\",\n}: GooeyMorphProps) {\n  const frame = useCurrentFrame();\n  const filterId = useId();\n\n  const travelEase = Easing.bezier(0.88, 0.14, 0.12, 0.86);\n\n  const morphWindow: [number, number, number] = [\n    morphStartFrame,\n    morphPeakFrame,\n    morphEndFrame,\n  ];\n\n  const blur = interpolate(frame, morphWindow, [0, blurRadius, 0], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.bezier(0.33, 0, 0.67, 1),\n  });\n  const sigma = boxBlurSigma(blur, blurIterations);\n\n  const displaceScale = interpolate(\n    frame,\n    morphWindow,\n    [0, displaceAmount, 0],\n    {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n      easing: Easing.bezier(0.33, 0, 0.67, 1),\n    },\n  );\n\n  const barsOpacity = interpolate(\n    frame,\n    [morphStartFrame, morphEndFrame],\n    [1, 0],\n    {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n    },\n  );\n  const wordOpacity = interpolate(\n    frame,\n    [morphStartFrame, morphEndFrame],\n    [0, 1],\n    {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n    },\n  );\n\n  const slope = 1 / Math.max(alphaInputWhite - alphaInputBlack, 1e-6);\n  const offset = -alphaInputBlack * slope;\n\n  const evolution = (frame / 30) * displaceEvolutionPerSecond;\n\n  return (\n    <div\n      className={className}\n      style={{ position: \"absolute\", inset: 0, overflow: \"hidden\" }}\n    >\n      <svg\n        width=\"100%\"\n        height=\"100%\"\n        viewBox={`0 0 ${width} ${height}`}\n        preserveAspectRatio=\"xMidYMid meet\"\n        style={{ display: \"block\" }}\n      >\n        <defs>\n          <filter\n            id={filterId}\n            x=\"-15%\"\n            y=\"-15%\"\n            width=\"130%\"\n            height=\"130%\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feGaussianBlur\n              in=\"SourceGraphic\"\n              stdDeviation={sigma}\n              result=\"blurred\"\n            />\n            <feColorMatrix\n              in=\"blurred\"\n              type=\"matrix\"\n              values={[\n                \"1 0 0 0 0\",\n                \"0 1 0 0 0\",\n                \"0 0 1 0 0\",\n                `0 0 0 ${slope} ${offset}`,\n              ].join(\" \")}\n              result=\"threshold\"\n            />\n            <feTurbulence\n              type=\"fractalNoise\"\n              baseFrequency={1 / Math.max(displaceSize, 1)}\n              numOctaves={Math.max(1, Math.round(displaceComplexity))}\n              seed={7}\n              result=\"noise\"\n            />\n            <feOffset\n              in=\"noise\"\n              dx={evolution}\n              dy={evolution * 0.6}\n              result=\"noiseMoved\"\n            />\n            <feDisplacementMap\n              in=\"threshold\"\n              in2=\"noiseMoved\"\n              scale={displaceScale}\n              xChannelSelector=\"R\"\n              yChannelSelector=\"G\"\n            />\n          </filter>\n        </defs>\n\n        <g filter={`url(#${filterId})`}>\n          <g opacity={barsOpacity}>\n            {barRestPositions.map((rest, i) => {\n              const start = barStartPositions[i] ?? rest;\n              const entry = barEntryFrames[i] ?? 0;\n              const local = frame - entry;\n              const stops = [0, barTravelFrames / 2, barTravelFrames];\n\n              const x = interpolate(\n                local,\n                stops,\n                [start[0], rest[0], rest[0]],\n                {\n                  extrapolateLeft: \"clamp\",\n                  extrapolateRight: \"clamp\",\n                  easing: travelEase,\n                },\n              );\n              const y = interpolate(\n                local,\n                stops,\n                [start[1], start[1], rest[1]],\n                {\n                  extrapolateLeft: \"clamp\",\n                  extrapolateRight: \"clamp\",\n                  easing: travelEase,\n                },\n              );\n\n              return (\n                <rect\n                  key={rest.join(\",\")}\n                  x={x - barWidth / 2}\n                  y={y - barHeight / 2}\n                  width={barWidth}\n                  height={barHeight}\n                  fill={fill}\n                />\n              );\n            })}\n          </g>\n\n          <text\n            x={wordCenterX}\n            y={wordBaselineY}\n            textAnchor=\"middle\"\n            fontFamily={fontFamily}\n            fontSize={fontSize}\n            fontWeight={900}\n            letterSpacing={tracking}\n            fill={fill}\n            opacity={wordOpacity}\n          >\n            {word}\n          </text>\n        </g>\n      </svg>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/gooey-morph.tsx"
    }
  ],
  "type": "registry:component"
}
