{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stretch-in",
  "title": "Stretch In",
  "description": "Letters fly in from off-canvas and smear horizontally on the way — a vertex-lag deformation, not a scale.",
  "dependencies": ["remotion", "opentype.js"],
  "files": [
    {
      "path": "registry/remocn/stretch-in/index.tsx",
      "content": "\"use client\";\n\nimport { type Font, parse } from \"opentype.js\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport {\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n} from \"remotion\";\n\nexport interface StretchInProps {\n  className?: string;\n  width?: number;\n  height?: number;\n  text?: string;\n  fontUrl?: string;\n  fontSize?: number;\n  letterSpacing?: number;\n  baselineY?: number;\n  entryStart?: number;\n  entryStagger?: number;\n  travelFrames?: number;\n  travelDistance?: number;\n  vertexLagFrames?: number;\n  curveSteps?: number;\n  fill?: string;\n  strokeWidth?: number;\n  stroke?: string;\n}\n\ntype Contour = [number, number][];\n\nfunction flattenPath(\n  commands: ReturnType<Font[\"getPath\"]>[\"commands\"],\n  steps: number,\n): Contour[] {\n  const contours: Contour[] = [];\n  let current: Contour = [];\n  let cx = 0;\n  let cy = 0;\n\n  for (const cmd of commands) {\n    if (cmd.type === \"M\") {\n      if (current.length > 0) contours.push(current);\n      current = [[cmd.x, cmd.y]];\n      cx = cmd.x;\n      cy = cmd.y;\n    } else if (cmd.type === \"L\") {\n      current.push([cmd.x, cmd.y]);\n      cx = cmd.x;\n      cy = cmd.y;\n    } else if (cmd.type === \"Q\") {\n      for (let i = 1; i <= steps; i++) {\n        const t = i / steps;\n        const mt = 1 - t;\n        current.push([\n          mt * mt * cx + 2 * mt * t * cmd.x1 + t * t * cmd.x,\n          mt * mt * cy + 2 * mt * t * cmd.y1 + t * t * cmd.y,\n        ]);\n      }\n      cx = cmd.x;\n      cy = cmd.y;\n    } else if (cmd.type === \"C\") {\n      for (let i = 1; i <= steps; i++) {\n        const t = i / steps;\n        const mt = 1 - t;\n        current.push([\n          mt * mt * mt * cx +\n            3 * mt * mt * t * cmd.x1 +\n            3 * mt * t * t * cmd.x2 +\n            t * t * t * cmd.x,\n          mt * mt * mt * cy +\n            3 * mt * mt * t * cmd.y1 +\n            3 * mt * t * t * cmd.y2 +\n            t * t * t * cmd.y,\n        ]);\n      }\n      cx = cmd.x;\n      cy = cmd.y;\n    } else if (cmd.type === \"Z\") {\n      if (current.length > 0) contours.push(current);\n      current = [];\n    }\n  }\n  if (current.length > 0) contours.push(current);\n  return contours;\n}\n\nexport function StretchIn({\n  className,\n  width = 1920,\n  height = 1080,\n  text = \"REMOCN\",\n  fontUrl = \"https://fonts.gstatic.com/s/anton/v27/1Ptgg87LROyAm0K0.ttf\",\n  fontSize = 510,\n  letterSpacing = 0,\n  baselineY,\n  entryStart = 0,\n  entryStagger = 3,\n  travelFrames = 33,\n  travelDistance = 1500,\n  vertexLagFrames = 3.5,\n  curveSteps = 8,\n  fill = \"#ffffff\",\n  strokeWidth = 0,\n  stroke = \"#ffffff\",\n}: StretchInProps) {\n  const frame = useCurrentFrame();\n  const [font, setFont] = useState<Font | null>(null);\n\n  useEffect(() => {\n    const handle = delayRender(\"stretch-in: loading font\");\n    let cancelled = false;\n    fetch(fontUrl)\n      .then((res) => res.arrayBuffer())\n      .then((buffer) => {\n        if (!cancelled) setFont(parse(buffer));\n        continueRender(handle);\n      })\n      .catch(() => {\n        continueRender(handle);\n      });\n    return () => {\n      cancelled = true;\n    };\n  }, [fontUrl]);\n\n  const letters = useMemo(() => {\n    if (!font) return null;\n    const scale = fontSize / font.unitsPerEm;\n    const glyphs = font.stringToGlyphs(text);\n    let x = 0;\n    const items = glyphs.map((glyph, i) => {\n      if (i > 0) x += font.getKerningValue(glyphs[i - 1], glyph) * scale;\n      const left = x;\n      const advance = (glyph.advanceWidth ?? 0) * scale;\n      const contours = flattenPath(\n        glyph.getPath(x, 0, fontSize).commands,\n        curveSteps,\n      );\n      x += advance + letterSpacing;\n      return { left, width: advance, contours };\n    });\n    return { items, totalWidth: Math.max(0, x - letterSpacing) };\n  }, [font, text, fontSize, letterSpacing, curveSteps]);\n\n  if (!letters) return null;\n\n  const startX = (width - letters.totalWidth) / 2;\n  const resolvedBaseline = baselineY ?? (height + fontSize * 0.72) / 2;\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        {letters.items.map((glyph, i) => {\n          const entry = entryStart + entryStagger * i;\n          const run = Math.max(1, travelFrames - vertexLagFrames);\n\n          const d = glyph.contours\n            .map((contour) => {\n              const pts = contour.map(([px, py]) => {\n                const u =\n                  glyph.width === 0\n                    ? 0\n                    : Math.min(1, Math.max(0, (px - glyph.left) / glyph.width));\n                const start = entry + u * vertexLagFrames;\n                const remaining = interpolate(\n                  frame,\n                  [start, start + run],\n                  [1, 0],\n                  {\n                    extrapolateLeft: \"clamp\",\n                    extrapolateRight: \"clamp\",\n                    easing: Easing.bezier(0.2, 0.9, 0.25, 1),\n                  },\n                );\n                return [\n                  startX + px + travelDistance * remaining,\n                  resolvedBaseline + py,\n                ];\n              });\n              return (\n                `M${pts[0][0].toFixed(2)},${pts[0][1].toFixed(2)}` +\n                pts\n                  .slice(1)\n                  .map((p) => `L${p[0].toFixed(2)},${p[1].toFixed(2)}`)\n                  .join(\"\") +\n                \"Z\"\n              );\n            })\n            .join(\" \");\n\n          return (\n            <path\n              key={`${i}-${glyph.left}`}\n              d={d}\n              fill={fill}\n              fillRule=\"evenodd\"\n              stroke={strokeWidth > 0 ? stroke : \"none\"}\n              strokeWidth={strokeWidth}\n            />\n          );\n        })}\n      </svg>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/stretch-in.tsx"
    }
  ],
  "type": "registry:component"
}
