{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cursor-flow",
  "title": "Cursor Flow",
  "description": "A realistic mouse that arcs along cubic Bezier paths, pauses, and clicks targets that dip in response.",
  "dependencies": [
    "remotion"
  ],
  "files": [
    {
      "path": "registry/bases/editframe/components/cursor-flow/index.tsx",
      "content": "\"use client\";\n\nimport { Timegroup } from \"@editframe/react\";\nimport type { CSSProperties } from \"react\";\n\nconst FONT_FAMILY =\n  \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\n\nexport interface CursorWaypoint {\n  x: number;\n  y: number;\n  /** If set, the cursor will pause here for this many frames before moving on. */\n  hold?: number;\n  /** If true, a click animation will fire when the cursor reaches this point. */\n  click?: boolean;\n  /** Optional label rendered next to the click target. */\n  label?: string;\n}\n\nexport interface CursorFlowProps {\n  waypoints?: CursorWaypoint[];\n  cursorColor?: string;\n  cursorSize?: number;\n  segmentDuration?: number;\n  background?: string;\n  showTargets?: boolean;\n  speed?: number;\n  fps?: number;\n  durationInFrames?: number;\n  width?: number;\n  height?: number;\n  className?: string;\n}\n\nconst DEFAULT_WAYPOINTS: CursorWaypoint[] = [\n  { x: 200, y: 180 },\n  { click: true, label: \"Generate\", x: 540, y: 240 },\n  { hold: 8, x: 880, y: 360 },\n  { click: true, label: \"Publish\", x: 1040, y: 520 },\n];\n\n// Simplified bezier path through waypoints\nconst CURSOR_PATH =\n  \"M 200 180 C 350 200, 450 220, 540 240 C 650 260, 750 300, 880 360 C 920 380, 980 460, 1040 520\";\n\nexport const CursorFlow = ({\n  waypoints = DEFAULT_WAYPOINTS,\n  cursorColor = \"#fafafa\",\n  cursorSize = 28,\n  segmentDuration = 36,\n  background = \"#0a0a0a\",\n  showTargets = true,\n  speed = 1,\n  fps = 30,\n  durationInFrames = 180,\n  width = 1280,\n  height = 720,\n  className,\n}: CursorFlowProps) => {\n  const safeSpeed = Math.max(0.01, speed);\n  const durationMs = (durationInFrames / fps) * 1000;\n  const segmentMs = ((segmentDuration / fps) * 1000) / safeSpeed;\n  const totalPathMs = segmentMs * (waypoints.length - 1);\n\n  const containerStyle: CSSProperties = {\n    background,\n    fontFamily: FONT_FAMILY,\n    height,\n    overflow: \"hidden\",\n    position: \"relative\",\n    width,\n  };\n\n  return (\n    <Timegroup\n      className={className}\n      duration={`${durationMs}ms`}\n      mode=\"fixed\"\n      style={containerStyle}\n    >\n      <>\n        <style>{`\n          @keyframes framecn-cursor-flow {\n            from { offset-distance: 0%; }\n            to { offset-distance: 100%; }\n          }\n          @keyframes framecn-cursor-click {\n            0%, 100% { transform: scale(1); }\n            50% { transform: scale(0.85); }\n          }\n          @keyframes framecn-target-appear {\n            from { opacity: 0; transform: scale(0.98); }\n            to { opacity: 1; transform: scale(1); }\n          }\n        `}</style>\n\n        {/* Click targets */}\n        {showTargets &&\n          waypoints.map((wp, i) => {\n            if (!wp.click) {\n              return null;\n            }\n            const targetDelayMs = i * segmentMs * 0.9;\n            return (\n              <div\n                key={i}\n                style={{\n                  animation: `framecn-target-appear 300ms cubic-bezier(0.16, 1, 0.3, 1) ${targetDelayMs}ms backwards`,\n                  background: \"#fafafa\",\n                  borderRadius: 10,\n                  boxShadow: \"0 12px 30px rgba(0,0,0,0.4)\",\n                  color: \"#0a0a0a\",\n                  fontSize: 14,\n                  fontWeight: 600,\n                  left: wp.x - 70,\n                  padding: \"10px 18px\",\n                  pointerEvents: \"none\",\n                  position: \"absolute\",\n                  top: wp.y - 22,\n                }}\n              >\n                {wp.label ?? \"Click\"}\n              </div>\n            );\n          })}\n\n        {/* Cursor */}\n        <svg\n          width={cursorSize}\n          height={cursorSize}\n          viewBox=\"0 0 24 24\"\n          style={{\n            animation: `framecn-cursor-flow ${totalPathMs}ms cubic-bezier(0.16, 1, 0.3, 1) backwards`,\n            filter: \"drop-shadow(0 4px 12px rgba(0,0,0,0.6))\",\n            left: 0,\n            offsetPath: `path(\"${CURSOR_PATH}\")`,\n            pointerEvents: \"none\",\n            position: \"absolute\",\n            top: 0,\n            transformOrigin: \"0 0\",\n            zIndex: 9999,\n          }}\n        >\n          <path\n            d=\"M3 2 L3 18 L7 14 L10 21 L13 20 L10 13 L17 13 Z\"\n            fill={cursorColor}\n            stroke=\"#0a0a0a\"\n            strokeWidth=\"1.2\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n\n        {/* Active waypoint markers */}\n        {waypoints.map((wp, i) => (\n          <div\n            key={`marker-${i}`}\n            style={{\n              animation: `framecn-target-appear 300ms ease-out ${i * segmentMs * 0.9}ms backwards`,\n              background: \"rgba(255,255,255,0.04)\",\n              borderRadius: \"50%\",\n              height: 6,\n              left: wp.x - 3,\n              pointerEvents: \"none\",\n              position: \"absolute\",\n              top: wp.y - 3,\n              width: 6,\n            }}\n          />\n        ))}\n      </>\n    </Timegroup>\n  );\n};\n",
      "type": "registry:component",
      "target": "components/framecn/cursor-flow.tsx"
    }
  ],
  "type": "registry:component"
}