{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Ballpit-JS-CSS",
	"title": "Ballpit",
	"description": "Physics ball pit simulation with bouncing colorful spheres.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Ballpit/Ballpit.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport {\n  Vector3 as a,\n  MeshPhysicalMaterial as c,\n  InstancedMesh as d,\n  Timer as e,\n  AmbientLight as f,\n  SphereGeometry as g,\n  ShaderChunk as h,\n  Scene as i,\n  Color as l,\n  Object3D as m,\n  SRGBColorSpace as n,\n  MathUtils as o,\n  PMREMGenerator as p,\n  Vector2 as r,\n  WebGLRenderer as s,\n  PerspectiveCamera as t,\n  PointLight as u,\n  ACESFilmicToneMapping as v,\n  Plane as w,\n  Raycaster as y\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n  #e;\n  canvas;\n  camera;\n  cameraMinAspect;\n  cameraMaxAspect;\n  cameraFov;\n  maxPixelRatio;\n  minPixelRatio;\n  scene;\n  renderer;\n  #t;\n  size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n  render = this.#i;\n  onBeforeRender = () => {};\n  onAfterRender = () => {};\n  onAfterResize = () => {};\n  #s = false;\n  #n = false;\n  isDisposed = false;\n  #o;\n  #r;\n  #a;\n  #c = new e();\n  #h = { elapsed: 0, delta: 0 };\n  #l;\n  constructor(e) {\n    this.#e = { ...e };\n    this.#m();\n    this.#d();\n    this.#p();\n    this.resize();\n    this.#g();\n  }\n  #m() {\n    this.camera = new t();\n    this.cameraFov = this.camera.fov;\n  }\n  #d() {\n    this.scene = new i();\n  }\n  #p() {\n    if (this.#e.canvas) {\n      this.canvas = this.#e.canvas;\n    } else if (this.#e.id) {\n      this.canvas = document.getElementById(this.#e.id);\n    } else {\n      console.error('Three: Missing canvas or id parameter');\n    }\n    this.canvas.style.display = 'block';\n    const e = {\n      canvas: this.canvas,\n      powerPreference: 'high-performance',\n      ...(this.#e.rendererOptions ?? {})\n    };\n    this.renderer = new s(e);\n    this.renderer.outputColorSpace = n;\n  }\n  #g() {\n    if (!(this.#e.size instanceof Object)) {\n      window.addEventListener('resize', this.#f.bind(this));\n      if (this.#e.size === 'parent' && this.canvas.parentNode) {\n        this.#r = new ResizeObserver(this.#f.bind(this));\n        this.#r.observe(this.canvas.parentNode);\n      }\n    }\n    this.#o = new IntersectionObserver(this.#u.bind(this), {\n      root: null,\n      rootMargin: '0px',\n      threshold: 0\n    });\n    this.#o.observe(this.canvas);\n    document.addEventListener('visibilitychange', this.#v.bind(this));\n  }\n  #y() {\n    window.removeEventListener('resize', this.#f.bind(this));\n    this.#r?.disconnect();\n    this.#o?.disconnect();\n    document.removeEventListener('visibilitychange', this.#v.bind(this));\n  }\n  #u(e) {\n    this.#s = e[0].isIntersecting;\n    this.#s ? this.#w() : this.#z();\n  }\n  #v() {\n    if (this.#s) {\n      document.hidden ? this.#z() : this.#w();\n    }\n  }\n  #f() {\n    if (this.#a) clearTimeout(this.#a);\n    this.#a = setTimeout(this.resize.bind(this), 100);\n  }\n  resize() {\n    let e, t;\n    if (this.#e.size instanceof Object) {\n      e = this.#e.size.width;\n      t = this.#e.size.height;\n    } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n      e = this.canvas.parentNode.offsetWidth;\n      t = this.canvas.parentNode.offsetHeight;\n    } else {\n      e = window.innerWidth;\n      t = window.innerHeight;\n    }\n    this.size.width = e;\n    this.size.height = t;\n    this.size.ratio = e / t;\n    this.#x();\n    this.#b();\n    this.onAfterResize(this.size);\n  }\n  #x() {\n    this.camera.aspect = this.size.width / this.size.height;\n    if (this.camera.isPerspectiveCamera && this.cameraFov) {\n      if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n        this.#A(this.cameraMinAspect);\n      } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n        this.#A(this.cameraMaxAspect);\n      } else {\n        this.camera.fov = this.cameraFov;\n      }\n    }\n    this.camera.updateProjectionMatrix();\n    this.updateWorldSize();\n  }\n  #A(e) {\n    const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n    this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n  }\n  updateWorldSize() {\n    if (this.camera.isPerspectiveCamera) {\n      const e = (this.camera.fov * Math.PI) / 180;\n      this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n      this.size.wWidth = this.size.wHeight * this.camera.aspect;\n    } else if (this.camera.isOrthographicCamera) {\n      this.size.wHeight = this.camera.top - this.camera.bottom;\n      this.size.wWidth = this.camera.right - this.camera.left;\n    }\n  }\n  #b() {\n    this.renderer.setSize(this.size.width, this.size.height);\n    this.#t?.setSize(this.size.width, this.size.height);\n    let e = window.devicePixelRatio;\n    if (this.maxPixelRatio && e > this.maxPixelRatio) {\n      e = this.maxPixelRatio;\n    } else if (this.minPixelRatio && e < this.minPixelRatio) {\n      e = this.minPixelRatio;\n    }\n    this.renderer.setPixelRatio(e);\n    this.size.pixelRatio = e;\n  }\n  get postprocessing() {\n    return this.#t;\n  }\n  set postprocessing(e) {\n    this.#t = e;\n    this.render = e.render.bind(e);\n  }\n  #w() {\n    if (this.#n) return;\n    const animate = () => {\n      this.#l = requestAnimationFrame(animate);\n      this.#c.update();\n      this.#h.delta = this.#c.getDelta();\n      this.#h.elapsed += this.#h.delta;\n      this.onBeforeRender(this.#h);\n      this.render();\n      this.onAfterRender(this.#h);\n    };\n    this.#n = true;\n    this.#c.reset();\n    animate();\n  }\n  #z() {\n    if (this.#n) {\n      cancelAnimationFrame(this.#l);\n      this.#n = false;\n    }\n  }\n  #i() {\n    this.renderer.render(this.scene, this.camera);\n  }\n  clear() {\n    this.scene.traverse(e => {\n      if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n        Object.keys(e.material).forEach(t => {\n          const i = e.material[t];\n          if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n            i.dispose();\n          }\n        });\n        e.material.dispose();\n        e.geometry.dispose();\n      }\n    });\n    this.scene.clear();\n  }\n  dispose() {\n    this.#y();\n    this.#z();\n    this.#c.dispose();\n    this.clear();\n    this.#t?.dispose();\n    this.renderer.dispose();\n    this.renderer.forceContextLoss();\n    this.isDisposed = true;\n  }\n}\n\nconst b = new Map(),\n  A = new r();\nlet R = false;\nfunction S(e) {\n  const t = {\n    position: new r(),\n    nPosition: new r(),\n    hover: false,\n    touching: false,\n    onEnter() {},\n    onMove() {},\n    onClick() {},\n    onLeave() {},\n    ...e\n  };\n  (function (e, t) {\n    if (!b.has(e)) {\n      b.set(e, t);\n      if (!R) {\n        document.body.addEventListener('pointermove', M);\n        document.body.addEventListener('pointerleave', L);\n        document.body.addEventListener('click', C);\n\n        document.body.addEventListener('touchstart', TouchStart, { passive: false });\n        document.body.addEventListener('touchmove', TouchMove, { passive: false });\n        document.body.addEventListener('touchend', TouchEnd, { passive: false });\n        document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n        R = true;\n      }\n    }\n  })(e.domElement, t);\n  t.dispose = () => {\n    const t = e.domElement;\n    b.delete(t);\n    if (b.size === 0) {\n      document.body.removeEventListener('pointermove', M);\n      document.body.removeEventListener('pointerleave', L);\n      document.body.removeEventListener('click', C);\n\n      document.body.removeEventListener('touchstart', TouchStart);\n      document.body.removeEventListener('touchmove', TouchMove);\n      document.body.removeEventListener('touchend', TouchEnd);\n      document.body.removeEventListener('touchcancel', TouchEnd);\n\n      R = false;\n    }\n  };\n  return t;\n}\n\nfunction M(e) {\n  A.x = e.clientX;\n  A.y = e.clientY;\n  processInteraction();\n}\n\nfunction processInteraction() {\n  for (const [elem, t] of b) {\n    const i = elem.getBoundingClientRect();\n    if (D(i)) {\n      P(t, i);\n      if (!t.hover) {\n        t.hover = true;\n        t.onEnter(t);\n      }\n      t.onMove(t);\n    } else if (t.hover && !t.touching) {\n      t.hover = false;\n      t.onLeave(t);\n    }\n  }\n}\n\nfunction C(e) {\n  A.x = e.clientX;\n  A.y = e.clientY;\n  for (const [elem, t] of b) {\n    const i = elem.getBoundingClientRect();\n    P(t, i);\n    if (D(i)) t.onClick(t);\n  }\n}\n\nfunction L() {\n  for (const t of b.values()) {\n    if (t.hover) {\n      t.hover = false;\n      t.onLeave(t);\n    }\n  }\n}\n\nfunction TouchStart(e) {\n  if (e.touches.length > 0) {\n    e.preventDefault();\n    A.x = e.touches[0].clientX;\n    A.y = e.touches[0].clientY;\n\n    for (const [elem, t] of b) {\n      const rect = elem.getBoundingClientRect();\n      if (D(rect)) {\n        t.touching = true;\n        P(t, rect);\n        if (!t.hover) {\n          t.hover = true;\n          t.onEnter(t);\n        }\n        t.onMove(t);\n      }\n    }\n  }\n}\n\nfunction TouchMove(e) {\n  if (e.touches.length > 0) {\n    e.preventDefault();\n    A.x = e.touches[0].clientX;\n    A.y = e.touches[0].clientY;\n\n    for (const [elem, t] of b) {\n      const rect = elem.getBoundingClientRect();\n      P(t, rect);\n\n      if (D(rect)) {\n        if (!t.hover) {\n          t.hover = true;\n          t.touching = true;\n          t.onEnter(t);\n        }\n        t.onMove(t);\n      } else if (t.hover && t.touching) {\n        t.onMove(t);\n      }\n    }\n  }\n}\n\nfunction TouchEnd() {\n  for (const [, t] of b) {\n    if (t.touching) {\n      t.touching = false;\n      if (t.hover) {\n        t.hover = false;\n        t.onLeave(t);\n      }\n    }\n  }\n}\n\nfunction P(e, t) {\n  const { position: i, nPosition: s } = e;\n  i.x = A.x - t.left;\n  i.y = A.y - t.top;\n  s.x = (i.x / t.width) * 2 - 1;\n  s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n  const { x: t, y: i } = A;\n  const { left: s, top: n, width: o, height: r } = e;\n  return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n  constructor(e) {\n    this.config = e;\n    this.positionData = new Float32Array(3 * e.count).fill(0);\n    this.velocityData = new Float32Array(3 * e.count).fill(0);\n    this.sizeData = new Float32Array(e.count).fill(1);\n    this.center = new a();\n    this.#R();\n    this.setSizes();\n  }\n  #R() {\n    const { config: e, positionData: t } = this;\n    this.center.toArray(t, 0);\n    for (let i = 1; i < e.count; i++) {\n      const s = 3 * i;\n      t[s] = E(2 * e.maxX);\n      t[s + 1] = E(2 * e.maxY);\n      t[s + 2] = E(2 * e.maxZ);\n    }\n  }\n  setSizes() {\n    const { config: e, sizeData: t } = this;\n    t[0] = e.size0;\n    for (let i = 1; i < e.count; i++) {\n      t[i] = k(e.minSize, e.maxSize);\n    }\n  }\n  update(e) {\n    const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n    let r = 0;\n    if (t.controlSphere0) {\n      r = 1;\n      F.fromArray(s, 0);\n      F.lerp(i, 0.1).toArray(s, 0);\n      V.set(0, 0, 0).toArray(o, 0);\n    }\n    for (let idx = r; idx < t.count; idx++) {\n      const base = 3 * idx;\n      I.fromArray(s, base);\n      B.fromArray(o, base);\n      B.y -= e.delta * t.gravity * n[idx];\n      B.multiplyScalar(t.friction);\n      B.clampLength(0, t.maxVelocity);\n      I.add(B);\n      I.toArray(s, base);\n      B.toArray(o, base);\n    }\n    for (let idx = r; idx < t.count; idx++) {\n      const base = 3 * idx;\n      I.fromArray(s, base);\n      B.fromArray(o, base);\n      const radius = n[idx];\n      for (let jdx = idx + 1; jdx < t.count; jdx++) {\n        const otherBase = 3 * jdx;\n        O.fromArray(s, otherBase);\n        N.fromArray(o, otherBase);\n        const otherRadius = n[jdx];\n        _.copy(O).sub(I);\n        const dist = _.length();\n        const sumRadius = radius + otherRadius;\n        if (dist < sumRadius) {\n          const overlap = sumRadius - dist;\n          j.copy(_)\n            .normalize()\n            .multiplyScalar(0.5 * overlap);\n          H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n          T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n          I.sub(j);\n          B.sub(H);\n          I.toArray(s, base);\n          B.toArray(o, base);\n          O.add(j);\n          N.add(T);\n          O.toArray(s, otherBase);\n          N.toArray(o, otherBase);\n        }\n      }\n      if (t.controlSphere0) {\n        _.copy(F).sub(I);\n        const dist = _.length();\n        const sumRadius0 = radius + n[0];\n        if (dist < sumRadius0) {\n          const diff = sumRadius0 - dist;\n          j.copy(_.normalize()).multiplyScalar(diff);\n          H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n          I.sub(j);\n          B.sub(H);\n        }\n      }\n      if (Math.abs(I.x) + radius > t.maxX) {\n        I.x = Math.sign(I.x) * (t.maxX - radius);\n        B.x = -B.x * t.wallBounce;\n      }\n      if (t.gravity === 0) {\n        if (Math.abs(I.y) + radius > t.maxY) {\n          I.y = Math.sign(I.y) * (t.maxY - radius);\n          B.y = -B.y * t.wallBounce;\n        }\n      } else if (I.y - radius < -t.maxY) {\n        I.y = -t.maxY + radius;\n        B.y = -B.y * t.wallBounce;\n      }\n      const maxBoundary = Math.max(t.maxZ, t.maxSize);\n      if (Math.abs(I.z) + radius > maxBoundary) {\n        I.z = Math.sign(I.z) * (t.maxZ - radius);\n        B.z = -B.z * t.wallBounce;\n      }\n      I.toArray(s, base);\n      B.toArray(o, base);\n    }\n  }\n}\n\nclass Y extends c {\n  constructor(e) {\n    super(e);\n    this.uniforms = {\n      thicknessDistortion: { value: 0.1 },\n      thicknessAmbient: { value: 0 },\n      thicknessAttenuation: { value: 0.1 },\n      thicknessPower: { value: 2 },\n      thicknessScale: { value: 10 }\n    };\n    this.defines.USE_UV = '';\n    this.onBeforeCompile = e => {\n      Object.assign(e.uniforms, this.uniforms);\n      e.fragmentShader =\n        '\\n        uniform float thicknessPower;\\n        uniform float thicknessScale;\\n        uniform float thicknessDistortion;\\n        uniform float thicknessAmbient;\\n        uniform float thicknessAttenuation;\\n      ' +\n        e.fragmentShader;\n      e.fragmentShader = e.fragmentShader.replace(\n        'void main() {',\n        '\\n        void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n          vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n          float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n          #ifdef USE_COLOR\\n            vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n          #else\\n            vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n          #endif\\n          reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n        }\\n\\n        void main() {\\n      '\n      );\n      const t = h.lights_fragment_begin.replaceAll(\n        'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n        '\\n          RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n          RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n        '\n      );\n      e.fragmentShader = e.fragmentShader.replace('#include <lights_fragment_begin>', t);\n      if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n    };\n  }\n}\n\nconst X = {\n  count: 200,\n  colors: [0, 0, 0],\n  ambientColor: 16777215,\n  ambientIntensity: 1,\n  lightIntensity: 200,\n  materialParams: {\n    metalness: 0.5,\n    roughness: 0.5,\n    clearcoat: 1,\n    clearcoatRoughness: 0.15\n  },\n  minSize: 0.5,\n  maxSize: 1,\n  size0: 1,\n  gravity: 0.5,\n  friction: 0.9975,\n  wallBounce: 0.95,\n  maxVelocity: 0.15,\n  maxX: 5,\n  maxY: 5,\n  maxZ: 2,\n  controlSphere0: false,\n  followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n  constructor(e, t = {}) {\n    const i = { ...X, ...t };\n    const s = new z();\n    const n = new p(e, 0.04).fromScene(s).texture;\n    const o = new g();\n    const r = new Y({ envMap: n, ...i.materialParams });\n    r.envMapRotation.x = -Math.PI / 2;\n    super(o, r, i.count);\n    this.config = i;\n    this.physics = new W(i);\n    this.#S();\n    this.setColors(i.colors);\n  }\n  #S() {\n    this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n    this.add(this.ambientLight);\n    this.light = new u(this.config.colors[0], this.config.lightIntensity);\n    this.add(this.light);\n  }\n  setColors(e) {\n    if (Array.isArray(e) && e.length > 1) {\n      const t = (function (e) {\n        let t, i;\n        function setColors(e) {\n          t = e;\n          i = [];\n          t.forEach(col => {\n            i.push(new l(col));\n          });\n        }\n        setColors(e);\n        return {\n          setColors,\n          getColorAt: function (ratio, out = new l()) {\n            const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n            const idx = Math.floor(scaled);\n            const start = i[idx];\n            if (idx >= t.length - 1) return start.clone();\n            const alpha = scaled - idx;\n            const end = i[idx + 1];\n            out.r = start.r + alpha * (end.r - start.r);\n            out.g = start.g + alpha * (end.g - start.g);\n            out.b = start.b + alpha * (end.b - start.b);\n            return out;\n          }\n        };\n      })(e);\n      for (let idx = 0; idx < this.count; idx++) {\n        this.setColorAt(idx, t.getColorAt(idx / this.count));\n        if (idx === 0) {\n          this.light.color.copy(t.getColorAt(idx / this.count));\n        }\n      }\n      this.instanceColor.needsUpdate = true;\n    }\n  }\n  update(e) {\n    this.physics.update(e);\n    for (let idx = 0; idx < this.count; idx++) {\n      U.position.fromArray(this.physics.positionData, 3 * idx);\n      if (idx === 0 && this.config.followCursor === false) {\n        U.scale.setScalar(0);\n      } else {\n        U.scale.setScalar(this.physics.sizeData[idx]);\n      }\n      U.updateMatrix();\n      this.setMatrixAt(idx, U.matrix);\n      if (idx === 0) this.light.position.copy(U.position);\n    }\n    this.instanceMatrix.needsUpdate = true;\n  }\n}\n\nfunction createBallpit(e, t = {}) {\n  const i = new x({\n    canvas: e,\n    size: 'parent',\n    rendererOptions: { antialias: true, alpha: true }\n  });\n  let s;\n  i.renderer.toneMapping = v;\n  i.camera.position.set(0, 0, 20);\n  i.camera.lookAt(0, 0, 0);\n  i.cameraMaxAspect = 1.5;\n  i.resize();\n  initialize(t);\n  const n = new y();\n  const o = new w(new a(0, 0, 1), 0);\n  const r = new a();\n  let c = false;\n\n  e.style.touchAction = 'none';\n  e.style.userSelect = 'none';\n  e.style.webkitUserSelect = 'none';\n\n  const h = S({\n    domElement: e,\n    onMove() {\n      n.setFromCamera(h.nPosition, i.camera);\n      i.camera.getWorldDirection(o.normal);\n      n.ray.intersectPlane(o, r);\n      s.physics.center.copy(r);\n      s.config.controlSphere0 = true;\n    },\n    onLeave() {\n      s.config.controlSphere0 = false;\n    }\n  });\n  function initialize(e) {\n    if (s) {\n      i.clear();\n      i.scene.remove(s);\n    }\n    s = new Z(i.renderer, e);\n    i.scene.add(s);\n  }\n  i.onBeforeRender = e => {\n    if (!c) s.update(e);\n  };\n  i.onAfterResize = e => {\n    s.config.maxX = e.wWidth / 2;\n    s.config.maxY = e.wHeight / 2;\n  };\n  return {\n    three: i,\n    get spheres() {\n      return s;\n    },\n    setCount(e) {\n      initialize({ ...s.config, count: e });\n    },\n    updateConfig(newProps) {\n      if (newProps.count !== undefined && newProps.count !== s.config.count) {\n        initialize({ ...s.config, ...newProps });\n      } else {\n        Object.assign(s.config, newProps);\n        if (newProps.colors) {\n          s.setColors(s.config.colors);\n        }\n        if (newProps.minSize !== undefined || newProps.maxSize !== undefined || newProps.size0 !== undefined) {\n          s.physics.setSizes();\n        }\n      }\n    },\n    togglePause() {\n      c = !c;\n    },\n    dispose() {\n      h.dispose();\n      i.dispose();\n    }\n  };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n  const canvasRef = useRef(null);\n  const spheresInstanceRef = useRef(null);\n  const isFirstRender = useRef(true);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n    return () => {\n      if (spheresInstanceRef.current) {\n        spheresInstanceRef.current.dispose();\n        spheresInstanceRef.current = null;\n      }\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    if (isFirstRender.current) {\n      isFirstRender.current = false;\n      return;\n    }\n    if (spheresInstanceRef.current) {\n      spheresInstanceRef.current.updateConfig({ followCursor, ...props });\n    }\n  }, [props, followCursor]);\n\n  return <canvas className={className} ref={canvasRef} style={{ width: '100%', height: '100%' }} />;\n};\n\nexport default Ballpit;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0"
	]
}