{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "kinetic-center-build",
  "title": "Kinetic Center Build",
  "description": "A word appears in the center; each new word enters from the right and pushes the line until the full phrase locks centered.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/kinetic-center-build/index.tsx",
      "content": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport { Easing, interpolate, useCurrentFrame } from \"remotion\";\n\nexport interface KineticCenterBuildProps {\n  text: string;\n  entryOffset?: number;\n  fontSize?: number;\n  color?: string;\n  fontWeight?: number;\n  speed?: number;\n  className?: string;\n}\n\nconst FONT_FAMILY =\n  \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\n\nconst MEASURE_FONT =\n  '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif';\n\nfunction measureWidths(\n  words: string[],\n  fontSize: number,\n  fontWeight: number,\n): number[] {\n  if (typeof document === \"undefined\") {\n    return words.map((w) => w.length * fontSize * 0.55);\n  }\n  const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n  if (!ctx) return words.map((w) => w.length * fontSize * 0.55);\n  ctx.font = `${fontWeight} ${fontSize}px ${MEASURE_FONT}`;\n  return words.map((w) => ctx.measureText(w).width);\n}\n\nexport function KineticCenterBuild({\n  text,\n  entryOffset = 88,\n  fontSize = 72,\n  color = \"#171717\",\n  fontWeight = 600,\n  speed = 1,\n  className,\n}: KineticCenterBuildProps) {\n  const frame = useCurrentFrame() * speed;\n\n  const words = useMemo(() => text.split(\" \"), [text]);\n  const widths = useMemo(\n    () => measureWidths(words, fontSize, fontWeight),\n    [words, fontSize, fontWeight],\n  );\n\n  const gap = 10;\n  const firstDur = 10;\n  const pushDur = 13;\n  const travelDur = 7;\n  const entryScale = 0.992;\n  const entryBlur = 3.5;\n  const reflowBlur = 0.8;\n  const firstWordY = 6;\n  const easing = Easing.bezier(0.2, 0.8, 0.2, 1);\n\n  const positionsAt = useMemo(() => {\n    const out: number[][] = [];\n    for (let k = 1; k <= words.length; k++) {\n      let total = gap * (k - 1);\n      for (let j = 0; j < k; j++) total += widths[j];\n      let cursor = -total / 2;\n      const xs: number[] = [];\n      for (let j = 0; j < k; j++) {\n        xs.push(cursor + widths[j] / 2);\n        cursor += widths[j] + gap;\n      }\n      out.push(xs);\n    }\n    return out;\n  }, [words, widths]);\n\n  const n = words.length;\n\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        background: \"transparent\",\n      }}\n    >\n      <div\n        className={className}\n        style={{\n          position: \"relative\",\n          fontSize,\n          fontWeight,\n          color,\n          letterSpacing: \"-0.03em\",\n          fontFamily: FONT_FAMILY,\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        {words.map((word, j) => {\n          const entryStart = j === 0 ? 0 : firstDur + (j - 1) * pushDur;\n          const entryEnd = entryStart + (j === 0 ? firstDur : pushDur);\n          const targetX = positionsAt[j][j];\n          const xFrom = j === 0 ? 0 : targetX + entryOffset;\n\n          let x = targetX;\n          let opacity = 1;\n          let scale = 1;\n          let blur = 0;\n          let y = 0;\n\n          if (frame < entryStart) {\n            opacity = 0;\n            x = xFrom;\n            scale = entryScale;\n            blur = entryBlur;\n            y = j === 0 ? firstWordY : 0;\n          } else if (frame <= entryEnd) {\n            const range: [number, number] = [entryStart, entryEnd];\n            const travelRange: [number, number] = [\n              entryStart,\n              Math.min(entryStart + travelDur, entryEnd),\n            ];\n            const opts = {\n              extrapolateLeft: \"clamp\" as const,\n              extrapolateRight: \"clamp\" as const,\n              easing,\n            };\n            x = interpolate(frame, travelRange, [xFrom, targetX], opts);\n            opacity = interpolate(frame, range, [0, 1], opts);\n            scale = interpolate(frame, range, [entryScale, 1], opts);\n            blur = interpolate(frame, range, [entryBlur, 0], opts);\n            y =\n              j === 0\n                ? interpolate(frame, travelRange, [firstWordY, 0], opts)\n                : 0;\n          } else {\n            for (let w = j + 1; w < n; w++) {\n              const pushStart = firstDur + (w - 1) * pushDur;\n              const pushEnd = pushStart + pushDur;\n              const fromX = positionsAt[w - 1][j];\n              const toX = positionsAt[w][j];\n              if (frame >= pushEnd) {\n                x = toX;\n              } else if (frame >= pushStart) {\n                x = interpolate(\n                  frame,\n                  [pushStart, Math.min(pushStart + travelDur, pushEnd)],\n                  [fromX, toX],\n                  {\n                    extrapolateLeft: \"clamp\",\n                    extrapolateRight: \"clamp\",\n                    easing,\n                  },\n                );\n                blur = interpolate(\n                  frame,\n                  [pushStart, (pushStart + pushEnd) / 2, pushEnd],\n                  [0, reflowBlur, 0],\n                  { extrapolateLeft: \"clamp\", extrapolateRight: \"clamp\" },\n                );\n                break;\n              } else {\n                x = fromX;\n                break;\n              }\n            }\n          }\n\n          return (\n            <span\n              key={j}\n              style={{\n                position: \"absolute\",\n                left: \"50%\",\n                top: \"50%\",\n                whiteSpace: \"nowrap\",\n                backfaceVisibility: \"hidden\",\n                transform: `translate(-50%, -50%) translate3d(${x}px, ${y}px, 0) scale(${scale})`,\n                filter: `blur(${blur}px)`,\n                opacity,\n              }}\n            >\n              {word}\n            </span>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/kinetic-center-build.tsx"
    }
  ],
  "type": "registry:component"
}
