{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "FluidGlass-JS-TW",
	"title": "FluidGlass",
	"description": "Glassmorphism container with animated liquid distortion refraction.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "FluidGlass/FluidGlass.jsx",
			"content": "/* eslint-disable react/no-unknown-property */\nimport * as THREE from 'three';\nimport { useRef, useState, useEffect, memo } from 'react';\nimport { Canvas, createPortal, useFrame, useThree } from '@react-three/fiber';\nimport {\n  useFBO,\n  useGLTF,\n  useScroll,\n  Image,\n  Scroll,\n  Preload,\n  ScrollControls,\n  MeshTransmissionMaterial,\n  Text\n} from '@react-three/drei';\nimport { easing } from 'maath';\n\nconst IMAGE_URLS = [\n  'https://images.unsplash.com/photo-1783394327207-acf441e37dda?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwcm9maWxlLXBhZ2V8MzR8fHxlbnwwfHx8fHw%3D',\n  'https://images.unsplash.com/photo-1782977389500-dd7adad33ebe?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwcm9maWxlLXBhZ2V8MzZ8fHxlbnwwfHx8fHw%3D',\n  'https://images.unsplash.com/photo-1782094002386-7d9ae1f49f50?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwcm9maWxlLXBhZ2V8NDB8fHxlbnwwfHx8fHw%3D',\n  'https://images.unsplash.com/photo-1781242629922-6f39cc3671cd?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwcm9maWxlLXBhZ2V8NDR8fHxlbnwwfHx8fHw%3D',\n  'https://images.unsplash.com/photo-1779684474703-5c0519bcf7e8?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwcm9maWxlLXBhZ2V8NTJ8fHxlbnwwfHx8fHw%3D'\n];\n\nexport default function FluidGlass({\n  mode = 'lens',\n  lensProps = {},\n  barProps = {},\n  cubeProps = {},\n  backgroundColor = '#120F17',\n  textColor = '#ffffff'\n}) {\n  const Wrapper = mode === 'bar' ? Bar : mode === 'cube' ? Cube : Lens;\n  const rawOverrides = mode === 'bar' ? barProps : mode === 'cube' ? cubeProps : lensProps;\n\n  const {\n    navItems = [\n      { label: 'Home', link: '' },\n      { label: 'About', link: '' },\n      { label: 'Contact', link: '' }\n    ],\n    ...modeProps\n  } = rawOverrides;\n\n  return (\n    <Canvas\n      camera={{ position: [0, 0, 20], fov: 15 }}\n      gl={{ alpha: true, toneMapping: THREE.NoToneMapping }}\n      style={{ backgroundColor }}\n    >\n      <ScrollControls damping={0.2} pages={3} distance={0.4}>\n        {mode === 'bar' && <NavItems items={navItems} textColor={textColor} />}\n        <Wrapper modeProps={modeProps} backgroundColor={backgroundColor}>\n          <Scroll>\n            <Typography textColor={textColor} />\n            <Images />\n          </Scroll>\n          <Scroll html />\n          <Preload />\n        </Wrapper>\n      </ScrollControls>\n    </Canvas>\n  );\n}\n\nconst ModeWrapper = memo(function ModeWrapper({\n  children,\n  glb,\n  geometryKey,\n  lockToBottom = false,\n  followPointer = true,\n  modeProps = {},\n  backgroundColor = '#120F17',\n  ...props\n}) {\n  const ref = useRef();\n  const { nodes } = useGLTF(glb);\n  const buffer = useFBO();\n  const { viewport: vp } = useThree();\n  const [scene] = useState(() => new THREE.Scene());\n  const geoWidthRef = useRef(1);\n\n  useEffect(() => {\n    const geo = nodes[geometryKey]?.geometry;\n    geo.computeBoundingBox();\n    geoWidthRef.current = geo.boundingBox.max.x - geo.boundingBox.min.x || 1;\n  }, [nodes, geometryKey]);\n\n  useFrame((state, delta) => {\n    const { gl, viewport, pointer, camera } = state;\n    const v = viewport.getCurrentViewport(camera, [0, 0, 15]);\n\n    const destX = followPointer ? (pointer.x * v.width) / 2 : 0;\n    const destY = lockToBottom ? -v.height / 2 + 0.2 : followPointer ? (pointer.y * v.height) / 2 : 0;\n    easing.damp3(ref.current.position, [destX, destY, 15], 0.15, delta);\n\n    if (modeProps.scale == null) {\n      const maxWorld = v.width * 0.9;\n      const desired = maxWorld / geoWidthRef.current;\n      ref.current.scale.setScalar(Math.min(0.15, desired));\n    }\n\n    gl.setClearColor(0x000000, 0);\n    gl.setRenderTarget(buffer);\n    gl.render(scene, camera);\n    gl.setRenderTarget(null);\n    gl.setClearColor(0x000000, 0);\n  });\n\n  const { scale, ior, thickness, anisotropy, chromaticAberration, ...extraMat } = modeProps;\n\n  return (\n    <>\n      {createPortal(\n        <>\n          <mesh position={[0, 0, -5]} scale={[vp.width * 2, vp.height * 2, 1]}>\n            <planeGeometry />\n            <meshBasicMaterial color={backgroundColor} toneMapped={false} />\n          </mesh>\n          {children}\n        </>,\n        scene\n      )}\n      <mesh scale={[vp.width, vp.height, 1]}>\n        <planeGeometry />\n        <meshBasicMaterial map={buffer.texture} transparent toneMapped={false} />\n      </mesh>\n      <mesh ref={ref} scale={scale ?? 0.15} rotation-x={Math.PI / 2} geometry={nodes[geometryKey]?.geometry} {...props}>\n        <MeshTransmissionMaterial\n          buffer={buffer.texture}\n          ior={ior ?? 1.15}\n          thickness={thickness ?? 5}\n          anisotropy={anisotropy ?? 0.01}\n          chromaticAberration={chromaticAberration ?? 0.1}\n          {...extraMat}\n        />\n      </mesh>\n    </>\n  );\n});\n\nfunction Lens({ modeProps, ...p }) {\n  return <ModeWrapper glb=\"/assets/3d/lens.glb\" geometryKey=\"Cylinder\" followPointer modeProps={modeProps} {...p} />;\n}\n\nfunction Cube({ modeProps, ...p }) {\n  return <ModeWrapper glb=\"/assets/3d/cube.glb\" geometryKey=\"Cube\" followPointer modeProps={modeProps} {...p} />;\n}\n\nfunction Bar({ modeProps = {}, ...p }) {\n  const defaultMat = {\n    transmission: 1,\n    roughness: 0,\n    thickness: 10,\n    ior: 1.15,\n    color: '#ffffff',\n    attenuationColor: '#ffffff',\n    attenuationDistance: 0.25\n  };\n\n  return (\n    <ModeWrapper\n      glb=\"/assets/3d/bar.glb\"\n      geometryKey=\"Cube\"\n      lockToBottom\n      followPointer={false}\n      modeProps={{ ...defaultMat, ...modeProps }}\n      {...p}\n    />\n  );\n}\n\nfunction NavItems({ items, textColor }) {\n  const group = useRef();\n  const { viewport, camera } = useThree();\n\n  const DEVICE = {\n    mobile: { max: 639, spacing: 0.2, fontSize: 0.035 },\n    tablet: { max: 1023, spacing: 0.24, fontSize: 0.045 },\n    desktop: { max: Infinity, spacing: 0.3, fontSize: 0.045 }\n  };\n  const getDevice = () => {\n    const w = window.innerWidth;\n    return w <= DEVICE.mobile.max ? 'mobile' : w <= DEVICE.tablet.max ? 'tablet' : 'desktop';\n  };\n\n  const [device, setDevice] = useState(getDevice());\n\n  useEffect(() => {\n    const onResize = () => setDevice(getDevice());\n    window.addEventListener('resize', onResize);\n    return () => window.removeEventListener('resize', onResize);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  const { spacing, fontSize } = DEVICE[device];\n\n  useFrame(() => {\n    if (!group.current) return;\n    const v = viewport.getCurrentViewport(camera, [0, 0, 15]);\n    group.current.position.set(0, -v.height / 2 + 0.2, 15.1);\n\n    group.current.children.forEach((child, i) => {\n      child.position.x = (i - (items.length - 1) / 2) * spacing;\n    });\n  });\n\n  const handleNavigate = link => {\n    if (!link) return;\n    link.startsWith('#') ? (window.location.hash = link) : (window.location.href = link);\n  };\n\n  return (\n    <group ref={group} renderOrder={10}>\n      {items.map(({ label, link }) => (\n        <Text\n          key={label}\n          fontSize={fontSize}\n          color={textColor}\n          anchorX=\"center\"\n          anchorY=\"middle\"\n          depthWrite={false}\n          outlineWidth={0}\n          outlineBlur=\"20%\"\n          outlineColor=\"#000\"\n          outlineOpacity={0.5}\n          depthTest={false}\n          renderOrder={10}\n          onClick={e => {\n            e.stopPropagation();\n            handleNavigate(link);\n          }}\n          onPointerOver={() => (document.body.style.cursor = 'pointer')}\n          onPointerOut={() => (document.body.style.cursor = 'auto')}\n        >\n          {label}\n        </Text>\n      ))}\n    </group>\n  );\n}\n\nfunction Images() {\n  const group = useRef();\n  const data = useScroll();\n  const { height } = useThree(s => s.viewport);\n\n  useFrame(() => {\n    group.current.children[0].material.zoom = 1 + data.range(0, 1 / 3) / 3;\n    group.current.children[1].material.zoom = 1 + data.range(0, 1 / 3) / 3;\n    group.current.children[2].material.zoom = 1 + data.range(1.15 / 3, 1 / 3) / 2;\n    group.current.children[3].material.zoom = 1 + data.range(1.15 / 3, 1 / 3) / 2;\n    group.current.children[4].material.zoom = 1 + data.range(1.15 / 3, 1 / 3) / 2;\n  });\n\n  return (\n    <group ref={group}>\n      <Image position={[-2, 0, 0]} scale={[3, height / 1.1, 1]} url={IMAGE_URLS[0]} />\n      <Image position={[2, 0, 3]} scale={3} url={IMAGE_URLS[1]} />\n      <Image position={[-2.05, -height, 6]} scale={[1, 3, 1]} url={IMAGE_URLS[2]} />\n      <Image position={[-0.6, -height, 9]} scale={[1, 2, 1]} url={IMAGE_URLS[3]} />\n      <Image position={[0.75, -height, 10.5]} scale={1.5} url={IMAGE_URLS[4]} />\n    </group>\n  );\n}\n\nfunction Typography({ textColor }) {\n  const DEVICE = {\n    mobile: { fontSize: 0.2 },\n    tablet: { fontSize: 0.4 },\n    desktop: { fontSize: 0.6 }\n  };\n  const getDevice = () => {\n    const w = window.innerWidth;\n    return w <= 639 ? 'mobile' : w <= 1023 ? 'tablet' : 'desktop';\n  };\n\n  const [device, setDevice] = useState(getDevice());\n\n  useEffect(() => {\n    const onResize = () => setDevice(getDevice());\n    window.addEventListener('resize', onResize);\n    return () => window.removeEventListener('resize', onResize);\n  }, []);\n\n  const { fontSize } = DEVICE[device];\n\n  return (\n    <Text\n      position={[0, 0, 12]}\n      fontSize={fontSize}\n      letterSpacing={-0.05}\n      outlineWidth={0}\n      outlineBlur=\"20%\"\n      outlineColor=\"#000\"\n      outlineOpacity={0.5}\n      color={textColor}\n      anchorX=\"center\"\n      anchorY=\"middle\"\n    >\n      React Bits\n    </Text>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0",
		"@react-three/fiber@^9.3.0",
		"@react-three/drei@^10.7.4",
		"maath@^0.10.8"
	]
}