{"_id":"@afromero/splattie-widget","_rev":"7-08a6af53084c546b2da1ecb107059fcb","name":"@afromero/splattie-widget","dist-tags":{"latest":"0.3.3"},"versions":{"0.1.0":{"name":"@afromero/splattie-widget","version":"0.1.0","keywords":["gaussian-splatting","3dgs","web-component","flame","avatar","interactive","splattie","3d","webgl"],"author":{"name":"Andres Romero","email":"me@afromero.co"},"license":"MIT","_id":"@afromero/splattie-widget@0.1.0","maintainers":[{"name":"afromero","email":"andres2912@gmail.com"}],"homepage":"https://github.com/affromero/splattie-widget#readme","bugs":{"url":"https://github.com/affromero/splattie-widget/issues"},"dist":{"shasum":"b71d9d93b1e47e3bfe74c349fecc48977807d5e1","tarball":"https://registry.npmjs.org/@afromero/splattie-widget/-/splattie-widget-0.1.0.tgz","fileCount":25,"integrity":"sha512-GceDqvlX51ovt4qxCuAiNWyCIF4VhrB3fSy2MUJQy1Q+RbmALhdTInQIIwn8U3QoJNKbS/vAA85VteRq0ee82Q==","signatures":[{"sig":"MEQCIDXgTIW7F6Ue2T+I5hZ23Q7aJtp9Vdo7cH3z9gyrdRvNAiAtSaODItCWyQGNRI6qfJDnL1nd5v1Nn/Nk7ggoMjD1Vg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15806266},"main":"dist/splattie-widget.umd.cjs","type":"module","types":"dist/index.d.ts","unpkg":"dist/splattie-widget.cdn.js","module":"dist/splattie-widget.js","exports":{".":{"import":"./dist/splattie-widget.js","require":"./dist/splattie-widget.umd.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/splattie-widget-react.js"}},"scripts":{"dev":"vite","test":"vitest run","build":"tsc && vite build && vite build --config vite.config.cdn.ts","test:watch":"vitest"},"_npmUser":{"name":"afromero","email":"andres2912@gmail.com"},"repository":{"url":"git+https://github.com/affromero/splattie-widget.git","type":"git"},"_npmVersion":"11.11.0","description":"Interactive 3D Gaussian Splatting web component - like Rive/Lottie for 3D","directories":{},"sideEffects":["./dist/splattie-widget.js","./dist/splattie-widget.cdn.js"],"_nodeVersion":"25.8.1","dependencies":{"jszip":"^3.10.1","three":">=0.180.0","@sparkjsdev/spark":"^2.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","vitest":"^3.1.0","typescript":"^5.8.0","@types/three":"^0.180.0"},"peerDependencies":{"react":">=18.0.0","three":">=0.180.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/splattie-widget_0.1.0_1779921104670_0.035914755440378476","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@afromero/splattie-widget","version":"0.1.1","keywords":["gaussian-splatting","3dgs","web-component","flame","avatar","interactive","splattie","3d","webgl"],"author":{"name":"Andres Romero","email":"me@afromero.co"},"license":"MIT","_id":"@afromero/splattie-widget@0.1.1","maintainers":[{"name":"afromero","email":"andres2912@gmail.com"}],"homepage":"https://github.com/affromero/splattie-widget#readme","bugs":{"url":"https://github.com/affromero/splattie-widget/issues"},"dist":{"shasum":"2080602487817bdb445920caf5c2e946ce0d3872","tarball":"https://registry.npmjs.org/@afromero/splattie-widget/-/splattie-widget-0.1.1.tgz","fileCount":7,"integrity":"sha512-ZjygHElpsI+R0li/WQn5jGssEJYeE3YzVN8q9ClHpPBSMJIEax04PFaeIqwK3lvMIrk21smpCInn80VzHDFoYA==","signatures":[{"sig":"MEQCICeOI+SQw7Jf/mLrwDpHPCbCgEIW2bHQpogVE4jN+NksAiAFElrbPXYJYk7rhdViSYLIVMmJS06YG/VMg3C2xHcWHA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15827146},"main":"dist/splattie-widget.umd.cjs","type":"module","types":"dist/index.d.ts","unpkg":"dist/splattie-widget.cdn.js","module":"dist/splattie-widget.js","exports":{".":{"import":"./dist/splattie-widget.js","require":"./dist/splattie-widget.umd.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/splattie-widget-react.js"}},"gitHead":"1e2871892fe95338b37a1687bc3a9ae1a630a482","scripts":{"dev":"vite","test":"vitest run","build":"tsc && vite build && vite build --config vite.config.cdn.ts","test:watch":"vitest"},"_npmUser":{"name":"afromero","email":"andres2912@gmail.com"},"repository":{"url":"git+https://github.com/affromero/splattie-widget.git","type":"git"},"_npmVersion":"10.8.2","description":"Interactive 3D Gaussian Splatting web component - like Rive/Lottie for 3D","directories":{},"sideEffects":["./dist/splattie-widget.js","./dist/splattie-widget.cdn.js"],"_nodeVersion":"20.20.2","dependencies":{"jszip":"^3.10.1","three":">=0.180.0","@sparkjsdev/spark":"^2.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","react":"^19.1.0","vitest":"^3.1.0","typescript":"^5.8.0","@types/react":"^19.1.0","@types/three":"^0.180.0"},"peerDependencies":{"react":">=18.0.0","three":">=0.180.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/splattie-widget_0.1.1_1779978394233_0.31668801332071417","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@afromero/splattie-widget","version":"0.2.0","keywords":["gaussian-splatting","3dgs","web-component","flame","avatar","interactive","splattie","3d","webgl"],"author":{"name":"Andres Romero","email":"me@afromero.co"},"license":"MIT","_id":"@afromero/splattie-widget@0.2.0","maintainers":[{"name":"afromero","email":"andres2912@gmail.com"}],"homepage":"https://github.com/affromero/splattie-widget#readme","bugs":{"url":"https://github.com/affromero/splattie-widget/issues"},"dist":{"shasum":"15dca6d5fc02f6ad90575be33cfeb66a2a1dfe83","tarball":"https://registry.npmjs.org/@afromero/splattie-widget/-/splattie-widget-0.2.0.tgz","fileCount":7,"integrity":"sha512-fZvZSEE1h3ZlJL7kOvmZx6fuzFgbO6JaFYHyi/1bqFBcqAQbud7It3FkLzcM3izpw8vO3n8xbB/O8JK3aVyheg==","signatures":[{"sig":"MEYCIQCuNbeXRqAt8K3Wc4nGfIsWeH1mXIi1UJpCSejfIg6EnwIhAMN1gIHM4v5JQu3tUB4nlWZSt6cAeh2YPWOwK5LI3Tjb","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15867608},"main":"dist/splattie-widget.umd.cjs","type":"module","types":"dist/index.d.ts","unpkg":"dist/splattie-widget.cdn.js","module":"dist/splattie-widget.js","exports":{".":{"import":"./dist/splattie-widget.js","require":"./dist/splattie-widget.umd.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/splattie-widget-react.js"}},"gitHead":"ece9603025d743b739f5c0f56f5fc51d88ab2b3d","scripts":{"ci":"npm run type-check && npm run coverage && npm run build","dev":"vite","test":"vitest run","build":"tsc && vite build && vite build --config vite.config.cdn.ts","coverage":"vitest run --coverage","test:watch":"vitest","type-check":"tsc --noEmit"},"_npmUser":{"name":"afromero","email":"andres2912@gmail.com"},"repository":{"url":"git+https://github.com/affromero/splattie-widget.git","type":"git"},"_npmVersion":"10.8.2","description":"Interactive 3D Gaussian Splatting web component - like Rive/Lottie for 3D","directories":{},"sideEffects":["./dist/splattie-widget.js","./dist/splattie-widget.cdn.js"],"_nodeVersion":"20.20.2","dependencies":{"jszip":"^3.10.1","three":">=0.180.0","@sparkjsdev/spark":"^2.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^29.1.1","react":"^19.1.0","vitest":"^3.1.0","typescript":"^5.8.0","@types/react":"^19.1.0","@types/three":"^0.180.0","@vitest/coverage-v8":"^3.2.4"},"peerDependencies":{"react":">=18.0.0","three":">=0.180.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/splattie-widget_0.2.0_1780191279379_0.6256129739468259","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@afromero/splattie-widget","version":"0.3.0","keywords":["gaussian-splatting","3dgs","web-component","flame","rigged","avatar","object","interactive","splattie","3d","webgl"],"author":{"name":"Andres Romero","email":"me@afromero.co"},"license":"MIT","_id":"@afromero/splattie-widget@0.3.0","maintainers":[{"name":"afromero","email":"andres2912@gmail.com"}],"homepage":"https://github.com/affromero/splattie-widget#readme","bugs":{"url":"https://github.com/affromero/splattie-widget/issues"},"dist":{"shasum":"11b435239fa935f489765678f56ae91154489602","tarball":"https://registry.npmjs.org/@afromero/splattie-widget/-/splattie-widget-0.3.0.tgz","fileCount":7,"integrity":"sha512-9zNQQzeiP1K1nFomWMTNPnReIMl2xYLPFm1+6HSvmiV/5oRwRM7urn98H3pbG2o4PvukNLnQINFBVnQXG79b1A==","signatures":[{"sig":"MEUCIDDAit3q/15BnPICG6i+Tz5e36tO/m/+K12SzwLgKvK8AiEAjWUz2EkhypM/BULPqeVFljBEQMJbMxzEw6sKv51Yg2c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15889267},"main":"dist/splattie-widget.umd.cjs","type":"module","types":"dist/index.d.ts","unpkg":"dist/splattie-widget.cdn.js","module":"dist/splattie-widget.js","exports":{".":{"import":"./dist/splattie-widget.js","require":"./dist/splattie-widget.umd.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/splattie-widget-react.js"}},"gitHead":"ceb790dab4eba46cdf67593fff37c644f0b04273","scripts":{"ci":"npm run type-check && npm run coverage && npm run build","dev":"vite","test":"vitest run","build":"tsc && vite build && vite build --config vite.config.cdn.ts","coverage":"vitest run --coverage","test:watch":"vitest","type-check":"tsc --noEmit"},"_npmUser":{"name":"afromero","email":"andres2912@gmail.com"},"repository":{"url":"git+https://github.com/affromero/splattie-widget.git","type":"git"},"_npmVersion":"10.8.2","description":"Interactive rigged 3D Gaussian Splatting web component - like Rive/Lottie for 3D","directories":{},"sideEffects":["./dist/splattie-widget.js","./dist/splattie-widget.cdn.js"],"_nodeVersion":"20.20.2","dependencies":{"jszip":"^3.10.1","three":">=0.180.0","@sparkjsdev/spark":"^2.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^29.1.1","react":"^19.1.0","vitest":"^3.1.0","typescript":"^5.8.0","@types/react":"^19.1.0","@types/three":"^0.180.0","@vitest/coverage-v8":"^3.2.4"},"peerDependencies":{"react":">=18.0.0","three":">=0.180.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/splattie-widget_0.3.0_1780268714896_0.39258597800369355","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@afromero/splattie-widget","version":"0.3.1","keywords":["gaussian-splatting","3dgs","web-component","flame","rigged","avatar","object","interactive","splattie","3d","webgl"],"author":{"name":"Andres Romero","email":"me@afromero.co"},"license":"MIT","_id":"@afromero/splattie-widget@0.3.1","maintainers":[{"name":"afromero","email":"andres2912@gmail.com"}],"homepage":"https://github.com/affromero/splattie-widget#readme","bugs":{"url":"https://github.com/affromero/splattie-widget/issues"},"dist":{"shasum":"a2a943e7c46743969200c51f623eaceecc2bafe3","tarball":"https://registry.npmjs.org/@afromero/splattie-widget/-/splattie-widget-0.3.1.tgz","fileCount":7,"integrity":"sha512-Oj9kOSr2J0ZdaGdDpc8PZ5Li0qswha65OXUwueU5abX6pk2A9MxnVrQjRpF+Jz/0MWhp0PWvmTHbflfeC5+hUw==","signatures":[{"sig":"MEQCIDhlZmUhut/0oFIz7JUjUGjHCVRsPxWSjhSU3SjXFWHBAiBY7uFs1US93uYrTmihHILOagFtFyOpLGeBan56DAi1kA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15891095},"main":"dist/splattie-widget.umd.cjs","type":"module","types":"dist/index.d.ts","unpkg":"dist/splattie-widget.cdn.js","module":"dist/splattie-widget.js","exports":{".":{"import":"./dist/splattie-widget.js","require":"./dist/splattie-widget.umd.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/splattie-widget-react.js"}},"gitHead":"a8a0e921debd49507c99e3513556d2888545e776","scripts":{"ci":"npm run type-check && npm run coverage && npm run build","dev":"vite","test":"vitest run","build":"tsc && vite build && vite build --config vite.config.cdn.ts","coverage":"vitest run --coverage","test:watch":"vitest","type-check":"tsc --noEmit"},"_npmUser":{"name":"afromero","email":"andres2912@gmail.com"},"repository":{"url":"git+https://github.com/affromero/splattie-widget.git","type":"git"},"_npmVersion":"10.8.2","description":"Interactive rigged 3D Gaussian Splatting web component - like Rive/Lottie for 3D","directories":{},"sideEffects":["./dist/splattie-widget.js","./dist/splattie-widget.cdn.js"],"_nodeVersion":"20.20.2","dependencies":{"jszip":"^3.10.1","three":">=0.180.0","@sparkjsdev/spark":"^2.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^29.1.1","react":"^19.1.0","vitest":"^3.1.0","typescript":"^5.8.0","@types/react":"^19.1.0","@types/three":"^0.180.0","@vitest/coverage-v8":"^3.2.4"},"peerDependencies":{"react":">=18.0.0","three":">=0.180.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/splattie-widget_0.3.1_1780674499737_0.8770624441266093","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@afromero/splattie-widget","version":"0.3.2","keywords":["gaussian-splatting","3dgs","web-component","flame","rigged","avatar","object","interactive","splattie","3d","webgl"],"author":{"name":"Andres Romero","email":"me@afromero.co"},"license":"MIT","_id":"@afromero/splattie-widget@0.3.2","maintainers":[{"name":"afromero","email":"andres2912@gmail.com"}],"homepage":"https://github.com/affromero/splattie-widget#readme","bugs":{"url":"https://github.com/affromero/splattie-widget/issues"},"dist":{"shasum":"57bfc8cbdc7349e977c500d9dae73634fff87edb","tarball":"https://registry.npmjs.org/@afromero/splattie-widget/-/splattie-widget-0.3.2.tgz","fileCount":7,"integrity":"sha512-SvlZfOTVkOTDS7gvpMP2M5lrjPLtWaRqUQr6w1MqLebZx+oQkSC5uuqLQdOEQfLQO00aJSjkJ0oOHesbn8ScMg==","signatures":[{"sig":"MEUCIQDQzdddfPiR83AFpHWD7YPJtj5x32rqf2oNNbp2C0L6LQIgQlujmuny9SfajCRikn8quB3o0mYnemmJcCYDjXt6my0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":8218783},"main":"dist/splattie-widget.umd.cjs","type":"module","types":"dist/index.d.ts","unpkg":"dist/splattie-widget.cdn.js","module":"dist/splattie-widget.js","exports":{".":{"import":"./dist/splattie-widget.js","require":"./dist/splattie-widget.umd.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/splattie-widget-react.js"}},"gitHead":"4bc450afdd34bf62913dac6ac9f962179ab5f2c0","scripts":{"ci":"npm run type-check && npm run coverage && npm run build","dev":"vite","test":"vitest run","build":"tsc && vite build && vite build --config vite.config.cdn.ts","coverage":"vitest run --coverage","test:watch":"vitest","type-check":"tsc --noEmit"},"_npmUser":{"name":"afromero","email":"andres2912@gmail.com"},"repository":{"url":"git+https://github.com/affromero/splattie-widget.git","type":"git"},"_npmVersion":"10.8.2","description":"Interactive rigged 3D Gaussian Splatting web component - like Rive/Lottie for 3D","directories":{},"sideEffects":["./dist/splattie-widget.js","./dist/splattie-widget.cdn.js"],"_nodeVersion":"20.20.2","dependencies":{"jszip":"^3.10.1","three":">=0.180.0","@sparkjsdev/spark":"github:affromero/spark#147a2ef100e45b72b32cc501beea55ad9ea8aeda"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^29.1.1","react":"^19.1.0","vitest":"^3.1.0","typescript":"^5.8.0","@types/react":"^19.1.0","@types/three":"^0.180.0","@vitest/coverage-v8":"^3.2.4"},"peerDependencies":{"react":">=18.0.0","three":">=0.180.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/splattie-widget_0.3.2_1786647666716_0.3157214511217292","host":"s3://npm-registry-packages-npm-production"}},"0.3.3":{"name":"@afromero/splattie-widget","version":"0.3.3","description":"Interactive rigged 3D Gaussian Splatting web component - like Rive/Lottie for 3D","type":"module","main":"dist/splattie-widget.umd.cjs","module":"dist/splattie-widget.js","types":"dist/index.d.ts","unpkg":"dist/splattie-widget.cdn.js","exports":{".":{"import":"./dist/splattie-widget.js","require":"./dist/splattie-widget.umd.cjs"},"./react":{"import":"./dist/splattie-widget-react.js","types":"./dist/react.d.ts"}},"sideEffects":["./dist/splattie-widget.js","./dist/splattie-widget.cdn.js"],"publishConfig":{"access":"public"},"scripts":{"dev":"vite","build":"tsc && vite build && vite build --config vite.config.cdn.ts","type-check":"tsc --noEmit","test":"vitest run","coverage":"vitest run --coverage","test:watch":"vitest","ci":"npm run type-check && npm run coverage && npm run build"},"keywords":["gaussian-splatting","3dgs","web-component","flame","rigged","avatar","object","interactive","splattie","3d","webgl"],"author":{"name":"Andres Romero","email":"me@afromero.co"},"repository":{"type":"git","url":"git+https://github.com/affromero/splattie-widget.git"},"license":"MIT","dependencies":{"@sparkjsdev/spark":"github:affromero/spark#147a2ef100e45b72b32cc501beea55ad9ea8aeda","jszip":"^3.10.1","three":">=0.180.0"},"devDependencies":{"@types/react":"^19.1.0","@types/three":"^0.180.0","@vitest/coverage-v8":"^3.2.4","jsdom":"^29.1.1","react":"^19.1.0","typescript":"^5.8.0","vite":"^6.0.0","vitest":"^3.1.0"},"peerDependencies":{"react":">=18.0.0","three":">=0.180.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_id":"@afromero/splattie-widget@0.3.3","gitHead":"03f1c47f37fe48f514b6d7109ca204bcd4418b80","bugs":{"url":"https://github.com/affromero/splattie-widget/issues"},"homepage":"https://github.com/affromero/splattie-widget#readme","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-e1pdvTGOAGesr/ytKY4MPWo5Eqvv9o+vGAxtHRxA20rV1heaqTQOV1Z6OYYDYs5270IRZKO+5ArVCNdjPj49/Q==","shasum":"8245ebb9057117f1d6f3123c172df722af798e4a","tarball":"https://registry.npmjs.org/@afromero/splattie-widget/-/splattie-widget-0.3.3.tgz","fileCount":7,"unpackedSize":8220705,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDCrYSqdY0XQIxXeo6vvUckwijLF1eBK42oMbvNnfWzvAiBL+SoNYMp0gtFOC9Z1mdcBE9tw3LdtSRhyM5UqawNzcQ=="}]},"_npmUser":{"name":"afromero","email":"andres2912@gmail.com"},"directories":{},"maintainers":[{"name":"afromero","email":"andres2912@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/splattie-widget_0.3.3_1786723761984_0.30223036892277166"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-27T22:31:44.549Z","modified":"2026-08-14T16:09:22.466Z","0.1.0":"2026-05-27T22:31:45.008Z","0.1.1":"2026-05-28T14:26:34.526Z","0.2.0":"2026-05-31T01:34:39.689Z","0.3.0":"2026-05-31T23:05:15.180Z","0.3.1":"2026-06-05T15:48:19.998Z","0.3.2":"2026-08-13T19:01:07.013Z","0.3.3":"2026-08-14T16:09:22.256Z"},"bugs":{"url":"https://github.com/affromero/splattie-widget/issues"},"author":{"name":"Andres Romero","email":"me@afromero.co"},"license":"MIT","homepage":"https://github.com/affromero/splattie-widget#readme","keywords":["gaussian-splatting","3dgs","web-component","flame","rigged","avatar","object","interactive","splattie","3d","webgl"],"repository":{"type":"git","url":"git+https://github.com/affromero/splattie-widget.git"},"description":"Interactive rigged 3D Gaussian Splatting web component - like Rive/Lottie for 3D","maintainers":[{"name":"afromero","email":"andres2912@gmail.com"}],"readme":"<div align=\"center\">\n\n<img src=\"assets/logo.svg\" alt=\"Splattie\" width=\"100\" />\n\n# splattie-widget\n\n**Interactive rigged 3D Gaussian Splatting - like Rive/Lottie for 3D**\n\n[![npm](https://img.shields.io/npm/v/@afromero/splattie-widget?color=blue)](https://www.npmjs.com/package/@afromero/splattie-widget)\n[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue?logo=typescript&logoColor=white)](https://typescriptlang.org)\n[![Spark](https://img.shields.io/badge/Spark_2.0-MIT-green)](https://github.com/sparkjsdev/spark)\n[![Three.js](https://img.shields.io/badge/Three.js-r170+-black?logo=three.js)](https://threejs.org)\n[![Tests](https://img.shields.io/badge/tests-91_passing-brightgreen)]()\n[![Bundle](https://img.shields.io/badge/format-.splattie-orange)]()\n\n[Quick Start](#quick-start) · [Format Spec](FORMAT.md) · [API](#api) · [Editor](#visual-editor) · [How It Works](#how-it-works)\n\n</div>\n\n---\n\n<p align=\"center\">\n  <img src=\"assets/demo.gif\" alt=\"Splattie Widget Demo\" width=\"600\" />\n</p>\n\nA web component that makes Gaussian splats **reactive**. One file, one tag. **Heads** track the cursor with their eyes, blink, and emote on hover/click (FLAME rig); **bodies** turn their head and torso toward visitors and pose with two-bone arm IK (SMPL-X rig); **objects** use arbitrary skeletons and sparse LBS weights for cursor-follow and direct pose editing; **animals** (quadruped mammals) ride that same object path with a SMAL-derived skeleton whose named neck/head joints drive cursor-follow head tracking. 60fps, client-side. The widget branches on the bundle's `assetType` (`head`, `body`, or `object`) — same tag for every kind; animals ship as `object` bundles tagged `metadata.category: quadruped_mammal`.\n\n**[See it live at afromero.co](https://afromero.co)** | **[Demos + editor at splattie.app](https://splattie.app)** | **[Generate your own from the repo](https://github.com/affromero/splattie)**\n\n## Quick Start\n\n```html\n<splattie-widget src=\"asset.splattie\"></splattie-widget>\n<script type=\"module\" src=\"https://unpkg.com/@afromero/splattie-widget\"></script>\n```\n\nOr via npm:\n\n```bash\nnpm install @afromero/splattie-widget\n```\n\n```typescript\nimport '@afromero/splattie-widget';\n```\n\n## The `.splattie` Format\n\n> **v0.x experimental.** Core files (PLY, FLAME bones) follow established standards. Expression basis and states may evolve.\n\nA ZIP bundle with a required `manifest.json` that declares every asset\nand locks the file's `formatVersion` to the widget version. See\n[`FORMAT.md`](FORMAT.md) for the full spec.\n\n```\nasset.splattie\n├── manifest.json             # (required) declares every asset + assetType + formatVersion\n├── *.ply or *.spz            # (required) Gaussian splats\n│\n│  # head (assetType: head) — FLAME rig:\n├── bone_tree.json            # (optional) Skeleton for skinning (5 FLAME bones)\n├── lbs_weight_20k.json       # (optional) Per-splat bone weights\n├── expression_basis.bin      # (optional) Blendshape basis\n│\n│  # body (assetType: body) — SMPL-X rig:\n├── skeleton.json             # (optional) 55-joint SMPL-X skeleton (baked-pose rest)\n├── lbs_weights.json          # (optional) Per-gaussian sparse LBS weights\n│\n│  # object (assetType: object) — arbitrary skeleton rig:\n├── skeleton.json             # (optional) Object joint hierarchy + rest positions\n├── lbs_weights.bin           # (optional) Binary sparse per-gaussian LBS weights\n│\n│  # quadruped mammal (assetType: object, metadata.category: quadruped_mammal):\n│  #   reuses the object rig above — skeleton.json carries named neck/head joints\n│  #   (SMAL fit to SuperAnimal keypoints) so the head follows the cursor\n│\n└── states.json               # (optional) Interaction states\n```\n\n<details>\n<summary><strong>Splat data</strong> (<code>.ply</code> or <code>.spz</code>) - standard 3DGS format</summary>\n\nEach splat has position, scale, rotation, opacity, and SH color. Auto-detected from file header. Works with any 3DGS method (LAM, DreamGaussian, InstantSplat, etc.). Standard format, unlikely to change.\n</details>\n\n<details>\n<summary><strong>bone_tree.json</strong> - skeleton hierarchy</summary>\n\n5 FLAME bones: root > neck > jaw, leftEye, rightEye. Used for SplatSkinning (dual quaternion).\n\n```json\n{\n  \"bones\": [{\n    \"name\": \"root\",\n    \"position\": [x, y, z],\n    \"children\": [{\n      \"name\": \"neck\",\n      \"position\": [x, y, z],\n      \"children\": [\n        { \"name\": \"jaw\", \"position\": [x, y, z] },\n        { \"name\": \"leftEye\", \"position\": [x, y, z] },\n        { \"name\": \"rightEye\", \"position\": [x, y, z] }\n      ]\n    }]\n  }]\n}\n```\n\nStable structure. Bone names are conventions, not hard requirements. Without it: no eye tracking, no jaw animation.\n</details>\n\n<details>\n<summary><strong>lbs_weight_20k.json</strong> - per-splat bone weights</summary>\n\n2D array `[num_splats][num_bones]`, each row sums to ~1.0. Widget selects top 4 per splat.\n\n```json\n[[0.8, 0.1, 0.05, 0.03, 0.02], ...]\n```\n\nStandard LBS format from FLAME. Without it: bones exist but nothing moves.\n</details>\n\n<details>\n<summary><strong>expression_basis.bin</strong> - FLAME blendshape basis</summary>\n\nPer-splat position displacements for each expression coefficient. Moves all splats coherently for smile, lip shapes, etc.\n\n```\nHeader: \"EXPR\" (4B) + num_vertices (u32 LE) + num_expressions (u32 LE)\nData:   float32 LE array, shape (num_vertices, num_expressions, 3)\n```\n\nOptional sidecar `expression_basis.json` with semantic labels:\n```json\n{ \"labels\": [\"jawDown\", \"lipsUp\", \"lipsL\", ...], \"num_expressions\": 50 }\n```\n\nExperimental format, may add compression. Without it: bone-driven expressions still work.\n</details>\n\n<details>\n<summary><strong>skeleton.json</strong> + <strong>lbs_weights.json</strong> - SMPL-X body rig (bodies)</summary>\n\nBodies (`assetType: body`) use a 55-joint SMPL-X skeleton instead of FLAME bones:\n\n```json\n// skeleton.json — joints in the BAKED (photographed) rest pose\n{ \"rig\": \"smplx\", \"jointCount\": 55, \"names\": [\"Pelvis\", \"L_Hip\", ...],\n  \"parents\": [-1, 0, 0, ...], \"restPositions\": [[x, y, z], ...] }\n\n// lbs_weights.json — sparse top-K per-gaussian skinning\n{ \"numGaussians\": 40000, \"k\": 4, \"indices\": [...], \"weights\": [...] }\n```\n\nThe body is exported already posed (arms at rest), so the widget's rest pose is the\nidentity. From there it drives **head + torso look-at** toward the cursor and a\n**two-bone arm IK** for editor posing, composing local joint rotations via SMPL-X\nforward kinematics + linear blend skinning. Without these: the gaussians render but\ndon't articulate.\n</details>\n\n<details>\n<summary><strong>skeleton.json</strong> + <strong>lbs_weights.bin</strong> - object rig</summary>\n\nObjects (`assetType: object`) use the same manifest-level LBS contract as bodies,\nbut with an arbitrary joint hierarchy:\n\n```json\n// skeleton.json\n{ \"rig\": \"puppeteer-object\", \"jointCount\": 12, \"names\": [\"root\", \"...\"],\n  \"parents\": [-1, 0, ...], \"restPositions\": [[x, y, z], ...] }\n\n// manifest excerpt\n{ \"weights\": { \"file\": \"lbs_weights.bin\", \"format\": \"lbsw-v1\" } }\n```\n\nThe binary weights file stores sparse uint16 joint indices and float16 weights for each\nGaussian. The widget projects terminal joints as editor handles, solves simple\njoint-chain rotations when you drag a handle, and uses root/joint cursor-follow\nsettings for lightweight interactivity.\n\n**Quadruped mammals** are a special case of this object rig: the backend fits a **SMAL**\nskeleton to **SuperAnimal-Quadruped** keypoints and exports it as a `puppeteer-object` rig\nwhose joints include named `neck`/`head`/`nose`. The widget detects those names and drives\nhead + eye cursor-follow (head tracking) instead of generic terminal-joint follow. The\nmanifest keeps `assetType: object` and sets `metadata.category: \"quadruped_mammal\"`.\n</details>\n\n<details>\n<summary><strong>states.json</strong> - interaction state definitions</summary>\n\nEach state (idle, hover, click) sets all 5 dimensions simultaneously.\n\n```json\n{\n  \"defaults\": {\n    \"camera\": { \"theta\": 0, \"phi\": 75, \"radius\": 0.5, \"fov\": 60 },\n    \"autoBlink\": { \"interval\": [2000, 7000], \"duration\": 150 }\n  },\n  \"states\": {\n    \"idle\": {\n      \"ghost\": { \"amplitude\": 0.003, \"frequency\": 0.4, \"wobble\": 0.2 },\n      \"expression\": { \"jawOpen\": 0, \"smile\": 0 },\n      \"camera\": { \"theta\": 0, \"phi\": 75, \"radius\": 0.5, \"fov\": 60 },\n      \"rotation\": [0, 0, 0],\n      \"tracking\": { \"eyes\": 1.0, \"head\": 0.1 }\n    },\n    \"hover\": { \"...\" : \"...\" },\n    \"click\": { \"...\" : \"...\" }\n  },\n  \"transitions\": {\n    \"idle->hover\": { \"duration\": 0.3, \"easing\": \"ease-out\" },\n    \"*->click\": { \"duration\": 0.1, \"easing\": \"snap\" }\n  }\n}\n```\n\nMost likely to evolve. Without it: sensible defaults (eyes track, gentle float, auto-blink).\n</details>\n\n### Creating Your Own\n\n**Visual editor**: `npm run dev`, adjust sliders, click \"Download .splattie\".\n\n**From scratch**: ZIP a `.ply` with any combination of the optional files.\n\n**From a photo or object image**: clone [Splattie](https://github.com/affromero/splattie)\nand run the backend pipeline yourself (needs a GPU) — heads (LAM), bodies (LHM), objects\n(TRELLIS + Puppeteer), or animals (TripoSplat + SMAL) — then bundle the result. The hosted\n[splattie.app](https://splattie.app) doesn't generate assets; it's for browsing the demos\nand editing an existing `.splattie` in the visual editor.\n\n## Five Dimensions of State\n\n| Dimension | Controls | Example |\n|-----------|----------|---------|\n| **Ghost** | Floating/bobbing | Gentle hover on idle, freeze on click |\n| **Expression** | FLAME blendshapes + bones | Smile on hover, surprise on click |\n| **Camera** | Spherical position | Zoom in on hover |\n| **Rotation** | Pitch/yaw/roll | Tilt head on hover |\n| **Tracking** | Cursor-follow intensity | Heads: eyes/head. Bodies: head/torso. Objects: root/joints. Animals: head/eyes |\n\nInterpolated between states with configurable easing and duration.\n\n<details>\n<summary><strong>Expression system details</strong></summary>\n\nTwo layers:\n\n**Bone-driven** (SplatSkinning, 5 FLAME bones):\n- Jaw open/close, neck pitch/yaw/roll\n- Eye gaze direction (left/right, up/down)\n- Brow raise/frown (left/right independently)\n\n**Blendshape-driven** (FLAME expression basis, 10+ PCA coefficients):\n- Moves all 20K splats coherently\n- Smile, lip shapes, jaw articulation, cheek/nose deformation\n- Spatial mask prevents beard/neck from deforming\n</details>\n\n## API\n\n| Attribute | Description |\n|-----------|-------------|\n| `src` | URL to `.splattie` file (or `.ply`/`.spz`) |\n| `background` | Background color hex (default: `#0e0e14`) |\n| `width` | CSS width (default: `100%`) |\n| `height` | CSS height (default: `400px`) |\n\n```javascript\nwidget.addEventListener('splatload', () => {});   // ready\nwidget.addEventListener('splathover', () => {});   // cursor over asset\nwidget.addEventListener('splatclick', () => {});   // clicked asset\nwidget.addEventListener('splatleave', () => {});   // cursor left\nwidget.setState('hover');                           // force transition\n```\n\n## Visual Editor\n\n```bash\nnpm run dev  # http://localhost:4002\n```\n\nSliders for all 5 dimensions, camera sphere widget, state tabs with copy-forward, FLAME blendshape controls (heads), on-canvas IK drag handles to pose limbs (bodies), skeleton handles for object pose editing, drag-and-drop `.splattie` upload, export when done.\n\n## How It Works\n\nBuilt on [Spark 2.0](https://github.com/sparkjsdev/spark) (MIT, World Labs).\n\n<details>\n<summary><strong>Architecture</strong></summary>\n\n1. **State machine** with per-dimension interpolation (lerp, slerp, ease curves)\n2. **SplatSkinning** (dual quaternion) driving 5 FLAME bones from expression + cursor data (heads)\n3. **SMPL-X skinning** (55-joint LBS) for bodies — head/torso look-at + analytic two-bone arm IK, composed via forward kinematics\n4. **Generic object skinning** for arbitrary skeletons (objects **and animals**) — root/joint cursor-follow, projected skeleton handles, and drag-to-pose chain solving. Quadruped mammals run this path with named `neck`/`head` joints, so the head follows the cursor instead of a terminal joint\n5. **Expression basis** (heads) - per-splat FLAME position offsets written to Spark's packed buffer (half-float, ~20K splats/frame)\n6. **Hit detection** via `readPixels` after render (pixel-perfect)\n7. **Auto-blink** (heads) with randomized interval and sine-curve via SplatEdit\n8. **Gyroscope** tracking on mobile (iOS permission prompt included)\n</details>\n\n## Mobile\n\nTouch + gyroscope drive the same cursor-follow tracking as desktop — eyes for heads, head/torso or rigged joints for bodies, objects, and animals — from device orientation, or touch position on tap. Returns to center when the finger lifts. iOS motion permission requested automatically.\n\n## Browser Support\n\nChrome, Firefox, Safari, Edge. WebGL 2 required. No COOP/COEP headers needed.\n\n## Acknowledgements\n\n- [LAM](https://github.com/aigc3d/LAM) (SIGGRAPH 2025) - single-image 3DGS heads. Zixuan Zeng et al., AIGC3D team\n- [LHM](https://github.com/aigc3d/LHM) (SIGGRAPH 2025) - single-image 3DGS bodies. AIGC3D team\n- [TRELLIS](https://github.com/microsoft/TRELLIS) - single-image 3D asset reconstruction. Microsoft.\n- [Puppeteer](https://github.com/snap-research/Puppeteer) - automatic skeleton and skinning for generated 3D assets. Snap Research.\n- [TripoSplat](https://github.com/VAST-AI-Research/TripoSplat) - single-image image-to-3D-gaussian reconstruction (default animal backend). VAST-AI Research.\n- [SMAL](https://smal.is.tue.mpg.de/) - parametric quadruped skeleton + shape (animals). Zuffi, Kanazawa, Jacobs, Black (MPI)\n- [SuperAnimal-Quadruped / DeepLabCut](https://github.com/DeepLabCut/DeepLabCut) - animal pose keypoints the SMAL fit anchors to. Mathis lab et al.\n- [FLAME](https://flame.is.tue.mpg.de/) - face model (heads). Tianye Li, Timo Bolkart, Michael J. Black, Hao Li, Javier Romero\n- [SMPL-X](https://smpl-x.is.tue.mpg.de/) - body model (bodies). Pavlakos, Choutas, Ghorbani, Bolkart, Osman, Tzionas, Black (MPI)\n- [Spark 2.0](https://github.com/sparkjsdev/spark) - World Labs (MIT)\n- [3D Gaussian Splatting](https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/) - Kerbl, Kopanas, Leimkuhler, Drettakis (INRIA)\n\n## License\n\nMIT\n","readmeFilename":"README.md"}