{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"background-paths","type":"registry:ui","title":"Background Paths","description":"Two layered ribbons of flowing SVG curves with light running along their strokes. Pure SVG + CSS.","dependencies":[],"registryDependencies":[],"files":[{"path":"registry/ui/background-paths.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useEffect, useId, useRef, type CSSProperties } from \"react\";\n\nexport interface BackgroundPathsProps {\n  /** Color at the start of each curve. */\n  color?: string;\n  /** Color at the end of each curve. */\n  accent?: string;\n  /** Curves per layer. */\n  count?: number;\n  /** Animation speed multiplier; 0 freezes the curves. */\n  speed?: number;\n  /** Stroke width multiplier. */\n  strokeWidth?: number;\n  className?: string;\n}\n\nconst css = `\n@keyframes bgp-run{from{stroke-dashoffset:1}to{stroke-dashoffset:-1}}\n@keyframes bgp-breathe{0%,100%{opacity:var(--bgp-lo)}50%{opacity:var(--bgp-hi)}}\n.bgp-run{animation:bgp-run var(--bgp-d) linear var(--bgp-delay) infinite,bgp-breathe calc(var(--bgp-d)*.7) ease-in-out var(--bgp-delay) infinite}\n.bgp-root[data-paused] .bgp-run,.bgp-root[data-frozen] .bgp-run{animation-play-state:paused}\n@media (prefers-reduced-motion:reduce){.bgp-run{animation:none;stroke-dashoffset:.35}}\n`;\n\n// Integer hash: identical on server and client (Math.sin can differ in the last bits).\nconst rand = (i: number, salt: number) => {\n  let h = Math.imul(i + 1, 0x9e3779b1) ^ Math.imul(salt + 1, 0x85ebca6b);\n  h = Math.imul(h ^ (h >>> 15), 0x2c1b3c6d);\n  h = Math.imul(h ^ (h >>> 12), 0x297a2d39);\n  return ((h ^ (h >>> 15)) >>> 0) / 4294967296;\n};\n\n/** One twisting ribbon of curves: they fan out at the edges and pinch as they cross. */\nfunction layer(count: number, dir: 1 | -1, lift: number) {\n  return Array.from({ length: count }, (_, i) => {\n    const t = count > 1 ? i / (count - 1) - 0.5 : 0;\n    const y0 = 400 + lift + t * 760 * dir;\n    const y1 = 400 - lift - t * 620 * dir;\n    const c1 = 400 + lift - t * 520 * dir;\n    const c2 = 400 - lift + t * 900 * dir;\n    return `M-120 ${y0.toFixed(1)} C 360 ${c1.toFixed(1)}, 760 ${c2.toFixed(1)}, 1320 ${y1.toFixed(1)}`;\n  });\n}\n\nexport function BackgroundPaths({\n  color = \"#a78bfa\",\n  accent = \"#22d3ee\",\n  count = 32,\n  speed = 1,\n  strokeWidth = 1,\n  className,\n}: BackgroundPathsProps) {\n  const root = useRef<SVGSVGElement>(null);\n  const gid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const n = Math.max(1, Math.round(count));\n\n  useEffect(() => {\n    const el = root.current!;\n    const io = new IntersectionObserver(([e]) => el.toggleAttribute(\"data-paused\", !e.isIntersecting));\n    io.observe(el);\n    return () => io.disconnect();\n  }, []);\n\n  const layers = [\n    { d: layer(n, 1, -40), grad: `${gid}a`, w: 1 },\n    { d: layer(n, -1, 60), grad: `${gid}b`, w: 0.8 },\n  ];\n  const s = Math.max(speed, 0);\n\n  return (\n    <svg\n      ref={root}\n      aria-hidden\n      className={`bgp-root ${className ?? \"\"}`}\n      viewBox=\"0 0 1200 800\"\n      preserveAspectRatio=\"xMidYMid slice\"\n      style={{ position: \"absolute\", inset: 0, width: \"100%\", height: \"100%\", pointerEvents: \"none\" }}\n      data-frozen={s === 0 ? \"\" : undefined}\n    >\n      <style href=\"background-paths\" precedence=\"default\">\n        {css}\n      </style>\n      <defs>\n        <linearGradient id={`${gid}a`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n          <stop offset=\"0\" stopColor={color} />\n          <stop offset=\"1\" stopColor={accent} />\n        </linearGradient>\n        <linearGradient id={`${gid}b`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n          <stop offset=\"0\" stopColor={accent} />\n          <stop offset=\"1\" stopColor={color} />\n        </linearGradient>\n      </defs>\n      {layers.map((l, li) => (\n        <g key={li} fill=\"none\" stroke={`url(#${l.grad})`} strokeLinecap=\"round\">\n          {l.d.map((d, i) => {\n            const t = n > 1 ? i / (n - 1) : 0;\n            const w = (0.4 + t * 1.2) * l.w * strokeWidth;\n            const dur = (9 + rand(i, li + 1) * 10) / (s || 1);\n            return (\n              <g key={i}>\n                <path d={d} strokeWidth={w} strokeOpacity={0.1 + t * 0.12} />\n                <path\n                  className=\"bgp-run\"\n                  d={d}\n                  pathLength={1}\n                  strokeWidth={w * 1.6}\n                  strokeDasharray={`${(0.18 + rand(i, li + 7) * 0.22).toFixed(3)} 2`}\n                  style={\n                    {\n                      \"--bgp-d\": `${dur.toFixed(2)}s`,\n                      \"--bgp-delay\": `${(-rand(i, li + 3) * dur).toFixed(2)}s`,\n                      \"--bgp-lo\": (0.35 + t * 0.25).toFixed(2),\n                      \"--bgp-hi\": (0.75 + t * 0.25).toFixed(2),\n                    } as CSSProperties\n                  }\n                />\n              </g>\n            );\n          })}\n        </g>\n      ))}\n    </svg>\n  );\n}\n"}]}