{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Waves-TS-CSS",
	"title": "Waves",
	"description": "Layered lines that form smooth wave patterns with animation.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "Waves.css",
			"target": "@components/Waves.css",
			"content": ".waves {\n  position: absolute;\n  top: 0;\n  left: 0;\n  margin: 0;\n  padding: 0;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n\n.waves::before {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 0.5rem;\n  height: 0.5rem;\n  background: #160000;\n  border-radius: 50%;\n  transform: translate3d(calc(var(-0.5rem) - 50%), calc(var(50%) - 50%), 0);\n  will-change: transform;\n}\n\n.waves-canvas {\n  display: block;\n  width: 100%;\n  height: 100%;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "Waves.tsx",
			"content": "import React, { useRef, useEffect, type CSSProperties } from 'react';\nimport './Waves.css';\n\nclass Grad {\n  x: number;\n  y: number;\n  z: number;\n  constructor(x: number, y: number, z: number) {\n    this.x = x;\n    this.y = y;\n    this.z = z;\n  }\n  dot2(x: number, y: number): number {\n    return this.x * x + this.y * y;\n  }\n}\n\nclass Noise {\n  grad3: Grad[];\n  p: number[];\n  perm: number[];\n  gradP: Grad[];\n\n  constructor(seed = 0) {\n    this.grad3 = [\n      new Grad(1, 1, 0),\n      new Grad(-1, 1, 0),\n      new Grad(1, -1, 0),\n      new Grad(-1, -1, 0),\n      new Grad(1, 0, 1),\n      new Grad(-1, 0, 1),\n      new Grad(1, 0, -1),\n      new Grad(-1, 0, -1),\n      new Grad(0, 1, 1),\n      new Grad(0, -1, 1),\n      new Grad(0, 1, -1),\n      new Grad(0, -1, -1)\n    ];\n    this.p = [\n      151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240,\n      21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88,\n      237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83,\n      111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216,\n      80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186,\n      3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,\n      17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,\n      129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,\n      238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,\n      184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128,\n      195, 78, 66, 215, 61, 156, 180\n    ];\n    this.perm = new Array(512);\n    this.gradP = new Array(512);\n    this.seed(seed);\n  }\n  seed(seed: number) {\n    if (seed > 0 && seed < 1) seed *= 65536;\n    seed = Math.floor(seed);\n    if (seed < 256) seed |= seed << 8;\n    for (let i = 0; i < 256; i++) {\n      let v = i & 1 ? this.p[i] ^ (seed & 255) : this.p[i] ^ ((seed >> 8) & 255);\n      this.perm[i] = this.perm[i + 256] = v;\n      this.gradP[i] = this.gradP[i + 256] = this.grad3[v % 12];\n    }\n  }\n  fade(t: number): number {\n    return t * t * t * (t * (t * 6 - 15) + 10);\n  }\n  lerp(a: number, b: number, t: number): number {\n    return (1 - t) * a + t * b;\n  }\n  perlin2(x: number, y: number): number {\n    let X = Math.floor(x),\n      Y = Math.floor(y);\n    x -= X;\n    y -= Y;\n    X &= 255;\n    Y &= 255;\n    const n00 = this.gradP[X + this.perm[Y]].dot2(x, y);\n    const n01 = this.gradP[X + this.perm[Y + 1]].dot2(x, y - 1);\n    const n10 = this.gradP[X + 1 + this.perm[Y]].dot2(x - 1, y);\n    const n11 = this.gradP[X + 1 + this.perm[Y + 1]].dot2(x - 1, y - 1);\n    const u = this.fade(x);\n    return this.lerp(this.lerp(n00, n10, u), this.lerp(n01, n11, u), this.fade(y));\n  }\n}\n\ninterface Point {\n  x: number;\n  y: number;\n  wave: { x: number; y: number };\n  cursor: { x: number; y: number; vx: number; vy: number };\n}\n\ninterface Mouse {\n  x: number;\n  y: number;\n  lx: number;\n  ly: number;\n  sx: number;\n  sy: number;\n  v: number;\n  vs: number;\n  a: number;\n  set: boolean;\n}\n\ninterface Config {\n  lineColor: string;\n  waveSpeedX: number;\n  waveSpeedY: number;\n  waveAmpX: number;\n  waveAmpY: number;\n  friction: number;\n  tension: number;\n  maxCursorMove: number;\n  xGap: number;\n  yGap: number;\n}\n\ninterface WavesProps {\n  lineColor?: string;\n  backgroundColor?: string;\n  waveSpeedX?: number;\n  waveSpeedY?: number;\n  waveAmpX?: number;\n  waveAmpY?: number;\n  xGap?: number;\n  yGap?: number;\n  friction?: number;\n  tension?: number;\n  maxCursorMove?: number;\n  style?: CSSProperties;\n  className?: string;\n}\n\nconst Waves: React.FC<WavesProps> = ({\n  lineColor = 'black',\n  backgroundColor = 'transparent',\n  waveSpeedX = 0.0125,\n  waveSpeedY = 0.005,\n  waveAmpX = 32,\n  waveAmpY = 16,\n  xGap = 10,\n  yGap = 32,\n  friction = 0.925,\n  tension = 0.005,\n  maxCursorMove = 100,\n  style = {},\n  className = ''\n}) => {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);\n  const boundingRef = useRef<{\n    width: number;\n    height: number;\n    left: number;\n    top: number;\n  }>({\n    width: 0,\n    height: 0,\n    left: 0,\n    top: 0\n  });\n  const noiseRef = useRef(new Noise(Math.random()));\n  const linesRef = useRef<Point[][]>([]);\n  const mouseRef = useRef<Mouse>({\n    x: -10,\n    y: 0,\n    lx: 0,\n    ly: 0,\n    sx: 0,\n    sy: 0,\n    v: 0,\n    vs: 0,\n    a: 0,\n    set: false\n  });\n  const configRef = useRef<Config>({\n    lineColor,\n    waveSpeedX,\n    waveSpeedY,\n    waveAmpX,\n    waveAmpY,\n    friction,\n    tension,\n    maxCursorMove,\n    xGap,\n    yGap\n  });\n  const frameIdRef = useRef<number | null>(null);\n\n  useEffect(() => {\n    configRef.current = {\n      lineColor,\n      waveSpeedX,\n      waveSpeedY,\n      waveAmpX,\n      waveAmpY,\n      friction,\n      tension,\n      maxCursorMove,\n      xGap,\n      yGap\n    };\n  }, [lineColor, waveSpeedX, waveSpeedY, waveAmpX, waveAmpY, friction, tension, maxCursorMove, xGap, yGap]);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container) return;\n    ctxRef.current = canvas.getContext('2d');\n\n    function setSize() {\n      if (!container || !canvas) return;\n      const rect = container.getBoundingClientRect();\n      boundingRef.current = {\n        width: rect.width,\n        height: rect.height,\n        left: rect.left,\n        top: rect.top\n      };\n      canvas.width = rect.width;\n      canvas.height = rect.height;\n    }\n\n    function setLines() {\n      const { width, height } = boundingRef.current;\n      linesRef.current = [];\n      const oWidth = width + 200,\n        oHeight = height + 30;\n      const { xGap, yGap } = configRef.current;\n      const totalLines = Math.ceil(oWidth / xGap);\n      const totalPoints = Math.ceil(oHeight / yGap);\n      const xStart = (width - xGap * totalLines) / 2;\n      const yStart = (height - yGap * totalPoints) / 2;\n      for (let i = 0; i <= totalLines; i++) {\n        const pts: Point[] = [];\n        for (let j = 0; j <= totalPoints; j++) {\n          pts.push({\n            x: xStart + xGap * i,\n            y: yStart + yGap * j,\n            wave: { x: 0, y: 0 },\n            cursor: { x: 0, y: 0, vx: 0, vy: 0 }\n          });\n        }\n        linesRef.current.push(pts);\n      }\n    }\n\n    function movePoints(time: number) {\n      const lines = linesRef.current;\n      const mouse = mouseRef.current;\n      const noise = noiseRef.current;\n      const { waveSpeedX, waveSpeedY, waveAmpX, waveAmpY, friction, tension, maxCursorMove } = configRef.current;\n      lines.forEach(pts => {\n        pts.forEach(p => {\n          const move = noise.perlin2((p.x + time * waveSpeedX) * 0.002, (p.y + time * waveSpeedY) * 0.0015) * 12;\n          p.wave.x = Math.cos(move) * waveAmpX;\n          p.wave.y = Math.sin(move) * waveAmpY;\n\n          const dx = p.x - mouse.sx,\n            dy = p.y - mouse.sy;\n          const dist = Math.hypot(dx, dy);\n          const l = Math.max(175, mouse.vs);\n          if (dist < l) {\n            const s = 1 - dist / l;\n            const f = Math.cos(dist * 0.001) * s;\n            p.cursor.vx += Math.cos(mouse.a) * f * l * mouse.vs * 0.00065;\n            p.cursor.vy += Math.sin(mouse.a) * f * l * mouse.vs * 0.00065;\n          }\n\n          p.cursor.vx += (0 - p.cursor.x) * tension;\n          p.cursor.vy += (0 - p.cursor.y) * tension;\n          p.cursor.vx *= friction;\n          p.cursor.vy *= friction;\n          p.cursor.x += p.cursor.vx * 2;\n          p.cursor.y += p.cursor.vy * 2;\n          p.cursor.x = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.x));\n          p.cursor.y = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.y));\n        });\n      });\n    }\n\n    function moved(point: Point, withCursor = true): { x: number; y: number } {\n      const x = point.x + point.wave.x + (withCursor ? point.cursor.x : 0);\n      const y = point.y + point.wave.y + (withCursor ? point.cursor.y : 0);\n      return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };\n    }\n\n    function drawLines() {\n      const { width, height } = boundingRef.current;\n      const ctx = ctxRef.current;\n      if (!ctx) return;\n      ctx.clearRect(0, 0, width, height);\n      ctx.beginPath();\n      ctx.strokeStyle = configRef.current.lineColor;\n      linesRef.current.forEach(points => {\n        let p1 = moved(points[0], false);\n        ctx.moveTo(p1.x, p1.y);\n        points.forEach((p, idx) => {\n          const isLast = idx === points.length - 1;\n          p1 = moved(p, !isLast);\n          const p2 = moved(points[idx + 1] || points[points.length - 1], !isLast);\n          ctx.lineTo(p1.x, p1.y);\n          if (isLast) ctx.moveTo(p2.x, p2.y);\n        });\n      });\n      ctx.stroke();\n    }\n\n    function tick(t: number) {\n      if (!container) return;\n      const mouse = mouseRef.current;\n      mouse.sx += (mouse.x - mouse.sx) * 0.1;\n      mouse.sy += (mouse.y - mouse.sy) * 0.1;\n      const dx = mouse.x - mouse.lx,\n        dy = mouse.y - mouse.ly;\n      const d = Math.hypot(dx, dy);\n      mouse.v = d;\n      mouse.vs += (d - mouse.vs) * 0.1;\n      mouse.vs = Math.min(100, mouse.vs);\n      mouse.lx = mouse.x;\n      mouse.ly = mouse.y;\n      mouse.a = Math.atan2(dy, dx);\n      container.style.setProperty('--x', `${mouse.sx}px`);\n      container.style.setProperty('--y', `${mouse.sy}px`);\n\n      movePoints(t);\n      drawLines();\n      frameIdRef.current = requestAnimationFrame(tick);\n    }\n\n    function onResize() {\n      setSize();\n      setLines();\n    }\n    function onMouseMove(e: MouseEvent) {\n      updateMouse(e.clientX, e.clientY);\n    }\n    function onTouchMove(e: TouchEvent) {\n      const touch = e.touches[0];\n      updateMouse(touch.clientX, touch.clientY);\n    }\n    function updateMouse(x: number, y: number) {\n      const mouse = mouseRef.current;\n      const b = boundingRef.current;\n      mouse.x = x - b.left;\n      mouse.y = y - b.top;\n      if (!mouse.set) {\n        mouse.sx = mouse.x;\n        mouse.sy = mouse.y;\n        mouse.lx = mouse.x;\n        mouse.ly = mouse.y;\n        mouse.set = true;\n      }\n    }\n\n    setSize();\n    setLines();\n    frameIdRef.current = requestAnimationFrame(tick);\n    window.addEventListener('resize', onResize);\n    window.addEventListener('mousemove', onMouseMove);\n    window.addEventListener('touchmove', onTouchMove, { passive: false });\n\n    return () => {\n      window.removeEventListener('resize', onResize);\n      window.removeEventListener('mousemove', onMouseMove);\n      window.removeEventListener('touchmove', onTouchMove);\n      if (frameIdRef.current !== null) {\n        cancelAnimationFrame(frameIdRef.current);\n      }\n    };\n  }, []);\n\n  return (\n    <div\n      ref={containerRef}\n      className={`waves ${className}`}\n      style={{\n        position: 'absolute',\n        top: 0,\n        left: 0,\n        margin: 0,\n        padding: 0,\n        width: '100%',\n        height: '100%',\n        overflow: 'hidden',\n        backgroundColor,\n        ...style\n      }}\n    >\n      <canvas ref={canvasRef} className=\"waves-canvas\" />\n    </div>\n  );\n};\n\nexport default Waves;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}