{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "FloatingLines-JS-CSS",
	"title": "FloatingLines",
	"description": "3D floating lines that react to cursor movement.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "FloatingLines.css",
			"target": "@components/FloatingLines.css",
			"content": ".floating-lines-container {\n  width: 100%;\n  height: 100%;\n  position: relative;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "FloatingLines.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport {\n  Clock,\n  Mesh,\n  OrthographicCamera,\n  PlaneGeometry,\n  Scene,\n  ShaderMaterial,\n  Vector2,\n  Vector3,\n  WebGLRenderer\n} from 'three';\n\nimport './FloatingLines.css';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3  iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\nuniform vec3 backgroundColor;\nuniform bool lightMode;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK  = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE  = vec3(47.0,  75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n  return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n  vec3 col = vec3(0.0);\n\n  float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n  float m = uv.y - y;\n\n  col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n  col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n  return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n  if (lineGradientCount <= 0) {\n    return baseColor;\n  }\n\n  vec3 gradientColor;\n  \n  if (lineGradientCount == 1) {\n    gradientColor = lineGradient[0];\n  } else {\n    float clampedT = clamp(t, 0.0, 0.9999);\n    float scaled = clampedT * float(lineGradientCount - 1);\n    int idx = int(floor(scaled));\n    float f = fract(scaled);\n    int idx2 = min(idx + 1, lineGradientCount - 1);\n\n    vec3 c1 = lineGradient[idx];\n    vec3 c2 = lineGradient[idx2];\n    \n    gradientColor = mix(c1, c2, f);\n  }\n  \n  return gradientColor * 0.5;\n}\n\n  float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n  float time = iTime * animationSpeed;\n\n  float x_offset   = offset;\n  float x_movement = time * 0.1;\n  float amp        = sin(offset + time * 0.2) * 0.3;\n  float y          = sin(uv.x + x_offset + x_movement) * amp;\n\n  if (shouldBend) {\n    vec2 d = screenUv - mouseUv;\n    float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n    float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n    y += bendOffset;\n  }\n\n  float m = uv.y - y;\n  return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n  vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n  baseUv.y *= -1.0;\n  \n  if (parallax) {\n    baseUv += parallaxOffset;\n  }\n\n  vec3 col = vec3(0.0);\n\n  vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n  vec2 mouseUv = vec2(0.0);\n  if (interactive) {\n    mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n    mouseUv.y *= -1.0;\n  }\n  \n  if (enableBottom) {\n    for (int i = 0; i < bottomLineCount; ++i) {\n      float fi = float(i);\n      float t = fi / max(float(bottomLineCount - 1), 1.0);\n      vec3 lineCol = getLineColor(t, b);\n      \n      float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n      vec2 ruv = baseUv * rotate(angle);\n      col += lineCol * wave(\n        ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n        1.5 + 0.2 * fi,\n        baseUv,\n        mouseUv,\n        interactive\n      ) * 0.2;\n    }\n  }\n\n  if (enableMiddle) {\n    for (int i = 0; i < middleLineCount; ++i) {\n      float fi = float(i);\n      float t = fi / max(float(middleLineCount - 1), 1.0);\n      vec3 lineCol = getLineColor(t, b);\n      \n      float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n      vec2 ruv = baseUv * rotate(angle);\n      col += lineCol * wave(\n        ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n        2.0 + 0.15 * fi,\n        baseUv,\n        mouseUv,\n        interactive\n      );\n    }\n  }\n\n  if (enableTop) {\n    for (int i = 0; i < topLineCount; ++i) {\n      float fi = float(i);\n      float t = fi / max(float(topLineCount - 1), 1.0);\n      vec3 lineCol = getLineColor(t, b);\n      \n      float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n      vec2 ruv = baseUv * rotate(angle);\n      ruv.x *= -1.0;\n      col += lineCol * wave(\n        ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n        1.0 + 0.2 * fi,\n        baseUv,\n        mouseUv,\n        interactive\n      ) * 0.1;\n    }\n  }\n\nif (lightMode) {\n  vec3 energy = max(col, vec3(0.0));\n  float peak = max(energy.r, max(energy.g, energy.b));\n  float coverage = smoothstep(0.018, 0.5, peak);\n  vec3 chroma = clamp(energy / max(peak, 0.0001), 0.0, 1.0);\n  chroma = pow(chroma, vec3(1.35));\n  float chromaPeak = max(chroma.r, max(chroma.g, chroma.b));\n  chroma /= max(chromaPeak, 0.0001);\n  vec3 ink = mix(chroma, clamp(chroma * 0.82, 0.0, 1.0), smoothstep(0.5, 1.0, coverage));\n  fragColor = vec4(mix(vec3(1.0), ink, coverage * 0.94), 1.0);\n} else {\n    fragColor = vec4(col, 1.0);\n  }\n}\n\nvoid main() {\n  vec4 color = vec4(0.0);\n  mainImage(color, gl_FragCoord.xy);\n  gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\nfunction hexToVec3(hex) {\n  let value = hex.trim();\n\n  if (value.startsWith('#')) {\n    value = value.slice(1);\n  }\n\n  let r = 255;\n  let g = 255;\n  let b = 255;\n\n  if (value.length === 3) {\n    r = parseInt(value[0] + value[0], 16);\n    g = parseInt(value[1] + value[1], 16);\n    b = parseInt(value[2] + value[2], 16);\n  } else if (value.length === 6) {\n    r = parseInt(value.slice(0, 2), 16);\n    g = parseInt(value.slice(2, 4), 16);\n    b = parseInt(value.slice(4, 6), 16);\n  }\n\n  return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n  linesGradient,\n  enabledWaves = ['top', 'middle', 'bottom'],\n  lineCount = [6],\n  lineDistance = [5],\n  topWavePosition,\n  middleWavePosition,\n  bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n  animationSpeed = 1,\n  interactive = true,\n  bendRadius = 5.0,\n  bendStrength = -0.5,\n  mouseDamping = 0.05,\n  parallax = true,\n  parallaxStrength = 0.2,\n  mixBlendMode = 'screen',\n  backgroundColor = '#000000',\n  lightMode = false\n}) {\n  const containerRef = useRef(null);\n  const targetMouseRef = useRef(new Vector2(-1000, -1000));\n  const currentMouseRef = useRef(new Vector2(-1000, -1000));\n  const targetInfluenceRef = useRef(0);\n  const currentInfluenceRef = useRef(0);\n  const targetParallaxRef = useRef(new Vector2(0, 0));\n  const currentParallaxRef = useRef(new Vector2(0, 0));\n\n  const getLineCount = waveType => {\n    if (typeof lineCount === 'number') return lineCount;\n    if (!enabledWaves.includes(waveType)) return 0;\n    const index = enabledWaves.indexOf(waveType);\n    return lineCount[index] ?? 6;\n  };\n\n  const getLineDistance = waveType => {\n    if (typeof lineDistance === 'number') return lineDistance;\n    if (!enabledWaves.includes(waveType)) return 0.1;\n    const index = enabledWaves.indexOf(waveType);\n    return lineDistance[index] ?? 0.1;\n  };\n\n  const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n  const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n  const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n  const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n  const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n  const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    let active = true;\n\n    const scene = new Scene();\n\n    const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n    camera.position.z = 1;\n\n    const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n    renderer.domElement.style.width = '100%';\n    renderer.domElement.style.height = '100%';\n    container.appendChild(renderer.domElement);\n\n    const uniforms = {\n      iTime: { value: 0 },\n      iResolution: { value: new Vector3(1, 1, 1) },\n      animationSpeed: { value: animationSpeed },\n\n      enableTop: { value: enabledWaves.includes('top') },\n      enableMiddle: { value: enabledWaves.includes('middle') },\n      enableBottom: { value: enabledWaves.includes('bottom') },\n\n      topLineCount: { value: topLineCount },\n      middleLineCount: { value: middleLineCount },\n      bottomLineCount: { value: bottomLineCount },\n\n      topLineDistance: { value: topLineDistance },\n      middleLineDistance: { value: middleLineDistance },\n      bottomLineDistance: { value: bottomLineDistance },\n\n      topWavePosition: {\n        value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n      },\n      middleWavePosition: {\n        value: new Vector3(\n          middleWavePosition?.x ?? 5.0,\n          middleWavePosition?.y ?? 0.0,\n          middleWavePosition?.rotate ?? 0.2\n        )\n      },\n      bottomWavePosition: {\n        value: new Vector3(\n          bottomWavePosition?.x ?? 2.0,\n          bottomWavePosition?.y ?? -0.7,\n          bottomWavePosition?.rotate ?? 0.4\n        )\n      },\n\n      iMouse: { value: new Vector2(-1000, -1000) },\n      interactive: { value: interactive },\n      bendRadius: { value: bendRadius },\n      bendStrength: { value: bendStrength },\n      bendInfluence: { value: 0 },\n\n      parallax: { value: parallax },\n      parallaxStrength: { value: parallaxStrength },\n      parallaxOffset: { value: new Vector2(0, 0) },\n\n      lineGradient: {\n        value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n      },\n      lineGradientCount: { value: 0 },\n      backgroundColor: { value: hexToVec3(backgroundColor) },\n      lightMode: { value: lightMode }\n    };\n\n    if (linesGradient && linesGradient.length > 0) {\n      const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n      uniforms.lineGradientCount.value = stops.length;\n\n      stops.forEach((hex, i) => {\n        const color = hexToVec3(hex);\n        uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n      });\n    }\n\n    const material = new ShaderMaterial({\n      uniforms,\n      vertexShader,\n      fragmentShader\n    });\n\n    const geometry = new PlaneGeometry(2, 2);\n    const mesh = new Mesh(geometry, material);\n    scene.add(mesh);\n\n    const clock = new Clock();\n\n    const setSize = () => {\n      if (!active) return;\n      const width = container.clientWidth || 1;\n      const height = container.clientHeight || 1;\n\n      renderer.setSize(width, height, false);\n\n      const canvasWidth = renderer.domElement.width;\n      const canvasHeight = renderer.domElement.height;\n      uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n    };\n\n    setSize();\n\n    const ro =\n      typeof ResizeObserver !== 'undefined'\n        ? new ResizeObserver(() => {\n            if (!active) return;\n            setSize();\n          })\n        : null;\n\n    if (ro) ro.observe(container);\n\n    const handlePointerMove = event => {\n      const rect = renderer.domElement.getBoundingClientRect();\n      const x = event.clientX - rect.left;\n      const y = event.clientY - rect.top;\n      const dpr = renderer.getPixelRatio();\n\n      targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n      targetInfluenceRef.current = 1.0;\n\n      if (parallax) {\n        const centerX = rect.width / 2;\n        const centerY = rect.height / 2;\n        const offsetX = (x - centerX) / rect.width;\n        const offsetY = -(y - centerY) / rect.height;\n        targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n      }\n    };\n\n    const handlePointerLeave = () => {\n      targetInfluenceRef.current = 0.0;\n    };\n\n    if (interactive) {\n      renderer.domElement.addEventListener('pointermove', handlePointerMove);\n      renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n    }\n\n    let raf = 0;\n    const renderLoop = () => {\n      if (!active) return;\n\n      uniforms.iTime.value = clock.getElapsedTime();\n\n      if (interactive) {\n        currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n        uniforms.iMouse.value.copy(currentMouseRef.current);\n\n        currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n        uniforms.bendInfluence.value = currentInfluenceRef.current;\n      }\n\n      if (parallax) {\n        currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n        uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n      }\n\n      renderer.render(scene, camera);\n      raf = requestAnimationFrame(renderLoop);\n    };\n    renderLoop();\n\n    return () => {\n      active = false;\n\n      cancelAnimationFrame(raf);\n\n      if (ro) ro.disconnect();\n\n      if (interactive) {\n        renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n        renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n      }\n\n      geometry.dispose();\n      material.dispose();\n      renderer.dispose();\n      renderer.forceContextLoss();\n      if (renderer.domElement.parentElement) {\n        renderer.domElement.parentElement.removeChild(renderer.domElement);\n      }\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [\n    linesGradient,\n    enabledWaves,\n    lineCount,\n    lineDistance,\n    topWavePosition,\n    middleWavePosition,\n    bottomWavePosition,\n    animationSpeed,\n    interactive,\n    bendRadius,\n    bendStrength,\n    mouseDamping,\n    parallax,\n    parallaxStrength,\n    backgroundColor,\n    lightMode\n  ]);\n\n  return (\n    <div\n      ref={containerRef}\n      className=\"floating-lines-container\"\n      style={{\n        mixBlendMode: lightMode ? 'normal' : mixBlendMode\n      }}\n    />\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0"
	]
}