{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "illustration-cursor",
  "type": "registry:ui",
  "description": "Cursor illustration and demo for collaboration UI",
  "files": [
    {
      "path": "registry/default/ui/illustration-cursor.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useMemo, useRef, useState } from \"react\"\n\nexport type CursorSide = \"left\" | \"right\"\n\nfunction createSeededRandom(seed: string) {\n  let hash = 2_166_136_261\n  for (let i = 0; i < seed.length; i += 1) {\n    hash ^= seed.charCodeAt(i)\n    hash = Math.imul(hash, 16_777_619)\n  }\n  let state = hash >>> 0\n  return () => {\n    state = (Math.imul(state, 1_664_525) + 1_013_904_223) >>> 0\n    return state / 4_294_967_296\n  }\n}\n\nexport function CursorSvg({ flip = false }: { flip?: boolean }) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      data-slot=\"collab-cursor-svg\"\n      fill=\"none\"\n      focusable=\"false\"\n      height=\"18\"\n      style={flip ? { transform: \"scaleX(-1)\" } : undefined}\n      viewBox=\"0 0 17 18\"\n      width=\"17\"\n    >\n      <path\n        d=\"M15.5036 3.11002L12.5357 15.4055C12.2666 16.5204 10.7637 16.7146 10.22 15.7049L7.4763 10.6094L2.00376 8.65488C0.915938 8.26638 0.891983 6.73663 1.96711 6.31426L13.8314 1.65328C14.7729 1.28341 15.741 2.12672 15.5036 3.11002ZM7.56678 10.6417L7.56645 10.6416C7.56656 10.6416 7.56667 10.6416 7.56678 10.6417L7.65087 10.4062L7.56678 10.6417Z\"\n        fill=\"currentColor\"\n        stroke=\"white\"\n        strokeWidth=\"1.5\"\n      />\n    </svg>\n  )\n}\n\nfunction useFloatingCursor(enabled: boolean) {\n  const ref = useRef<HTMLDivElement>(null)\n  const raf = useRef<number | null>(null)\n  const time = useRef(0)\n  const params = useRef<{\n    phaseX: number\n    phaseY: number\n    speedX: number\n    speedY: number\n    ampX: number\n    ampY: number\n  } | null>(null)\n\n  useEffect(() => {\n    if (!enabled) return\n\n    if (!params.current) {\n      params.current = {\n        phaseX: Math.random() * Math.PI * 2,\n        phaseY: Math.random() * Math.PI * 2,\n        speedX: 0.55 + Math.random() * 0.35,\n        speedY: 0.7 + Math.random() * 0.35,\n        ampX: 3 + Math.random() * 5,\n        ampY: 2 + Math.random() * 4,\n      }\n    }\n\n    const p = params.current\n    if (!p) return\n\n    const tick = () => {\n      time.current += 0.016\n      const t = time.current\n      const x =\n        Math.sin(t * p.speedX + p.phaseX) * p.ampX +\n        Math.sin(t * p.speedX * 1.7 + p.phaseX * 0.5) * (p.ampX * 0.3)\n      const y =\n        Math.sin(t * p.speedY + p.phaseY) * p.ampY +\n        Math.cos(t * p.speedY * 1.3 + p.phaseY * 0.7) * (p.ampY * 0.25)\n\n      if (ref.current) {\n        ref.current.style.transform = `translate(${x}px, ${y}px)`\n      }\n      raf.current = requestAnimationFrame(tick)\n    }\n\n    raf.current = requestAnimationFrame(tick)\n    return () => {\n      if (raf.current) cancelAnimationFrame(raf.current)\n    }\n  }, [enabled])\n\n  return ref\n}\n\nexport function Cursor({\n  name,\n  color,\n  side,\n  style: posStyle,\n  delay = false,\n}: {\n  name: string\n  color: string\n  side: CursorSide\n  style?: React.CSSProperties\n  delay?: boolean\n}) {\n  const [visible, setVisible] = useState(!delay)\n  const floatRef = useFloatingCursor(visible)\n\n  useEffect(() => {\n    if (!delay) return\n    const timer = setTimeout(() => setVisible(true), 800)\n    return () => clearTimeout(timer)\n  }, [delay])\n\n  return (\n    <div\n      data-slot=\"collab-cursor\"\n      style={{ position: \"absolute\", pointerEvents: \"none\", ...posStyle }}\n    >\n      <div\n        ref={floatRef}\n        style={{\n          color,\n          display: \"flex\",\n          alignItems: \"flex-start\",\n          gap: 0,\n          opacity: visible ? 1 : 0,\n          filter: visible ? \"blur(0px)\" : \"blur(6px)\",\n          transition:\n            \"opacity .5s cubic-bezier(.23,1,.32,1), filter .5s cubic-bezier(.23,1,.32,1)\",\n          willChange: \"transform\",\n        }}\n      >\n        <CursorSvg flip={side === \"right\"} />\n        <span\n          data-slot=\"collab-cursor-label\"\n          style={{\n            background: color,\n            color: \"#fff\",\n            fontSize: 12,\n            fontWeight: 500,\n            padding: \"2px 7px\",\n            borderRadius: 4,\n            whiteSpace: \"nowrap\",\n            marginTop: 10,\n            marginLeft: side === \"left\" ? -2 : 0,\n            marginRight: side === \"right\" ? -2 : 0,\n          }}\n        >\n          {name}\n        </span>\n      </div>\n    </div>\n  )\n}\n\nexport function CursorDemo() {\n  const cursors = useMemo(() => {\n    const names = [\"John\", \"Jane\", \"Jim\", \"Jill\", \"Jack\", \"Mina\"] as const\n    const colors = [\n      \"#7c3aed\",\n      \"#2563eb\",\n      \"#db2777\",\n      \"#ea580c\",\n      \"#059669\",\n      \"#0f766e\",\n    ] as const\n    const random = createSeededRandom(\"cursor-demo\")\n    const colorPool = [...colors]\n    for (let i = colorPool.length - 1; i > 0; i -= 1) {\n      const j = Math.floor(random() * (i + 1))\n      ;[colorPool[i], colorPool[j]] = [colorPool[j], colorPool[i]]\n    }\n    const placed: Array<{ x: number; y: number }> = []\n    return names.map((name, index) => {\n      const minDistance = 44\n      let x = 0\n      let y = 0\n\n      for (let attempt = 0; attempt < 30; attempt += 1) {\n        const angle = random() * Math.PI * 2\n        const spreadX = 54 + random() * 42\n        const spreadY = 26 + random() * 28\n        const candidateX = Math.round(Math.cos(angle) * spreadX)\n        const candidateY = Math.round(Math.sin(angle) * spreadY)\n        const isFarEnough = placed.every((point) => {\n          const dx = point.x - candidateX\n          const dy = point.y - candidateY\n          return Math.hypot(dx, dy) >= minDistance\n        })\n\n        x = candidateX\n        y = candidateY\n        if (isFarEnough) break\n      }\n\n      placed.push({ x, y })\n      const side: CursorSide = random() > 0.5 ? \"left\" : \"right\"\n      const color =\n        colorPool.pop() ?? colors[Math.floor(random() * colors.length)]\n      return {\n        name,\n        color,\n        side,\n        delay: index > 1,\n        style: {\n          left: `calc(50% + ${x}px)`,\n          bottom: `calc(48% + ${y}px)`,\n        } satisfies React.CSSProperties,\n      }\n    })\n  }, [])\n\n  return (\n    <div className=\"flex flex-col gap-3 antialiased\">\n      <div className=\"flex items-center justify-between\">\n        <h4 className=\"font-medium text-foreground text-sm\">\n          Live collaborators\n        </h4>\n        <span className=\"rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground\">\n          6 online\n        </span>\n      </div>\n      <div className=\"relative h-44 overflow-hidden rounded-[calc(var(--radius)+8px)] bg-[radial-gradient(circle_at_50%_45%,rgba(148,163,184,0.16),transparent_58%),linear-gradient(180deg,rgba(255,255,255,0.75),rgba(255,255,255,0.45))] p-4 shadow-[0px_0px_0px_1px_rgba(0,0,0,0.06),0px_1px_2px_-1px_rgba(0,0,0,0.06),0px_8px_20px_rgba(15,23,42,0.08)] dark:bg-[radial-gradient(circle_at_50%_45%,rgba(148,163,184,0.2),transparent_58%),linear-gradient(180deg,rgba(15,23,42,0.52),rgba(2,6,23,0.7))] dark:shadow-[0px_0px_0px_1px_rgba(255,255,255,0.08),0px_1px_2px_-1px_rgba(255,255,255,0.04),0px_8px_20px_rgba(0,0,0,0.35)]\">\n        <div className=\"pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,rgba(148,163,184,0.14)_1px,transparent_1px),linear-gradient(to_bottom,rgba(148,163,184,0.14)_1px,transparent_1px)] bg-size-[26px_26px] opacity-70\" />\n        <div className=\"-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute top-1/2 left-1/2 h-32 w-32 rounded-full bg-[radial-gradient(circle,rgba(59,130,246,0.14)_0%,rgba(59,130,246,0)_70%)]\" />\n        {cursors.map((cursor, index) => (\n          <Cursor\n            color={cursor.color}\n            delay={cursor.delay}\n            key={`${cursor.name}-${index}`}\n            name={cursor.name}\n            side={cursor.side}\n            style={cursor.style}\n          />\n        ))}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}