{"_id":"morphicons","_rev":"13-d30099ec65db15e6276c3677ae76fe90","name":"morphicons","dist-tags":{"latest":"1.7.1"},"versions":{"0.1.0":{"name":"morphicons","version":"0.1.0","keywords":["icons","svg","morphing","animation","spring","lucide","feather","tabler","react","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@0.1.0","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"b37fe4755e1f99445abdfdcad08a695a1a6d7bee","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-0.1.0.tgz","fileCount":11,"integrity":"sha512-1H6nNFhkuFYuC9y/7S3DaGStolsvKzFGxiol2IDHjZcZiB1Gr7sjXDI+sfCoI1na4nQQL2z955MiRHBREBF0OA==","signatures":[{"sig":"MEUCIQCrAiwcULlyBNXGoeqJYPkS7gfKbrnmS1q6bXnvsSbv2wIgEHFSF8cWuJIs0bUftZd8u2jDPdWgjqdFZ1Zepnm6KPA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":67698},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"}},"gitHead":"72bd1ee5d8ecd470c64aef01129c1e5bda7cc29a","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"}],"_npmVersion":"11.16.0","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","lucide":"^1.28.0","tsdown":"^0.22.14","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","@size-limit/preset-small-lib":"^13.0.3"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_0.1.0_1785575176601_0.24497029910127877","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"morphicons","version":"0.1.1","keywords":["icons","svg","morphing","animation","spring","lucide","feather","tabler","react","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@0.1.1","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"97a1f255ab74d0b23a7b0d7ecc42d469d71ee890","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-0.1.1.tgz","fileCount":11,"integrity":"sha512-E7e4Bgh51zSGBg22Id66/WqY/vdyaWAuIgI1TCMJ9zkAH+B+HWqdjFKpMO9t17olOF/SurDbXaQ7PDKjxtcc9Q==","signatures":[{"sig":"MEQCIBBU3yteYivU4yTtW+mQ5L68jIDvWw/8aDnMUSdRZlbMAiBq7EyyhnCAYh+9Wk5BFox4m7xO1CJrtnxGRMdV2qsulg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":67698},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"}},"gitHead":"4220251dbe0617076862f637ed14c57187cf3d5c","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:92b78d00-ed29-46bb-911e-ae09be9d07a1"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","lucide":"^1.28.0","tsdown":"^0.22.14","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","@size-limit/preset-small-lib":"^13.0.3"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_0.1.1_1785575352416_0.6202555737793569","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"morphicons","version":"1.0.0","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","feather","tabler","react","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.0.0","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"adce2f9ebf0a62dd2e86195604468506c54a2250","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.0.0.tgz","fileCount":11,"integrity":"sha512-K2kUoq0rYu4ZQTYdUmFJkarQPRb35QaknK3e+pu5yTJ/7QvgdHi+SoUYguYXT+hPeXWn/X6D+M3I27kZYm3BUA==","signatures":[{"sig":"MEYCIQDvrJykVClkui+NyPX72wYLSf64YzNUejDzB2ZDABh1sgIhANHdEd1MDR20+l58bY2s9ceo4mQgyoXb7uARrW90aN2n","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":67814},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"}},"gitHead":"90507fd81c09304cc0e2a43f06cbd126e56d38d6","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","lucide":"^1.28.0","tsdown":"^0.22.14","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","@size-limit/preset-small-lib":"^13.0.3"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.0.0_1785577861117_0.8058091482613163","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"morphicons","version":"1.1.0","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.1.0","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"5ee7584d2d8eb3bedfc9eaf8953ee944ec942169","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.1.0.tgz","fileCount":11,"integrity":"sha512-rbUs/wBjKfNpNBuWLVirApBkasD4gHpzsIXjwyHgT9p9z/0Z2qWUVQHWHxzEtaOrlsaJ9+/QzqA0e32puj+ljQ==","signatures":[{"sig":"MEYCIQCwvcgLYMepHAiXNI2XROY6fuOAMrBYgD8OWTUlDqfNOwIhALVteeRFj8BV7nMG3WAOGksr9tB7iZgBSOGnXff01VoD","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":70911},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"}},"gitHead":"a6c3f4ea07248a9d69b5380b2a263da76f0b4def","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","lucide":"^1.28.0","tsdown":"^0.22.14","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","@size-limit/preset-small-lib":"^13.0.3"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.1.0_1785586252224_0.7751173368427826","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"morphicons","version":"1.1.1","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.1.1","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"97fc9528891d4c3eead4423c2e40370587e6a1cf","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.1.1.tgz","fileCount":11,"integrity":"sha512-fBYNTVo39zzFUXEg7LsSf7zee4shk/vankDX9FwvdU86dVdFqYhdu/Ek6uNJObtR/jXblD2qo0x8gHiGIpiMYw==","signatures":[{"sig":"MEUCIA2UGB/RtTDASqar2oHH7M7hUk7IHpzFKFevOZeN/YqEAiEAwz8AIDMgTTjAFayq5ybp6+IqigeLROuAKgwgh9bXkZI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":72922},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"}},"gitHead":"e5f884f8ae9bf675afea0769896dfbfd3f707b23","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","lucide":"^1.28.0","tsdown":"^0.22.14","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","@size-limit/preset-small-lib":"^13.0.3"},"peerDependencies":{"react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.1.1_1785607447997_0.5467336088972292","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"morphicons","version":"1.2.0","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","vue","lucide-vue-next","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.2.0","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"3443d4bf4b15a7bccae02c72ccae5ccd5e081eca","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.2.0.tgz","fileCount":13,"integrity":"sha512-HuPIwS5PVv/N85k/38e5qhL1vDZArtumd9je0xB1H77eT20HuiCswmmWPogiRj7t9MAiBlcNNoPkfxSxmSzuTw==","signatures":[{"sig":"MEQCIEv8hhirAPOm5HwuszaUWaN+0gG27DHrafQE4rvP61XgAiBg+brnusv3OQoaFfK26ug6xv4JSPLGdXJ0HFddMKL7ng==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":83433},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./vue":{"types":"./dist/vue.d.ts","default":"./dist/vue.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"}},"gitHead":"b2fd866130decb99c9e605e9059953df89a75a6a","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit && tsc -p tsconfig.vue.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"},{"gzip":true,"name":"core + dom + vue (MorphIcon, vue external)","path":"dist/vue.js","limit":"8.5 KB","ignore":["vue"],"import":"*"}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.40","react":"^19.2.8","lucide":"^1.28.0","tsdown":"^0.22.14","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","@size-limit/preset-small-lib":"^13.0.3"},"peerDependencies":{"vue":">=3.3","react":">=18"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.2.0_1785745359642_0.6861594624793754","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"morphicons","version":"1.4.0","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","react-native","lucide-react-native","vue","lucide-vue-next","svelte","lucide-svelte","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.4.0","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"9167b05053b56961b59d47a2e1c39314234b8e3f","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.4.0.tgz","fileCount":20,"integrity":"sha512-Yerf8QnogXcyaW3rtm6IEnrDgeh5gFmk/md5dNdyoqjDMbCmgI9UTWO0lMccKcjwAHZFNULhZCRolWqB01BLzw==","signatures":[{"sig":"MEUCIFZOKhXXrF/OkqUW/yaURN+gcWhQJlC3U2v3y6gzonJCAiEAqjS/xMDixWvOu9PS7yOILZ5eXjlzcKYLW7S5uIXj73o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":109735},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./vue":{"types":"./dist/vue.d.ts","default":"./dist/vue.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"./svelte":{"types":"./dist/svelte.d.ts","svelte":"./dist/svelte.js","default":"./dist/svelte.js"},"./react-native":{"types":"./dist/react-native.d.ts","default":"./dist/react-native.js"}},"gitHead":"5e46df2c00a104f97ea7253d8fd3e428724595ca","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown && bun scripts/build-svelte.ts","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit && tsc -p tsconfig.react-native.json --noEmit && tsc -p tsconfig.vue.json --noEmit && tsc -p tsconfig.svelte.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"},{"gzip":true,"name":"core + dom + react-native (MorphIcon, react/rn/rnsvg external)","path":"dist/react-native.js","limit":"9 KB","ignore":["react","react-native","react-native-svg"],"import":"*"},{"gzip":true,"name":"core + dom + vue (MorphIcon, vue external)","path":"dist/vue.js","limit":"8.5 KB","ignore":["vue"],"import":"*"},{"gzip":true,"name":"core + dom + svelte TS half (svelte external)","path":"dist/svelte-shared.js","limit":"8 KB","ignore":["svelte"],"import":"*"},{"gzip":true,"name":"svelte component (ships as source, consumer-compiled)","path":"dist/MorphIcon.svelte","limit":"1.4 KB","disablePlugins":["@size-limit/esbuild"]}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.40","react":"^19.2.8","lucide":"^1.28.0","svelte":"^5.56.8","tsdown":"^0.22.14","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","react-native":"^0.86.2","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","react-native-svg":"^15.15.5","@size-limit/preset-small-lib":"^13.0.3","@happy-dom/global-registrator":"^20.11.1"},"peerDependencies":{"vue":">=3.3","react":">=18","svelte":">=5","react-native":">=0.71","react-native-svg":">=14"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true},"svelte":{"optional":true},"react-native":{"optional":true},"react-native-svg":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.4.0_1785847343706_0.13684217431740442","host":"s3://npm-registry-packages-npm-production"}},"1.4.1":{"name":"morphicons","version":"1.4.1","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","react-native","lucide-react-native","vue","lucide-vue-next","svelte","lucide-svelte","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.4.1","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"c3e05f5ce8ce4847182bd91467d7f9e052670e3a","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.4.1.tgz","fileCount":20,"integrity":"sha512-l+F51rwvCAvuTnVq9eKrrwjd2oM40dW/fQ6fWQyz2FVSwB9Gq+mcnznJQPeFpenDvBY9qFEg17WTTCwChBdabw==","signatures":[{"sig":"MEYCIQDxgcNm8eDzJ5ELq9Qu6zAC20VDpQf2ggp3d2MJq4xflwIhAO8i768RYmMzPe/oZrmnZoSRVehpCsZH6MFoxWIPFejO","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":110785},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./vue":{"types":"./dist/vue.d.ts","default":"./dist/vue.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"./svelte":{"types":"./dist/svelte.d.ts","svelte":"./dist/svelte.js","default":"./dist/svelte.js"},"./react-native":{"types":"./dist/react-native.d.ts","default":"./dist/react-native.js"}},"gitHead":"2cdc9292c2b6165274d34ea16909a941cf550cab","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown && bun scripts/build-svelte.ts","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit && tsc -p tsconfig.react-native.json --noEmit && tsc -p tsconfig.vue.json --noEmit && tsc -p tsconfig.svelte.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"},{"gzip":true,"name":"core + dom + react-native (MorphIcon, react/rn/rnsvg external)","path":"dist/react-native.js","limit":"9 KB","ignore":["react","react-native","react-native-svg"],"import":"*"},{"gzip":true,"name":"core + dom + vue (MorphIcon, vue external)","path":"dist/vue.js","limit":"8.5 KB","ignore":["vue"],"import":"*"},{"gzip":true,"name":"core + dom + svelte TS half (svelte external)","path":"dist/svelte-shared.js","limit":"8 KB","ignore":["svelte"],"import":"*"},{"gzip":true,"name":"svelte component (ships as source, consumer-compiled)","path":"dist/MorphIcon.svelte","limit":"1.4 KB","disablePlugins":["@size-limit/esbuild"]}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.40","react":"^19.2.8","lucide":"^1.28.0","svelte":"^5.56.8","tsdown":"^0.22.14","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","react-native":"^0.86.2","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","react-native-svg":"^15.15.5","@size-limit/preset-small-lib":"^13.0.3","@happy-dom/global-registrator":"^20.11.1"},"peerDependencies":{"vue":">=3.3","react":">=18","svelte":">=5","react-native":">=0.71","react-native-svg":">=14"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true},"svelte":{"optional":true},"react-native":{"optional":true},"react-native-svg":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.4.1_1785871316591_0.25927188651756494","host":"s3://npm-registry-packages-npm-production"}},"1.4.2":{"name":"morphicons","version":"1.4.2","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","react-native","lucide-react-native","vue","lucide-vue-next","svelte","lucide-svelte","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.4.2","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"21cf904e9c1f54ff1b68847b0ba52bafa9cc07fd","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.4.2.tgz","fileCount":20,"integrity":"sha512-kw7JF+2PcDCo12XhHRq0Yumv2o7QNY88kHIpfm93W21lnhIALpI7vxAnbbGfjaK1Wanzo1+Q3NQJETjXTxO2dg==","signatures":[{"sig":"MEQCIGwzMoSilqcbi62qhz0ECJg9wW8XCnDdLWM/Etww0i45AiAvcUxbPCh/TPwRS+INsCNXnvEBtamuTSe/Ug5QgABxfw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":116455},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./vue":{"types":"./dist/vue.d.ts","default":"./dist/vue.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"./svelte":{"types":"./dist/svelte.d.ts","svelte":"./dist/svelte.js","default":"./dist/svelte.js"},"./react-native":{"types":"./dist/react-native.d.ts","default":"./dist/react-native.js"}},"gitHead":"cee6f3071cda9fce01a8b5a58a8f67b3e6d66060","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown && bun scripts/build-svelte.ts","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit && tsc -p tsconfig.react-native.json --noEmit && tsc -p tsconfig.vue.json --noEmit && tsc -p tsconfig.svelte.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"},{"gzip":true,"name":"core + dom + react-native (MorphIcon, react/rn/rnsvg external)","path":"dist/react-native.js","limit":"9 KB","ignore":["react","react-native","react-native-svg"],"import":"*"},{"gzip":true,"name":"core + dom + vue (MorphIcon, vue external)","path":"dist/vue.js","limit":"8.5 KB","ignore":["vue"],"import":"*"},{"gzip":true,"name":"core + dom + svelte TS half (svelte external)","path":"dist/svelte-shared.js","limit":"8 KB","ignore":["svelte"],"import":"*"},{"gzip":true,"name":"svelte component (ships as source, consumer-compiled)","path":"dist/MorphIcon.svelte","limit":"1.4 KB","disablePlugins":["@size-limit/esbuild"]}],"_npmVersion":"11.17.0","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.40","react":"^19.2.8","lucide":"^1.28.0","svelte":"^5.56.8","tsdown":"^0.22.14","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","react-native":"^0.86.2","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","react-native-svg":"^15.15.5","@size-limit/preset-small-lib":"^13.0.3","@happy-dom/global-registrator":"^20.11.1"},"peerDependencies":{"vue":">=3.3","react":">=18","svelte":">=5","react-native":">=0.71","react-native-svg":">=14"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true},"svelte":{"optional":true},"react-native":{"optional":true},"react-native-svg":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.4.2_1786037665846_0.970957326173342","host":"s3://npm-registry-packages-npm-production"}},"1.5.0":{"name":"morphicons","version":"1.5.0","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","react-native","lucide-react-native","vue","lucide-vue-next","svelte","lucide-svelte","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.5.0","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"47e09931f80026f700bb23e95b0c55848d1ae0ec","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.5.0.tgz","fileCount":24,"integrity":"sha512-N68YHSAovKUiM/Il/hMCme5qbjFYhgI3LvxEJccIBQwaT1rTNaQdFtgCm8Hu5DA2fwPViWjf4sQ6sS8MW+/iCg==","signatures":[{"sig":"MEQCIGoq/QVqeq8YrypPZpkPXIyxvwK7xE2Yy7DuTn67A72ZAiB+P6s7NmdWt0cCD2MWi5eo14mGGzJCgJxDG1MVKYedAw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":131492},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./vue":{"types":"./dist/vue.d.ts","default":"./dist/vue.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"./svelte":{"types":"./dist/svelte.d.ts","svelte":"./dist/svelte.js","default":"./dist/svelte.js"},"./adapters":{"types":"./dist/adapters.d.ts","default":"./dist/adapters.js"},"./react-native":{"types":"./dist/react-native.d.ts","default":"./dist/react-native.js"}},"gitHead":"00a2911ba117e918adbcfb4742cae5b99b267a06","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown && bun scripts/build-svelte.ts","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit && tsc -p tsconfig.react-native.json --noEmit && tsc -p tsconfig.vue.json --noEmit && tsc -p tsconfig.svelte.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"adapters (aggregate — grows by design, see per-export gates)","path":"dist/adapters.js","limit":"4.4 KB","import":"*"},{"gzip":true,"name":"adapters › svgToIcon (per-export shakeability gate)","path":"dist/adapters.js","limit":"3.7 KB","import":"{ svgToIcon }"},{"gzip":true,"name":"adapters › maskTarget (per-export shakeability gate)","path":"dist/adapters.js","limit":"0.8 KB","import":"{ maskTarget }"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"},{"gzip":true,"name":"core + dom + react-native (MorphIcon, react/rn/rnsvg external)","path":"dist/react-native.js","limit":"9 KB","ignore":["react","react-native","react-native-svg"],"import":"*"},{"gzip":true,"name":"core + dom + vue (MorphIcon, vue external)","path":"dist/vue.js","limit":"8.5 KB","ignore":["vue"],"import":"*"},{"gzip":true,"name":"core + dom + svelte TS half (svelte external)","path":"dist/svelte-shared.js","limit":"8 KB","ignore":["svelte"],"import":"*"},{"gzip":true,"name":"svelte component (ships as source, consumer-compiled)","path":"dist/MorphIcon.svelte","limit":"1.4 KB","disablePlugins":["@size-limit/esbuild"]}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.40","react":"^19.2.8","lucide":"^1.28.0","svelte":"^5.56.8","tsdown":"^0.22.14","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","react-native":"^0.86.2","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","react-native-svg":"^15.15.5","@size-limit/preset-small-lib":"^13.0.3","@happy-dom/global-registrator":"^20.11.1"},"peerDependencies":{"vue":">=3.3","react":">=18","svelte":">=5","react-native":">=0.71","react-native-svg":">=14"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true},"svelte":{"optional":true},"react-native":{"optional":true},"react-native-svg":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.5.0_1786102499940_0.8650342289809072","host":"s3://npm-registry-packages-npm-production"}},"1.6.0":{"name":"morphicons","version":"1.6.0","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","react-native","lucide-react-native","vue","lucide-vue-next","svelte","lucide-svelte","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.6.0","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"b575bfdb7b26a6d12b5ed7407bd59e50c66c4f01","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.6.0.tgz","fileCount":24,"integrity":"sha512-Sq1110Tzm53D07V6T2smlU6aXVb3XACdF7MSred76ttuhpVj6Rvt4i4D5N7m/GYEJ5GaK89XBTJRBm8SQvGWGA==","signatures":[{"sig":"MEUCIQDxlH4ItR+O+o1fd/19oxsEU3aGASg/L0LvrxLKUbrlhQIgNqHFObQCteazI+XQBEy+0hXonWyVSG+NQCAQ8wMjzN4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":138604},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./vue":{"types":"./dist/vue.d.ts","default":"./dist/vue.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"./svelte":{"types":"./dist/svelte.d.ts","svelte":"./dist/svelte.js","default":"./dist/svelte.js"},"./adapters":{"types":"./dist/adapters.d.ts","default":"./dist/adapters.js"},"./react-native":{"types":"./dist/react-native.d.ts","default":"./dist/react-native.js"}},"gitHead":"d83a4c26e2ed93fe6b9fa83e8c63bc946e7cdeea","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown && bun scripts/build-svelte.ts","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit && tsc -p tsconfig.react-native.json --noEmit && tsc -p tsconfig.vue.json --noEmit && tsc -p tsconfig.svelte.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"adapters (aggregate — grows by design, see per-export gates)","path":"dist/adapters.js","limit":"4.8 KB","import":"*"},{"gzip":true,"name":"adapters › svgToIcon (per-export shakeability gate)","path":"dist/adapters.js","limit":"3.7 KB","import":"{ svgToIcon }"},{"gzip":true,"name":"adapters › maskTarget (per-export shakeability gate)","path":"dist/adapters.js","limit":"0.8 KB","import":"{ maskTarget }"},{"gzip":true,"name":"adapters › canvasTarget (per-export shakeability gate)","path":"dist/adapters.js","limit":"0.55 KB","import":"{ canvasTarget }"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"},{"gzip":true,"name":"core + dom + react-native (MorphIcon, react/rn/rnsvg external)","path":"dist/react-native.js","limit":"9 KB","ignore":["react","react-native","react-native-svg"],"import":"*"},{"gzip":true,"name":"core + dom + vue (MorphIcon, vue external)","path":"dist/vue.js","limit":"8.5 KB","ignore":["vue"],"import":"*"},{"gzip":true,"name":"core + dom + svelte TS half (svelte external)","path":"dist/svelte-shared.js","limit":"8 KB","ignore":["svelte"],"import":"*"},{"gzip":true,"name":"svelte component (ships as source, consumer-compiled)","path":"dist/MorphIcon.svelte","limit":"1.4 KB","disablePlugins":["@size-limit/esbuild"]}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.40","react":"^19.2.8","lucide":"^1.28.0","svelte":"^5.56.8","tsdown":"^0.22.14","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","react-native":"^0.86.2","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","react-native-svg":"^15.15.5","@size-limit/preset-small-lib":"^13.0.3","@happy-dom/global-registrator":"^20.11.1"},"peerDependencies":{"vue":">=3.3","react":">=18","svelte":">=5","react-native":">=0.71","react-native-svg":">=14"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true},"svelte":{"optional":true},"react-native":{"optional":true},"react-native-svg":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.6.0_1786189358953_0.6404085523948841","host":"s3://npm-registry-packages-npm-production"}},"1.7.0":{"name":"morphicons","version":"1.7.0","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","react-native","lucide-react-native","vue","lucide-vue-next","svelte","lucide-svelte","astro","astro-component","web-component","custom-element","icon-animation"],"author":{"url":"https://guillermolg.com","name":"Guillermo"},"license":"MIT","_id":"morphicons@1.7.0","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"homepage":"https://www.morphicons.com","bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"dist":{"shasum":"cbd99e3f8d1f3e301929b50c3bf2a1f01133c060","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.7.0.tgz","fileCount":30,"integrity":"sha512-MOqSK+O5RdxynER5016vUvvqQaHkqqWYmNpocsk9TEszUZ9PB/K52Yqq8AGTRK1NUO5dj8znGSEVf9slqEIQaw==","signatures":[{"sig":"MEQCIHUwN5/cxyLTKvYW9/Z1RAt1GmdS+Y3usNalOpeNctkIAiBAogLnje+Aci3ysRnDBXB/6DsC0CrTrbcz1vNbQ+ZPlg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.7.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":156271},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./vue":{"types":"./dist/vue.d.ts","default":"./dist/vue.js"},"./astro":{"types":"./dist/astro.d.ts","default":"./dist/MorphIcon.astro"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"./svelte":{"types":"./dist/svelte.d.ts","svelte":"./dist/svelte.js","default":"./dist/svelte.js"},"./element":{"types":"./dist/element.d.ts","default":"./dist/element.js"},"./adapters":{"types":"./dist/adapters.d.ts","default":"./dist/adapters.js"},"./react-native":{"types":"./dist/react-native.d.ts","default":"./dist/react-native.js"}},"gitHead":"67332f01b3583051b7e374b180874d3ec929fed4","scripts":{"lint":"biome check .","play":"bun ./playground/index.html","size":"size-limit","test":"bun test","build":"tsdown && bun scripts/build-svelte.ts && bun scripts/build-astro.ts","format":"biome check --write .","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit && tsc -p tsconfig.react-native.json --noEmit && tsc -p tsconfig.vue.json --noEmit && tsc -p tsconfig.svelte.json --noEmit && tsc -p tsconfig.element.json --noEmit","prepublishOnly":"bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"repository":{"url":"git+https://github.com/guillermolg00/morphicons.git","type":"git"},"size-limit":[{"gzip":true,"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","limit":"7 KB","import":"*"},{"gzip":true,"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","limit":"7.5 KB","import":"*"},{"gzip":true,"name":"adapters (aggregate — grows by design, see per-export gates)","path":"dist/adapters.js","limit":"4.8 KB","import":"*"},{"gzip":true,"name":"adapters › svgToIcon (per-export shakeability gate)","path":"dist/adapters.js","limit":"3.7 KB","import":"{ svgToIcon }"},{"gzip":true,"name":"adapters › maskTarget (per-export shakeability gate)","path":"dist/adapters.js","limit":"0.8 KB","import":"{ maskTarget }"},{"gzip":true,"name":"adapters › canvasTarget (per-export shakeability gate)","path":"dist/adapters.js","limit":"0.55 KB","import":"{ canvasTarget }"},{"gzip":true,"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","limit":"8.5 KB","ignore":["react"],"import":"*"},{"gzip":true,"name":"core + dom + react-native (MorphIcon, react/rn/rnsvg external)","path":"dist/react-native.js","limit":"9 KB","ignore":["react","react-native","react-native-svg"],"import":"*"},{"gzip":true,"name":"core + dom + vue (MorphIcon, vue external)","path":"dist/vue.js","limit":"8.5 KB","ignore":["vue"],"import":"*"},{"gzip":true,"name":"core + dom + svelte TS half (svelte external)","path":"dist/svelte-shared.js","limit":"8 KB","ignore":["svelte"],"import":"*"},{"gzip":true,"name":"svelte component (ships as source, consumer-compiled)","path":"dist/MorphIcon.svelte","limit":"1.4 KB","disablePlugins":["@size-limit/esbuild"]},{"gzip":true,"name":"core + dom + element (<morph-icon> custom element)","path":"dist/element.js","limit":"9.5 KB","import":"*"},{"gzip":true,"name":"astro shell (ships as source, consumer-compiled)","path":"dist/MorphIcon.astro","limit":"1.75 KB","disablePlugins":["@size-limit/esbuild"]}],"_npmVersion":"12.0.2","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.40","react":"^19.2.8","lucide":"^1.28.0","svelte":"^5.56.8","tsdown":"^0.22.14","happy-dom":"^20.11.1","react-dom":"^19.2.8","@types/bun":"^1.3.14","size-limit":"^13.0.3","typescript":"^7.0.2","@types/react":"^19.2.18","react-native":"^0.86.2","@tabler/icons":"^3.46.0","feather-icons":"^4.29.2","@biomejs/biome":"^2.5.6","@types/react-dom":"^19.2.4","react-native-svg":"^15.15.5","@size-limit/preset-small-lib":"^13.0.3","@happy-dom/global-registrator":"^20.11.1"},"peerDependencies":{"vue":">=3.3","react":">=18","svelte":">=5","react-native":">=0.71","react-native-svg":">=14"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true},"svelte":{"optional":true},"react-native":{"optional":true},"react-native-svg":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/morphicons_1.7.0_1786616792409_0.41548448785314096","host":"s3://npm-registry-packages-npm-production"}},"1.7.1":{"name":"morphicons","version":"1.7.1","description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","license":"MIT","author":{"name":"Guillermo","url":"https://guillermolg.com"},"homepage":"https://www.morphicons.com","repository":{"type":"git","url":"git+https://github.com/guillermolg00/morphicons.git"},"bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","react-native","lucide-react-native","vue","lucide-vue-next","svelte","lucide-svelte","astro","astro-component","web-component","custom-element","icon-animation"],"type":"module","sideEffects":false,"publishConfig":{"access":"public"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dom":{"types":"./dist/dom.d.ts","default":"./dist/dom.js"},"./adapters":{"types":"./dist/adapters.d.ts","default":"./dist/adapters.js"},"./react":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"./react-native":{"types":"./dist/react-native.d.ts","default":"./dist/react-native.js"},"./vue":{"types":"./dist/vue.d.ts","default":"./dist/vue.js"},"./svelte":{"types":"./dist/svelte.d.ts","svelte":"./dist/svelte.js","default":"./dist/svelte.js"},"./element":{"types":"./dist/element.d.ts","default":"./dist/element.js"},"./astro":{"types":"./dist/astro.d.ts","default":"./dist/MorphIcon.astro"}},"peerDependencies":{"react":">=18","react-native":">=0.71","react-native-svg":">=14","svelte":">=5","vue":">=3.3"},"peerDependenciesMeta":{"react":{"optional":true},"react-native":{"optional":true},"react-native-svg":{"optional":true},"svelte":{"optional":true},"vue":{"optional":true}},"scripts":{"test":"bun test","typecheck":"tsc --noEmit && tsc -p playground --noEmit && tsc -p tsconfig.react.json --noEmit && tsc -p tsconfig.react-native.json --noEmit && tsc -p tsconfig.vue.json --noEmit && tsc -p tsconfig.svelte.json --noEmit && tsc -p tsconfig.element.json --noEmit","play":"bun ./playground/index.html","lint":"biome check .","format":"biome check --write .","build":"tsdown && bun scripts/build-svelte.ts && bun scripts/build-astro.ts","size":"size-limit","prepublishOnly":"bun run build"},"size-limit":[{"name":"core (gzip — anti-regression tripwire, see README)","path":"dist/index.js","import":"*","limit":"7 KB","gzip":true},{"name":"core + dom (createMorph + scheduler)","path":"dist/dom.js","import":"*","limit":"7.5 KB","gzip":true},{"name":"adapters (aggregate — grows by design, see per-export gates)","path":"dist/adapters.js","import":"*","limit":"4.8 KB","gzip":true},{"name":"adapters › svgToIcon (per-export shakeability gate)","path":"dist/adapters.js","import":"{ svgToIcon }","limit":"3.7 KB","gzip":true},{"name":"adapters › maskTarget (per-export shakeability gate)","path":"dist/adapters.js","import":"{ maskTarget }","limit":"0.8 KB","gzip":true},{"name":"adapters › canvasTarget (per-export shakeability gate)","path":"dist/adapters.js","import":"{ canvasTarget }","limit":"0.55 KB","gzip":true},{"name":"core + dom + react (MorphIcon, react external)","path":"dist/react.js","import":"*","ignore":["react"],"limit":"8.5 KB","gzip":true},{"name":"core + dom + react-native (MorphIcon, react/rn/rnsvg external)","path":"dist/react-native.js","import":"*","ignore":["react","react-native","react-native-svg"],"limit":"9 KB","gzip":true},{"name":"core + dom + vue (MorphIcon, vue external)","path":"dist/vue.js","import":"*","ignore":["vue"],"limit":"8.5 KB","gzip":true},{"name":"core + dom + svelte TS half (svelte external)","path":"dist/svelte-shared.js","import":"*","ignore":["svelte"],"limit":"8 KB","gzip":true},{"name":"svelte component (ships as source, consumer-compiled)","path":"dist/MorphIcon.svelte","disablePlugins":["@size-limit/esbuild"],"limit":"1.4 KB","gzip":true},{"name":"core + dom + element (<morph-icon> custom element)","path":"dist/element.js","import":"*","limit":"9.5 KB","gzip":true},{"name":"astro shell (ships as source, consumer-compiled)","path":"dist/MorphIcon.astro","disablePlugins":["@size-limit/esbuild"],"limit":"1.75 KB","gzip":true}],"devDependencies":{"@biomejs/biome":"^2.5.6","@happy-dom/global-registrator":"^20.11.1","@size-limit/preset-small-lib":"^13.0.3","@tabler/icons":"^3.46.0","@types/bun":"^1.3.14","@types/react":"^19.2.18","@types/react-dom":"^19.2.4","feather-icons":"^4.29.2","happy-dom":"^20.11.1","lucide":"^1.28.0","react":"^19.2.8","react-dom":"^19.2.8","react-native":"^0.86.2","react-native-svg":"^15.15.5","size-limit":"^13.0.3","svelte":"^5.56.8","tsdown":"^0.22.14","typescript":"^7.0.2","vue":"^3.5.40"},"gitHead":"38d2a7221633a453eeafebd872ee3649b9274b22","_id":"morphicons@1.7.1","_nodeVersion":"24.19.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-q5ylxy5/d7vBg0OAzanlooXf05PekovMYDuuQVpr6vAQZxl99lrJbaIi+jJ32PXQf9WEEaDO2pbNBsx1ZhEnFQ==","shasum":"2384e48e410ba2954c3bbde69d6b0804a8127d2a","tarball":"https://registry.npmjs.org/morphicons/-/morphicons-1.7.1.tgz","fileCount":30,"unpackedSize":156974,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/morphicons@1.7.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBD/bjzdacgXQ5/lqPKBrOmpLxK54OpbyOI+AwQKu85ZAiArz4ffIHWXzPbmOgan1+B2dAC3nwWzIlBSUqig9oZ7ig=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:15170658-88bd-40f2-8b9f-676e76701b67"}},"directories":{},"maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/morphicons_1.7.1_1787945992691_0.8334236560471189"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-01T09:06:16.420Z","modified":"2026-08-28T19:39:53.186Z","0.1.0":"2026-08-01T09:06:16.731Z","0.1.1":"2026-08-01T09:09:12.552Z","1.0.0":"2026-08-01T09:51:01.267Z","1.1.0":"2026-08-01T12:10:52.370Z","1.1.1":"2026-08-01T18:04:08.140Z","1.2.0":"2026-08-03T08:22:39.791Z","1.4.0":"2026-08-04T12:42:23.843Z","1.4.1":"2026-08-04T19:21:56.743Z","1.4.2":"2026-08-06T17:34:25.992Z","1.5.0":"2026-08-07T11:35:00.094Z","1.6.0":"2026-08-08T11:42:39.093Z","1.7.0":"2026-08-13T10:26:32.612Z","1.7.1":"2026-08-28T19:39:52.895Z"},"bugs":{"url":"https://github.com/guillermolg00/morphicons/issues"},"author":{"name":"Guillermo","url":"https://guillermolg.com"},"license":"MIT","homepage":"https://www.morphicons.com","keywords":["icons","svg","morphing","morph","morph-animation","animation","animated-icons","icon-morphing","svg-morph","spring","lucide","lucide-react","tabler","heroicons","iconoir","shadcn","feather","react","react-native","lucide-react-native","vue","lucide-vue-next","svelte","lucide-svelte","astro","astro-component","web-component","custom-element","icon-animation"],"repository":{"type":"git","url":"git+https://github.com/guillermolg00/morphicons.git"},"description":"Universal morphing for stroke-based icons with spring physics — zero runtime dependencies","maintainers":[{"name":"guillermolg00","email":"guillermolopezg00@gmail.com"}],"readme":"<p align=\"center\">\n  <a href=\"https://www.morphicons.com\">\n    <picture>\n      <source media=\"(prefers-color-scheme: dark)\" srcset=\"assets/logo-dark.svg\">\n      <img src=\"assets/logo-light.svg\" alt=\"morphicons\" width=\"96\">\n    </picture>\n  </a>\n</p>\n\n<h1 align=\"center\">morphicons</h1>\n\n<p align=\"center\">\n  <a href=\"https://www.morphicons.com\"><strong>morphicons.com</strong></a> — live playground\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/sponsors/guillermolg00\">\n    <img src=\"https://img.shields.io/badge/%E2%98%95%20Buy%20me%20a%20coffee-EA4AAA\" alt=\"Buy me a coffee — sponsor morphicons on GitHub Sponsors\">\n  </a>\n</p>\n\nUniversal morphing for stroke-based icons (Lucide, Tab\nler, Heroicons, Iconoir, or your own paths): **any icon morphs into any other** with spring physics. Rotations are never declared by hand — they emerge from the math (2D Procrustes + polar interpolation). Zero runtime dependencies.\n\n```tsx\nimport { MorphIcon } from \"morphicons/react\";\nimport { Menu, X } from \"lucide\"; // data, not components\n\n<button onClick={() => setOpen(o => !o)} aria-expanded={open}>\n  <MorphIcon icon={open ? X : Menu} />\n</button>\n```\n\nThat's the whole thing. No wrappers, no `AnimatePresence`, no keys, no from/to pairs, no configuration. State lives outside; the animation is an implementation detail the component picks up when the prop changes.\n\n## Why\n\nThe usual icon morphs either interpolate raw coordinates (shapes shrink and shear in transit) or require hand-declaring \"rotation groups\" per icon pair. morphicons solves the optimal similarity between shapes in closed form and interpolates it in its natural space: if a pair is congruent under rotation, it **rotates**; if not, it morphs in the aligned frame. arrow-right → arrow-down yields θ = 90° on its own, without anyone declaring it.\n\n## Install\n\n```bash\nbun add morphicons        # or npm install / pnpm add\n```\n\nESM only. `react` (>= 18), `vue` (>= 3.3), `svelte` (>= 5) and `react-native` (>= 0.71) + `react-native-svg` (>= 14) are optional peers — only needed for `morphicons/react`, `morphicons/vue`, `morphicons/svelte` and `morphicons/react-native`. `morphicons/element` (the `<morph-icon>` custom element) and `morphicons/astro` (its SSR shell) need no peer at all — Astro compiles the shell itself, like every `.astro` component shipped as source.\n\n**Icons come from a data package, not a component package.** morphicons consumes icon *data* (an `IconNode` or a raw `d` string). For Lucide that means the vanilla `lucide` package: `import { Menu, X } from \"lucide\"` gives you `IconNode`s, which is why every snippet below says \"data, not components\". The framework packages (`lucide-react`, `lucide-vue-next`, `@lucide/svelte`, `lucide-react-native`) export components, and `MorphIcon` can't consume those. If your app already renders static icons with one of them, keep it: the data and component packages coexist by design and both tree-shake cleanly, so you only pay for the icons you import. Just keep their versions aligned, so the icons you morph match the ones you render statically.\n\n## Usage\n\n### React — three modes\n\n```tsx\nimport { MorphIcon, type MorphHandle } from \"morphicons/react\";\n\n// 1. Uncontrolled (90% of uses): change the prop and morphicons animates\n<MorphIcon icon={open ? X : Menu} spring=\"snappy\" />\n\n// 2. Controlled (gestures, scroll): explicit progress, no spring\n<MorphIcon from={Menu} to={X} progress={dragProgress} />\n\n// 3. Imperative (sequences)\nconst ref = useRef<MorphHandle>(null);\n<MorphIcon ref={ref} icon={Menu} />\nref.current?.morphTo(Check); // animates\nref.current?.set(X);         // jumps without animating\n```\n\nDrop-in replacement for lucide-react (the props surface: the `icon` prop takes Lucide *data*, not lucide-react components, see [Install](#install)): `size`, `strokeWidth`, `absoluteStrokeWidth`, `color`, `className` and the rest of the `<svg>` props pass straight through. Correct accessibility by default: `aria-hidden` unless you pass `label` (→ `role=\"img\"` + `<title>`). Clean SSR: the server emits the exact static SVG (zero flash, zero layout shift); the runtime is born on hydration. Morphs play regardless of the OS reduce-motion setting by default; opt into honoring it with `reducedMotion=\"user\"` (see [Reduced motion](#reduced-motion-all-five-bindings)).\n\n### Vue — same three modes\n\n```vue\n<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { MorphIcon, type MorphHandle } from \"morphicons/vue\";\nimport { Menu, X, Check } from \"lucide\"; // data, not components\n\nconst open = ref(false);\nconst morph = ref<MorphHandle | null>(null);\n// morph.value?.morphTo(Check)  → animates\n// morph.value?.set(X)          → jumps without animating\n</script>\n\n<template>\n  <!-- 1. Uncontrolled (90% of uses): change the prop and morphicons animates -->\n  <MorphIcon :icon=\"open ? X : Menu\" spring=\"snappy\" />\n\n  <!-- 2. Controlled (gestures, scroll): explicit progress, no spring -->\n  <MorphIcon :from=\"Menu\" :to=\"X\" :progress=\"dragProgress\" />\n\n  <!-- 3. Imperative (sequences): template ref → morphTo / set -->\n  <MorphIcon ref=\"morph\" :icon=\"Menu\" />\n</template>\n```\n\nSame surface as the React binding: presentation props (`size`, `strokeWidth`, `absoluteStrokeWidth`, `color`; `class`, `style` and the rest of the `<svg>` attrs fall through), the same accessibility defaults (`aria-hidden` unless you pass `label`) and the same clean SSR — works with Nuxt out of the box: the server emits the exact static SVG and the runtime is born on hydration. The binding is a plain render function; no SFC compiler or JSX involved.\n\n### Svelte — same three modes\n\n```svelte\n<script lang=\"ts\">\n  import { MorphIcon, type MorphHandle } from \"morphicons/svelte\";\n  import { Menu, X, Check } from \"lucide\"; // data, not components\n\n  let open = $state(false);\n  let morph = $state<MorphHandle>();\n  // morph?.morphTo(Check)  → animates\n  // morph?.set(X)          → jumps without animating\n</script>\n\n<!-- 1. Uncontrolled (90% of uses): change the prop and morphicons animates -->\n<MorphIcon icon={open ? X : Menu} spring=\"snappy\" />\n\n<!-- 2. Controlled (gestures, scroll): explicit progress, no spring -->\n<MorphIcon from={Menu} to={X} progress={dragProgress} />\n\n<!-- 3. Imperative (sequences): bind:this → morphTo / set -->\n<MorphIcon bind:this={morph} icon={Menu} />\n```\n\nSame surface again: presentation props, `class`/`style`/rest-attr fall-through — fully typed via `svelte/elements`, so SVG attrs, events and ARIA autocomplete and typos fail the build, like lucide-svelte — the same accessibility defaults and the same clean SSR — works with SvelteKit out of the box. Svelte 5 (runes); the component ships as `.svelte` source and your bundler compiles it via the `svelte` export condition, like every Svelte library. If you followed Lucide's official Svelte guide you already have `@lucide/svelte`: keep it for your static icons; `MorphIcon` takes the data exports from the vanilla `lucide` package (see [Install](#install)).\n\n### React Native — same three modes\n\n```tsx\nimport { MorphIcon, type MorphHandle } from \"morphicons/react-native\";\nimport { Menu, X, Check } from \"lucide\"; // data, not components\n\n// 1. Uncontrolled (90% of uses): change the prop and morphicons animates\n<MorphIcon icon={open ? X : Menu} spring=\"snappy\" />\n\n// 2. Controlled (gestures, scroll): explicit progress, no spring\n<MorphIcon from={Menu} to={X} progress={dragProgress} />\n\n// 3. Imperative (sequences)\nconst ref = useRef<MorphHandle>(null);\n<MorphIcon ref={ref} icon={Menu} />\nref.current?.morphTo(Check); // animates\nref.current?.set(X);         // jumps without animating\n```\n\nThe DOM-free core beyond the browser: the dom driver is reused verbatim as the engine (React Native has a global `requestAnimationFrame`, and `PathEl` is structural), so the whole platform difference is a shim that forwards the per-frame `d` write to `Path.setNativeProps` of react-native-svg — outside the React render, exactly like the web mutation. On the New Architecture that write is not the last word, because every Fabric commit rebuilds the native path from the props React declares: the binding therefore publishes the driver's live `d` on each render, and re-applies it in a layout effect when a frame landed in between, so an unrelated re-render (a color or selection change, at rest or mid-flight) can no longer restore the icon the component mounted with. Same surface as the React binding (`size`, `strokeWidth`, `absoluteStrokeWidth`, `color`, plus the native `Svg` props: `testID`, touch handlers…), same accessibility defaults (`aria-hidden` unless you pass `label` → `role=\"img\"` + `aria-label`). With `reducedMotion=\"user\"`, the OS setting comes from `AccessibilityInfo` (best-effort: the query is async; a `reduceMotionChanged` subscription keeps it exact from then on). Requires Metro with package `exports` resolution — default since React Native 0.79; on older versions enable `unstable_enablePackageExports`.\n\n### Astro — same three modes, zero framework runtime\n\n```astro\n---\nimport MorphIcon from \"morphicons/astro\";\nimport { Menu } from \"lucide\"; // data, not components\n---\n\n<MorphIcon icon={Menu} label=\"Menu\" id=\"menu-icon\" />\n\n<script>\n  import { X } from \"lucide\";\n  import type { MorphIconElement } from \"morphicons/element\";\n  // Post-load control happens ON the element (it upgrades to <morph-icon>):\n  const el = document.querySelector<MorphIconElement>(\"#menu-icon\")!;\n  el.morphTo(X);             // 3. imperative: morphTo / set, like a ref\n  // el.icon = X;            // 1. uncontrolled: assigning the property animates\n  // el.from = Menu; el.to = X; el.progress = 0.5;  // 2. controlled: frozen seek\n</script>\n```\n\nThe server emits the exact static SVG with the pure core (zero flash, zero layout shift, works with static output and any SSR adapter) and hydration is just custom-element upgrade: the shell defines `<morph-icon>` once per page and the element adopts the server markup verbatim — zero `d` writes during upgrade, pinned by instrumented tests. No framework runtime ships — the only client bytes are `morphicons/element`. A controlled pair of `d` strings survives upgrade as attributes, so assigning `progress` from a script scrubs the server-frozen pair; `IconNode` pairs are server-only (attributes can't carry them) — control those by assigning the element's properties. Same presentation props as every binding (`size`, `strokeWidth`, `absoluteStrokeWidth`, `color`, `label`); rest attrs (`id`, `class`, `data-*`…) land on the `<morph-icon>` element, because grabbing it from a client script is the interaction model here. React/Vue/Svelte islands keep working for icons that live inside one — this entry is for the pages that don't need an island at all.\n\n### Web component — `<morph-icon>` anywhere HTML reaches\n\nThe Astro shell is a thin SSR layer over `morphicons/element`, which stands on its own in plain HTML, HTMX, Rails, or any server-rendered stack:\n\n```html\n<script type=\"module\">\n  import { defineMorphIcon } from \"morphicons/element\";\n  defineMorphIcon(); // idempotent; custom tag: defineMorphIcon(\"my-icon\")\n</script>\n\n<!-- attributes take d strings; properties take the full IconInput -->\n<morph-icon icon=\"M4 6h16M4 12h16M4 18h16\" label=\"Menu\"></morph-icon>\n```\n\nThe element is the fifth binding, not a second implementation: it wires attributes and properties to the same shared controller, passes the same mirrored mount suite, and exposes the same three modes — `icon` (uncontrolled), `from`/`to`/`progress` (controlled), `morphTo`/`set` methods (imperative, the element IS the handle). Attributes carry strings (`d` paths, preset names); properties accept everything (`IconNode`s, custom springs). If the element already contains a server-rendered `<svg><path>`, it adopts it verbatim and treats its `d` as the at-rest icon — SSR bytes are never rewritten at rest.\n\n### Lifecycle contract (all five bindings)\n\nThe five components share one contract, pinned by mirrored client-mount tests:\n\n- **Lazy driver.** Mounting without any icon is fine — SSR emits `<path d=\"\">` and the driver is born with the FIRST icon that shows up, whether a late `icon` prop (data that loads async), a late `from`/`to` pair, or an imperative `set`/`morphTo`. The first icon paints without animating; `morphTo` before the driver exists behaves as `set` (there is nothing to fly from).\n- **Controlled wins.** While `from` AND `to` are both present, the pair owns the path: `icon` changes are ignored, no spring fires. Drop the pair and the current `icon` takes over (animated). Mixing the modes is not an error — the precedence is just explicit.\n- **Clean re-entry.** Any exit from controlled mode (an imperative call or an icon takeover) invalidates the frozen pair, so returning to the same `from`/`to` re-bases on `from` and renders exactly like a clean mount at that `progress`.\n\n### Reduced motion (all five bindings)\n\nIcon morphs are small, short, communicative micro-transitions: the kind of motion the reduce-motion guidance considers generally acceptable, unlike parallax or full-screen movement. Auto-degrading them made the library look broken to every user with the OS setting on, so since 1.4.2 they play by default and the policy is an explicit prop (the same default Motion, formerly Framer Motion, ships):\n\n```tsx\n<MorphIcon icon={open ? X : Menu} reducedMotion=\"user\" />\n```\n\n- `\"never\"` (default): morphs always animate; the OS setting is ignored.\n- `\"user\"`: while the OS reduce-motion setting is on, every `morphTo` degrades to an instant swap (the pre-1.4.2 behavior). The web bindings read `prefers-reduced-motion`; React Native reads `AccessibilityInfo`, and only touches the accessibility bridge when some instance opts in.\n- `\"always\"`: every `morphTo` jumps without animating (tests, screenshots).\n\nThe prop is live: changing it governs the next morph. The vanilla driver takes the same policy as an option, `createMorph(el, icon, { reducedMotion: \"user\" })`, and exposes it as a mutable `morph.reducedMotion`. Apps that must strictly follow the user's system preference should pass `reducedMotion=\"user\"` across the board.\n\n### Vanilla (no React)\n\n```ts\nimport { createMorph } from \"morphicons/dom\";\n\nconst m = createMorph(pathEl, Menu); // pathEl: any object with setAttribute\nm.morphTo(X, \"snappy\");              // preset or { stiffness, damping }\nm.set(Check);                        // jump without animating (canonical d)\nm.seek(X, 0.4);                      // morph frozen at t — scrubbing, gestures\nm.progress = 0.4;                    // sugar: seek on the active target\nm.reducedMotion = \"user\";            // live policy: \"never\" (default) | \"user\" | \"always\"\nm.destroy();\n```\n\nTruly interruptible: a `morphTo` mid-flight re-plans from the current intermediate shape while preserving the spring's velocity — click spam never jumps. A single global `requestAnimationFrame` drives all instances.\n\n### Pure core (no DOM)\n\n```ts\nimport { resampleIcon, buildPlan, interpPolar, allocOutputs, serialize } from \"morphicons\";\n\nconst plan = buildPlan(resampleIcon(Menu), resampleIcon(X)); // cacheable\nconst out = allocOutputs(plan);\ninterpPolar(plan, 0.5, out);                                 // t ∈ [0, 1]\nconst d = serialize(out, plan.items.map(it => it.closed));   // `d` attribute\n```\n\nAccepts `IconNode` (Lucide's data format, structurally typed — Lucide is neither a dependency nor a peer) or a raw `d` attribute. Tabler, Heroicons, Iconoir and custom paths work the same.\n\n## Format adapters — `morphicons/adapters`\n\nThe core has exactly two structural contracts: `IconInput` on the way in (an `IconNode` or a `d` string) and `PathEl` on the way out (anything with `setAttribute` — the same contract that lets the React Native binding drive `setNativeProps`). A **format adapter** converts a foreign icon representation into one of those two contracts. All of them live in one opt-in entry, so the unit of payment is the import: core, driver and bindings never grow for a capability you didn't ask for, and inside the entry each adapter is an independent module — a named import tree-shakes to just that adapter, pinned by per-export size gates in CI. Adapters are per *format*, never per vendor: there is no `/iconify` and never will be.\n\n### `svgToIcon` — SVG markup in\n\nFor icons that exist as markup rather than data: an Iconify body from `@iconify-json/*` (what UnoCSS `presetIcons` and the Tailwind icon plugins consume), a full `<svg>` copied from a website, a `<path>` from the shadcn registry. One call turns it into an `IconInput` any entry accepts:\n\n```ts\nimport { svgToIcon } from \"morphicons/adapters\";\n\n// Parse ONCE, at module scope — reuse the reference so the plan cache holds.\nconst MENU = svgToIcon(menuBody); // '<path stroke=\"currentColor\" d=\"M4 5h16…\"/>'\nconst X = svgToIcon(xBody);       // works in createMorph and all four bindings\n```\n\nEverything else happens under the hood: non-rendered containers (`<defs>`, `<mask>`, `<clipPath>`, `<symbol>`) are stripped, the morphability scan rejects what can't honestly morph (a fill-drawn icon with no stroke — Material Symbols style — a `transform`, an element outside the seven stroke primitives) with a clear error at parse time, and when the markup carries a `viewBox` the geometry is re-gridded onto 24 via [`fitIcon`](#icon-library-compatibility) automatically, so off-grid collections just work.\n\n### `maskTarget` — CSS-mask elements out\n\nFor apps that render icons as CSS masks — UnoCSS `presetIcons`, `@iconify/tailwind`, `@egoist/tailwindcss-icons` (`i-lucide-*`, `icon-[lucide-x]`): the icon is a `mask-image` data URI on a `<span>`/`<div>`, with **no `<path>` in the DOM** to write to. `maskTarget` creates a hidden pair of referenced `<svg><mask><path>` buffers once, points the element's mask at them, and hands the driver a `PathEl` whose `d` writes land on that real geometry — so the element morphs in place, keeping your `size-*`/`text-*` classes. Two engine traps shaped this mechanism (see `docs/adr/0002`): a data-URI mask swapped per frame never decodes in time in Chromium (blank flight), and WebKit doesn't repaint when a referenced mask's *content* mutates (frozen flight) — hence the pair: each write flips `mask-image` between the two buffers, which forces re-resolution everywhere.\n\n```ts\nimport { maskTarget } from \"morphicons/adapters\";\nimport { createMorph } from \"morphicons/dom\";\n\nconst target = maskTarget(spanEl);     // spanEl: class=\"size-5 text-current\"\nconst m = createMorph(target, MENU);\nm.morphTo(X, \"snappy\");                // full Morph handle: set/seek/progress/destroy\n// on unmount: m.destroy(); target.dispose();\n```\n\nIt *is* `createMorph` — only the write target differs — so springs, reduced motion and mid-flight interruption behave identically. On setup it sets `background-color: currentColor`, so a bare `<span>` works with no icon class (`{ paint: false }` keeps an existing `bg-*`; `strokeWidth` and `viewBox` are options). The returned target adds `dispose()` — call it after `morph.destroy()` when the element unmounts, so the hidden mask nodes go with it.\n\n**Cost.** A live inline `<path>` gets the cheapest possible write; a mask element additionally makes the browser re-rasterize the mask and repaint the masked box every frame (main thread, no compositor-only path). Great for toggles and short lists (menu↔close, chevrons, play↔pause); for icon-heavy views, the inline-SVG bindings remain the leanest option — same styling, one less indirection.\n\n### `canvasTarget` — 2D canvas out\n\nFor surfaces where the DOM never enters: **the icon stops being a document node and becomes pixels you own.** The platform does the heavy lifting — `Path2D` parses `d` strings natively — so every frame is one `stroke(new Path2D(d))` on whatever you hand it: an `HTMLCanvasElement`, an `OffscreenCanvas`, or a bare 2D context (what a worker, or a host that already owns the context, has in hand).\n\n```ts\nimport { canvasTarget } from \"morphicons/adapters\";\nimport { createMorph } from \"morphicons/dom\";\n\nconst m = createMorph(canvasTarget(canvas), MENU);\nm.morphTo(X, \"snappy\");\n// the canvas is a live texture: gl.texImage2D(gl.TEXTURE_2D, ..., canvas)\n```\n\nOnce the icon is pixels, everything the SVG model can't reach opens up:\n\n- **Canvas-first apps** with no DOM to put an icon in: Konva, Fabric.js, PixiJS, games, chart draws, tldraw-style editors.\n- **`OffscreenCanvas` + workers**: the whole morph runs off the main thread.\n- **Native export**: `toBlob()` for PNG sprites, `captureStream()` + MediaRecorder for the morph as a video asset.\n- **An animated favicon**: draw, `toDataURL()`, swap the `<link>`.\n- **HUDs over video or fullscreen surfaces**: same buffer as the player or the game, no DOM layer on top.\n- **WebGL textures**: the canvas is a ready `texImage2D` source — shader effects over live icons are one branch of this list, not its headline.\n- Down the road, the same gesture (`PathEl` → `Path2D`) is `PathEl` → `SkPath`: a react-native-skia / CanvasKit target would consume the identical contract.\n\nIt *is* `createMorph` — springs, mid-flight interruption and reduced motion behave identically. The target maps the icon grid onto whatever backing store it finds, per write: min-side scale, centered (the canvas equivalent of `preserveAspectRatio=\"xMidYMid meet\"`), so non-square canvases letterbox and devicePixelRatio is simply whatever you sized the backing store to. Color cascades: the `color` option wins; without it, a CSS-styled canvas contributes its computed `color` (the moral equivalent of `currentColor`, which canvas lacks — read lazily, once); where CSS can't reach (OffscreenCanvas, workers) the context's current `strokeStyle` stands, which is the idiomatic canvas contract. `strokeWidth` and `viewBox` mirror the other adapters; `clear: false` skips the per-frame clear for manual compositing and trails.\n\n**Hosts with their own render loop.** Don't hand a Konva/Chart.js/game context to a morph — the driver writes on its own rAF and the two clocks fight over the frame. Give the icon a small dedicated canvas and composite it into the scene (`drawImage`, `Konva.Image`, a texture upload), with `onWrite` as the dirty signal:\n\n```ts\nconst icon = new OffscreenCanvas(64, 64);\nconst target = canvasTarget(icon, {\n  color: \"#e8eaed\",                      // CSS can't reach an OffscreenCanvas\n  onWrite: () => { textureDirty = true } // re-upload / blit on the host's next frame\n});\nconst m = createMorph(target, PLAY);\n```\n\n**Cost.** The cheapest target of the three: no layout, no DOM paint — one native path parse and one stroke rasterization per frame, on a canvas that is typically small. 0.48 KB gzip standalone, the lightest adapter yet.\n\n## Icon library compatibility\n\nmorphicons has no per-library adapters — any icon set that meets these requirements works out of the box:\n\n1. **Stroke-drawn icons.** The geometry must be the stroked centerline (`fill=\"none\"`, color via `stroke`). The whole pipeline — resampling, correspondence, in-flight polylines — assumes strokes; filled or outlined-fill glyphs (Material Symbols, Bootstrap Icons, Remix Icon, Phosphor, Heroicons *solid*) parse fine but won't read correctly in transit.\n2. **Geometry available as data.** Either a raw `d` attribute per icon, or a `[tag, attrs][]` node list (Lucide's IconNode shape) using only `path`, `line`, `circle`, `ellipse`, `rect`, `polyline`, `polygon`. No `<g>` wrappers and no `transform` attributes — coordinates must be literal. Any other tag throws a clear error.\n3. **A shared coordinate space per pair.** Both endpoints of a morph must live on the same grid. Lucide, Tabler, Heroicons and Iconoir all draw on 24×24 — that's why cross-library morphs just work. For a pack on another canvas (Heroicons *solid* on 20, Carbon on 32, Teenyicons on 15), re-grid it once with `fitIcon`:\n\n   ```ts\n   import { fitIcon } from \"morphicons\";\n\n   const search = fitIcon(carbonSearch, 32);   // 32×32 → the 24 grid\n   const bell   = fitIcon(teenySmallBell, 15); // also \"0 0 15 15\" or [0, 0, 15, 15]\n   ```\n\n   It returns a plain `d`, so it goes anywhere an icon is accepted. Call it at module scope, not per render. Skipping it doesn't throw — Procrustes is similarity-invariant, so the grid mismatch never reads as false rotation; it lands in σ as an unwanted zoom, and the target draws outside the canvas. The React, Vue and Svelte bindings default to `viewBox=\"0 0 24 24\"`, overridable via props.\n4. **Uniform stroke.** Cosmetic rather than structural: a consistent stroke width and round caps/joins make in-flight shapes look native. These live on the `<svg>`, not in morphicons.\n\nBeyond the libraries in the playground, sets known to qualify on the 24×24 grid — no fitting needed: **Heroicons** (outline style), **Iconoir** (1.5px stroke), **Akar Icons**, **Untitled UI** and **Hugeicons** (stroke style). Off-grid packs work through `fitIcon`: **Teenyicons** (15×15), **Carbon** (32×32), **Heroicons solid** (20×20).\n\nThis also covers the [shadcn registry](https://www.shadcn.io/icons), which re-publishes 200+ icon libraries as React components with an inline `<path d>` — pass that `d` straight in, adding `fitIcon` when the pack's `viewBox` isn't 24.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────┐\n│  bindings   react · vue · svelte · rn      │  thin: ref + createMorph\n│             · element (+ astro SSR shell)   │\n├─────────────────────────────────────────────┤\n│  drivers    dom (setAttribute + rAF)        │  singleton scheduler\n├─────────────────────────────────────────────┤\n│  core       parse → normalize → resample    │  pure functions,\n│             → match → align → plan          │  NO DOM, produce\n│             → interpolate → serialize       │  `d` strings and numbers\n│             + spring                        │\n└─────────────────────────────────────────────┘\n```\n\nThe hard rule: **the core never touches the DOM**. Pure functions consume icon data and produce `d` strings. Direct consequence: the React Native binding over `react-native-svg` is just another adapter, not a rewrite — it reuses the dom driver verbatim through a `setNativeProps` shim. One package with subpath exports (`.` core, `./dom`, `./react`, `./react-native`, `./vue`, `./svelte`, `./element`, `./astro`), ESM only, `sideEffects: false`.\n\nThe plan is the central artifact:\n\n```\nicon A ─┐\n        ├→ normalize → resample → match → align → PLAN ─→ interpolate(t) → serialize → d\nicon B ─┘                                           ↑\n                                        cacheable, serializable\n```\n\n`plan()` accepts any list of sampled subpaths — not just canonical icons — which is what makes interruptions free: the currently rendered buffers are a valid morph source.\n\n## How the math works\n\n### 1. Normalization — everything to cubic Béziers\n\nEvery SVG primitive is reduced to cubic segments:\n\n- **Line** → cubic with collinear controls: `C₁ = P₀ + ⅓(P₃−P₀)`, `C₂ = P₀ + ⅔(P₃−P₀)`.\n- **Quadratic** → exact degree elevation: `C₁ = Q₀ + ⅔(Q₁−Q₀)`, `C₂ = Q₂ + ⅔(Q₁−Q₂)`.\n- **Circle** → 4 cubics with offset `k = r·(4/3)·tan(π/8) ≈ 0.5523·r`.\n- **Arc `A`** → center parametrization (SVG spec, appendix F.6), sliced into arcs ≤ 90°, each slice to a cubic with `α = (4/3)·tan(Δθ/4)`.\n- **rect/polyline/polygon** → lines (rounded rects: lines + quarter ellipses).\n\nThe `d` parser handles absolute/relative commands, H/V/S/T shorthands, packed arc flags and scientific notation. Result: icon = list of subpaths; subpath = chain of cubics + closed/open flag.\n\n### 2. Resampling — arc length with anchored corners\n\nInstead of fighting Bézier structures of different cardinality, every subpath is sampled at **N points equidistant by arc length** (N = 64). Every subpath becomes a `Float64Array(2N)` and the structural problem disappears: correspondence is index to index.\n\nThe arc length of a cubic has no closed form; `|B′(t)|` is integrated with 8-point Gauss-Legendre quadrature per segment.\n\nThe refinement that drives quality: **corner detection** (tangent discontinuity above an angular threshold) with corners anchored as exact sample points. Remaining points are apportioned by arc length between corners — integer apportionment by largest remainder, minimum 1 interval per run, summing exactly to N−1. Effects:\n\n- At rest the shape is **exact** (a check's vertex is a sample, not an approximation).\n- In transit, source corners flatten smoothly and target corners sharpen — the desired behavior. Without anchoring, a morphing check visibly rounds its corners.\n\nClosed paths anchor **only corners**, not the arbitrary `M` start point — sampling is intrinsic to the shape, so two congruent loops with different start points produce the same sample set (modulo index rotation, resolved by the circular correspondence below).\n\n### 3. Correspondence\n\nThree subproblems:\n\n- **Orientation.** Both traversal directions of B are tried and scored (see the tie-break below). A hamburger line folding to the correct diagonal depends on this.\n- **Start point (closed paths).** A closed contour has a circular degree of freedom: which index is \"first\". The N circular offsets × 2 directions are tried and the best post-alignment score wins. O(N²) brute force with N = 64 is ~4K operations per pair — negligible.\n- **Subpath matching.** With p subpaths in A and q in B, pairing cost is `dist(centroids) + 0.35·|ΔL|` (arc lengths). Equal counts use the minimum-cost permutation (exhaustive with pruning up to 8 subpaths, greedy above); unequal counts use a **surjective** assignment from the large side to the small one, so no subpath ever appears or disappears out of nowhere. Leftovers are **not collapsed to a point**: the nearest subpath is **duplicated** in place and the copies separate in flight — it reads as cell division, far more natural than growing from nothing. At rest, overlapping duplicates with the same solid stroke are invisible, and the canonical snap on settle resets the count.\n\n### 4. Alignment — 2D Procrustes\n\nBefore interpolating, the **optimal similarity** (rotation θ, scale σ, translation) mapping A onto B is computed by minimizing `Σ|σ·R(θ)·(aᵢ−c_A) − (bᵢ−c_B)|²`. In 2D it has a closed form, no SVD:\n\n```\nS_xx = Σ aₓbₓ    S_xy = Σ aₓbᵧ    (centered clouds)\nS_yx = Σ aᵧbₓ    S_yy = Σ aᵧbᵧ\n\nθ* = atan2(S_xy − S_yx,  S_xx + S_yy)\nσ* = [cos θ*·(S_xx+S_yy) + sin θ*·(S_xy−S_yx)] / Σ‖aᵢ‖²\n```\n\nThe normalized RMS **residual** after alignment (`res = √(Σ|σRa−b|² / Σ‖b‖²)`) is the shape metric: ≈ 0 means A and B are the same shape rotated/scaled. This turns rotation groups into an **emergent property**: arrow-right → arrow-down gives residual ~0 with θ = 90° and the system chooses pure rotation on its own.\n\nProcrustes runs **per subpath** (each piece rotates around its own centroid, which translates independently) — this gives local, organic motion: a hamburger *folds* instead of spinning as a block. A **global hybrid** pass then runs Procrustes over the concatenated clouds: if the global residual is ≈ 0 (< 5e-3) the whole icon is congruent and every subpath shares the same (θ, σ), so e.g. both subpaths of an arrow spin the same way.\n\n#### Minimal-rotation tie-break\n\nFor shapes symmetric under inversion (a straight line), both traversal orientations give residual 0 — indistinguishable to Procrustes — but produce different rotations. Naive tie-breaking can pick θ = 135° when the inverted orientation gives θ = −45° with the same end result. Each orientation is scored with:\n\n```\nscore = res + λ·|θ|/π      (λ = 0.05)\n```\n\nDeformation is minimized first and, at comparable residuals, the shortest rotation wins. λ is small enough that a notably worse shape never wins just by rotating less. This is the kind of detail the math doesn't see but the eye does.\n\n### 5. Polar interpolation\n\nRaw coordinate lerp **collapses rotations**: points take the chord, the shape shrinks and shears in transit. The fix: decompose the motion into similarity + residual and interpolate each part in its natural space.\n\nLet `aᶜᵢ = aᵢ − c_A` and `b̃ᵢ = R(−θ*)·(bᵢ − c_B)/σ*` (B brought into A's frame). Then:\n\n```\nP(t) = c(t) + σ*ᵗ · R(t·θ*) · [(1−t)·aᶜᵢ + t·b̃ᵢ]\n\n  c(t)  = lerp(c_A, c_B, t)        linear translation (block transport under the global hybrid — below)\n  t·θ*  = linear angle             (θ* is already in (−π, π], short path)\n  σ*ᵗ   = exp(t·ln σ*)             log-linear scale (geodesic in ℝ⁺)\n```\n\nExact at both endpoints (max error ~3.6e-15 in tests). Properties:\n\n- If B is A rotated (residual 0), the bracket is constant and the motion is **pure rotation**.\n- If there is no rotation, it's a clean coordinate morph.\n- Mixed cases do both simultaneously and continuously.\n- With spring overshoot (t > 1) the formula **extrapolates** naturally: rotation and scale overshoot slightly and come back — free juice.\n\n**Block transport (global hybrid).** When the whole icon is congruent (§4), lerping each subpath centroid reintroduces the chord disease one level up: every part spins with the shared θ*, but an off-center part travels the chord — inside the arc — and drifts toward the center mid-flight. An arrow's head sagged ~1 px toward its shaft at t = 0.5 (`r·(1 − cos(θ/2))` with r = 3.5, θ = 90°) while both parts rotated perfectly. Under the hybrid, each centroid rides the shared similarity around the global centroid `g_A` instead:\n\n```\nc_k(t) = c_{A,k} + t·d_k + (σ*ᵗ·R(t·θ*) − I)·(c_{A,k} − g_A)\n```\n\n`d_k` is fixed once so that `c_k(1) = c_{B,k}` exactly (it absorbs the tiny global residual). Every point of every subpath then moves under ONE similarity per frame: congruent icons are rigid through the whole flight, not only at the endpoints. With θ* = 0, or centroids sitting on `g_A`, or no hybrid at all, the formula degrades continuously to the plain lerp — no thresholds, no special cases.\n\n### 6. Serialization and canonical snap\n\nIn flight, each subpath is emitted as `M x y L x y …` (+ `Z` if the pair is closed↔closed; closed→open flies open, the loop opening at the cut chosen by the circular correspondence) with 2 decimals — invisible at 24px, and the frame's only allocation. On settle, the driver **snaps to the canonical `d`** of the target icon: exact fidelity at rest (real curves, not polylines), subpath count reset after duplications, and the DOM ends up identical to a static icon's. The jump is < 0.02px — imperceptible. The canonical `d` itself is emitted with 4 decimals, and that is a correctness choice, not a size one: arc→cubic conversion goes through trig whose last ulp differs across JS engines, and SSR hydration compares the server's bytes against the browser's — quantized emission keeps them identical, where full precision leaked each engine's ulp into the markup (a Next hydration mismatch on icons with non-cardinal arcs).\n\n### 7. The spring\n\nDamped harmonic oscillator over progress `x: 0 → 1`:\n\n```\nẍ = k·(1 − x) − c·ẋ\n```\n\nIntegrated with **semi-implicit Euler** at h = 1/240 s substeps (stable up to ω·h ≈ 2; with k = 420, ω ≈ 20.5 — ample margin). ~25 lines, no animation library needed.\n\n| preset | k | c | ζ = c/(2√k) | character |\n|--------|-----|-----|------|-----------|\n| smooth | 170 | 26 | 1.00 | critically damped, no overshoot |\n| snappy | 420 | 30 | 0.73 | fast, subtle overshoot |\n| bouncy | 300 | 14 | 0.40 | playful |\n\n**Interruptions**: when a `morphTo` arrives mid-flight, the plan is rebuilt from the current intermediate shape (the rendered buffers are already N points per subpath — they serve directly as source), `x` resets to 0 and **velocity is preserved** (clamped to ±14). Perceived motion is continuous; tap spam feels alive, never jumps. The spring settles and the rAF stops when `|1−x| < 0.001 ∧ |v| < 0.02`.\n\n## Performance\n\n- `plan(A, B)` complete (resample + match + Procrustes × orientations): **sub-millisecond** with N = 64 — measured 0.01 ms (open paths) to 0.06 ms (closed, with circular search); 0.42 ms for a pathological 12-loops case.\n- **Zero numeric allocation per frame**: interpolators write into the plan's preallocated `Float64Array`s. Only the `d` string is allocated.\n- **Caches by reference**: `normalize(icon)` in a WeakMap keyed by the IconNode (Lucide imports are stable references → no string keys, GC-friendly). `plan(A, B)` in a nested WeakMap; plans from interruptions are never cached.\n- **One global rAF** (singleton scheduler): active instances are iterated, settled ones unregister. A hundred icons morphing at once = one loop.\n\n## Size\n\nCI budget (size-limit, gzip) as an anti-regression tripwire — gates carry ~10% headroom over what's measured; if a real capability needs more, the number is renegotiated, but growing unnoticed is not accepted:\n\n| entry | measured | gate |\n|---|---|---|\n| `morphicons` (core) | 6.60 KB | 7 KB |\n| `morphicons/dom` (core + driver) | 7.12 KB | 7.5 KB |\n| `morphicons/adapters` (aggregate — grows by design) | 4.32 KB | 4.8 KB |\n| `adapters` › `{ svgToIcon }` alone | 3.34 KB | 3.7 KB |\n| `adapters` › `{ maskTarget }` alone | 0.70 KB | 0.8 KB |\n| `adapters` › `{ canvasTarget }` alone | 0.48 KB | 0.55 KB |\n| `morphicons/react` (all, react external) | 8.01 KB | 8.5 KB |\n| `morphicons/react-native` (all, react/rn/rnsvg external) | 8.37 KB | 9 KB |\n| `morphicons/vue` (all, vue external) | 8.04 KB | 8.5 KB |\n| `morphicons/svelte` TS half (all, svelte external) | 7.66 KB | 8 KB |\n| `MorphIcon.svelte` (ships as source, consumer-compiled) | 1.26 KB | 1.4 KB |\n| `morphicons/element` (all, `<morph-icon>`) | 8.86 KB | 9.5 KB |\n| `MorphIcon.astro` (ships as source, consumer-compiled) | 1.58 KB | 1.75 KB |\n\nThe adapters entry carries **two kinds of gate**: the aggregate (allowed to grow as adapters are added) and one per named export, which pins each adapter as independently tree-shakeable — if two adapters ever couple, the per-export gate trips in CI. Standalone, `{ svgToIcon }` measures 3.34 KB because auto-fit drags the core's lowering machinery; an app that already imports the core shares that chunk and pays ~0.4 KB incremental.\n\n## Playground\n\n```bash\nbun run play   # → http://localhost:3000\n```\n\n38 real icons from Lucide (IconNode imported from the package), Feather and Tabler; per-pair readout of the detected similarity (θ, σ, residual), polar/linear modes for comparison, spring presets and a t scrubber. Feather/Tabler data is extracted from the npm packages with `bun playground/extract-vendor-icons.mjs`.\n\n## Development\n\n```bash\nbun test          # 156 tests / ~13,700 asserts\nbun run typecheck # strict ×6: core+dom without lib DOM, playground, react, react-native, vue, svelte\nbun run format    # biome\nbun run build     # tsdown → dist/ + scripts/build-svelte.ts (svelte entry)\nbun run size      # size gates\n```\n\nIcons shown in the playground belong to their authors: [Lucide](https://lucide.dev) (ISC), [Feather](https://feathericons.com) (MIT), [Tabler](https://tabler.io/icons) (MIT).\n\n---\n\nMIT © [Guillermo](https://guillermolg.com) — made by [guillermolg.com](https://guillermolg.com)\n","readmeFilename":"README.md"}