{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "signature",
  "title": "Signature",
  "description": "Animated signature component with handwriting effect using custom fonts.",
  "dependencies": [
    "motion",
    "opentype.js",
    "@types/opentype.js"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/spell-ui/signature.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useState } from \"react\";\nimport { motion } from \"motion/react\";\nimport { parse as parseFont } from \"opentype.js\";\n\ntype SignatureGlyph = {\n  advanceWidth?: number;\n  getPath: (\n    x: number,\n    y: number,\n    fontSize: number,\n  ) => {\n    toPathData: (decimalPlaces?: number) => string;\n  };\n};\n\ntype SignatureFont = {\n  unitsPerEm: number;\n  charToGlyph: (char: string) => SignatureGlyph;\n};\n\nconst SVG_HEIGHT = 100;\nconst PATH_DELAY_STEP = 0.2;\nconst OPACITY_DELAY_OFFSET = 0.01;\nconst fontCache = new Map<string, SignatureFont>();\n\nfunction getFontCacheKey(path: string): string {\n  try {\n    return new URL(path, window.location.origin).href;\n  } catch {\n    return path;\n  }\n}\n\nfunction getPathTransition(index: number, duration: number, delay: number) {\n  const pathDelay = delay + index * PATH_DELAY_STEP;\n\n  return {\n    pathLength: {\n      delay: pathDelay,\n      duration,\n      ease: \"easeInOut\" as const,\n    },\n    opacity: {\n      delay: pathDelay + OPACITY_DELAY_OFFSET,\n      duration: 0.01,\n    },\n  };\n}\n\nasync function loadFontFromPaths(fontPaths: string[]): Promise<SignatureFont> {\n  for (const path of fontPaths) {\n    try {\n      const cacheKey = getFontCacheKey(path);\n      const cachedFont = fontCache.get(cacheKey);\n\n      if (cachedFont) {\n        return cachedFont;\n      }\n\n      const response = await fetch(path);\n\n      if (!response.ok) {\n        continue;\n      }\n\n      const fontBuffer = await response.arrayBuffer();\n      const font = parseFont(fontBuffer) as SignatureFont;\n      fontCache.set(cacheKey, font);\n\n      return font;\n    } catch {\n      // Try next path\n    }\n  }\n\n  throw new Error(\n    `Font could not be loaded from the provided path${fontPaths.length === 1 ? \"\" : \"s\"}: ${fontPaths.join(\", \")}`,\n  );\n}\n\nasync function buildSignaturePaths({\n  text,\n  fontSize,\n  baseline,\n  horizontalPadding,\n}: {\n  text: string;\n  fontSize: number;\n  baseline: number;\n  horizontalPadding: number;\n}): Promise<{ paths: string[]; width: number }> {\n  const font = await loadFontFromPaths([\"/LastoriaBoldRegular.otf\"]);\n\n  let x = horizontalPadding;\n  const nextPaths: string[] = [];\n\n  for (const char of text) {\n    const glyph = font.charToGlyph(char);\n    const path = glyph.getPath(x, baseline, fontSize);\n    nextPaths.push(path.toPathData(3));\n\n    const advanceWidth = glyph.advanceWidth ?? font.unitsPerEm;\n    x += advanceWidth * (fontSize / font.unitsPerEm);\n  }\n\n  return {\n    paths: nextPaths,\n    width: x + horizontalPadding,\n  };\n}\n\nfunction renderMotionPaths({\n  paths,\n  stroke,\n  strokeWidth,\n  strokeLinecap,\n  strokeLinejoin,\n  duration,\n  delay,\n}: {\n  paths: string[];\n  stroke: string;\n  strokeWidth: number;\n  strokeLinecap: \"round\" | \"butt\";\n  strokeLinejoin: \"round\";\n  duration: number;\n  delay: number;\n}) {\n  return paths.map((d, index) => (\n    <motion.path\n      key={index}\n      d={d}\n      stroke={stroke}\n      strokeWidth={strokeWidth}\n      fill=\"none\"\n      variants={PATH_VARIANTS}\n      transition={getPathTransition(index, duration, delay)}\n      vectorEffect=\"non-scaling-stroke\"\n      strokeLinecap={strokeLinecap}\n      strokeLinejoin={strokeLinejoin}\n    />\n  ));\n}\n\nconst PATH_VARIANTS = {\n  hidden: { pathLength: 0, opacity: 0 },\n  visible: { pathLength: 1, opacity: 1 },\n};\n\ninterface SignatureProps {\n  text?: string;\n  color?: string;\n  fontSize?: number;\n  duration?: number;\n  delay?: number;\n  className?: string;\n  inView?: boolean;\n  once?: boolean;\n}\n\nexport function Signature({\n  text = \"Signature\",\n  color = \"#000\",\n  fontSize = 14,\n  duration = 1.5,\n  delay = 0,\n  className,\n  inView = false,\n  once = true,\n}: SignatureProps) {\n  const [paths, setPaths] = useState<string[]>([]);\n  const [width, setWidth] = useState<number>(300);\n  const horizontalPadding = fontSize * 0.1;\n  const topMargin = Math.max(5, (SVG_HEIGHT - fontSize) / 2);\n  const baseline = Math.min(SVG_HEIGHT - 5, topMargin + fontSize);\n  const maskId = `signature-reveal-${useId().replace(/:/g, \"\")}`;\n\n  useEffect(() => {\n    let isCancelled = false;\n\n    async function loadSignaturePaths() {\n      try {\n        const { paths: nextPaths, width: nextWidth } = await buildSignaturePaths({\n          text,\n          fontSize,\n          baseline,\n          horizontalPadding,\n        });\n\n        if (isCancelled) {\n          return;\n        }\n\n        setPaths(nextPaths);\n        setWidth(nextWidth);\n      } catch {\n        if (isCancelled) {\n          return;\n        }\n\n        setPaths([]);\n        setWidth(text.length * fontSize * 0.6);\n      }\n    }\n\n    void loadSignaturePaths();\n\n    return () => {\n      isCancelled = true;\n    };\n  }, [text, fontSize, baseline, horizontalPadding]);\n\n  return (\n    <motion.svg\n      key={paths.length}\n      width={width}\n      height={SVG_HEIGHT}\n      viewBox={`0 0 ${width} ${SVG_HEIGHT}`}\n      fill=\"none\"\n      className={className}\n      initial=\"hidden\"\n      whileInView={inView ? \"visible\" : undefined}\n      animate={inView ? undefined : \"visible\"}\n      viewport={{ once }}\n    >\n      <defs>\n        <mask id={maskId} maskUnits=\"userSpaceOnUse\">\n          {renderMotionPaths({\n            paths,\n            stroke: \"white\",\n            strokeWidth: fontSize * 0.22,\n            strokeLinecap: \"round\",\n            strokeLinejoin: \"round\",\n            duration,\n            delay,\n          })}\n        </mask>\n      </defs>\n\n      {renderMotionPaths({\n        paths,\n        stroke: color,\n        strokeWidth: 2,\n        strokeLinecap: \"butt\",\n        strokeLinejoin: \"round\",\n        duration,\n        delay,\n      })}\n\n      <g mask={`url(#${maskId})`}>\n        {paths.map((d, index) => (\n          <path key={index} d={d} fill={color} />\n        ))}\n      </g>\n    </motion.svg>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}
