{"_id":"@aznabee/freehand-ui","_rev":"8-873fa9f9ee0ca4d820a52dff1e77f4a8","name":"@aznabee/freehand-ui","dist-tags":{"latest":"0.1.8"},"versions":{"0.1.1":{"name":"@aznabee/freehand-ui","version":"0.1.1","keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"author":{"name":"Lakshay Gupta"},"license":"MIT","_id":"@aznabee/freehand-ui@0.1.1","maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"homepage":"https://github.com/Lakshay-art/freehand-ui#readme","bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"dist":{"shasum":"ba7eec2f7031fe42a737b0bab60d0a63344f46a7","tarball":"https://registry.npmjs.org/@aznabee/freehand-ui/-/freehand-ui-0.1.1.tgz","fileCount":9,"integrity":"sha512-YHwjTv8lLXHfufuQvpH+lZLXXgN14yYEOA2FlxSZF5CxGojfDJsRn3wfrawDE3yMjonHb5xTwTYoncuXJlysag==","signatures":[{"sig":"MEUCIC3pDO6b7KGTynbETDX8LM//k9L5zEQ4oO0z1Wv6ZpkGAiEAkQ+qfnZq5S76wdtAPbQGEoePSjA4NFMuQ1s3TED8iDQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":216155},"main":"./dist/freehand-ui.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/freehand-ui.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/freehand-ui.js","require":"./dist/freehand-ui.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"gitHead":"7b91773dd02f93555fcdf5ecc0c7e00161599a30","scripts":{"dev":"node scripts/build.js && node scripts/build-example-react.js && node scripts/serve.js","test":"node --test tests/*.test.js","build":"node scripts/build.js","prepublishOnly":"npm run build && npm test","build:example-react":"node scripts/build-example-react.js"},"_npmUser":{"name":"lakshay-the-gupta","email":"care@aznabee.com"},"repository":{"url":"git+https://github.com/Lakshay-art/freehand-ui.git","type":"git"},"_npmVersion":"11.16.0","description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","esbuild":"^0.25.0","react-dom":"^19.0.0"},"peerDependencies":{"react":">=17"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/freehand-ui_0.1.1_1786215124020_0.7877081571406521","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@aznabee/freehand-ui","version":"0.1.2","keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"author":{"name":"Lakshay Gupta"},"license":"MIT","_id":"@aznabee/freehand-ui@0.1.2","maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"homepage":"https://github.com/Lakshay-art/freehand-ui#readme","bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"dist":{"shasum":"57acb4c93a2124b536a3206ce5da6704c5c7abf1","tarball":"https://registry.npmjs.org/@aznabee/freehand-ui/-/freehand-ui-0.1.2.tgz","fileCount":9,"integrity":"sha512-eSqXp5tDNZOpE+bhS193WfFi3HA43cucItY2jP20egr4Tn0AsTMt/xt+sWXQxoLmIq7Nq1onKtBCz+OzTR+WVA==","signatures":[{"sig":"MEUCIQCBWBidzLBrWM1qg5DeP0ZMraVOc/f6kMbfwaPSI5zZywIgW/RdS9BnWvpbWavZ4YJH9PIOSaTQLRuEETgE4BEFuyE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":224392},"main":"./dist/freehand-ui.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/freehand-ui.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/freehand-ui.js","require":"./dist/freehand-ui.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"gitHead":"9e6339274cb0aec65e51b7354d46e271ba33e3eb","scripts":{"dev":"node scripts/build.js && node scripts/build-example-react.js && node scripts/serve.js","test":"node --test tests/*.test.js","build":"node scripts/build.js","prepublishOnly":"npm run build && npm test","build:example-react":"node scripts/build-example-react.js"},"_npmUser":{"name":"lakshay-the-gupta","email":"care@aznabee.com"},"repository":{"url":"git+https://github.com/Lakshay-art/freehand-ui.git","type":"git"},"_npmVersion":"11.16.0","description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","esbuild":"^0.25.0","react-dom":"^19.0.0"},"peerDependencies":{"react":">=17"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/freehand-ui_0.1.2_1786224200835_0.21513690369476435","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@aznabee/freehand-ui","version":"0.1.3","keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"author":{"name":"Lakshay Gupta"},"license":"MIT","_id":"@aznabee/freehand-ui@0.1.3","maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"homepage":"https://github.com/Lakshay-art/freehand-ui#readme","bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"dist":{"shasum":"46e945a217439026ed103120d7701c248f07e71f","tarball":"https://registry.npmjs.org/@aznabee/freehand-ui/-/freehand-ui-0.1.3.tgz","fileCount":9,"integrity":"sha512-1hSUTOy2gVJj7pxpsCFLioCjaP7K9QtOOSgiKLyCJSlONZPMzYCNHuf7fzAzrR/bOnPoO4AhqV1NzfBuNwPh3A==","signatures":[{"sig":"MEYCIQD2XArnGrj+dN5HRe/JF12kNktPU7boahS15qT7bpudiAIhAMb/+496hTlQK9JwxZRWWxDvqZVEDQmhqkxNXoJkA4u1","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":244351},"main":"./dist/freehand-ui.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/freehand-ui.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/freehand-ui.js","require":"./dist/freehand-ui.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"gitHead":"3d0b05df0762c95f72ebefb14c71a81d5f1cc0f6","scripts":{"dev":"node scripts/build.js && node scripts/build-example-react.js && node scripts/serve.js","test":"node --test tests/*.test.js","build":"node scripts/build.js","prepublishOnly":"npm run build && npm test","build:example-react":"node scripts/build-example-react.js"},"_npmUser":{"name":"lakshay-the-gupta","email":"care@aznabee.com"},"repository":{"url":"git+https://github.com/Lakshay-art/freehand-ui.git","type":"git"},"_npmVersion":"11.16.0","description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","esbuild":"^0.25.0","react-dom":"^19.0.0"},"peerDependencies":{"react":">=17"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/freehand-ui_0.1.3_1786230834870_0.6521677646863431","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@aznabee/freehand-ui","version":"0.1.4","keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"author":{"name":"Lakshay Gupta"},"license":"MIT","_id":"@aznabee/freehand-ui@0.1.4","maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"homepage":"https://github.com/Lakshay-art/freehand-ui#readme","bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"dist":{"shasum":"9b8b35f29fc4f81de2ccf874e809cc3f14c8d908","tarball":"https://registry.npmjs.org/@aznabee/freehand-ui/-/freehand-ui-0.1.4.tgz","fileCount":9,"integrity":"sha512-aVJjtBNtdbuupHyv7QMciJk8PUzOemyfKMVeyX//xQKxxS7UlKDoxe3WoHtKB1YBUH3SeNtATPLftn5HKi75+Q==","signatures":[{"sig":"MEYCIQDYytZEEI8adeQ6alZaZskjMzFkPL3s5rGEJqlnKbVzrQIhAKFfb9wSn6rCryTHKCgF2n42wDLbKS/didUBKCa+2dKf","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":244641},"main":"./dist/freehand-ui.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/freehand-ui.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/freehand-ui.js","require":"./dist/freehand-ui.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"gitHead":"17526224dc7c23d8b6cf335d8db8387ffb507800","scripts":{"dev":"node scripts/build.js && node scripts/build-example-react.js && node scripts/serve.js","test":"node --test tests/*.test.js","build":"node scripts/build.js","prepublishOnly":"npm run build && npm test","build:example-react":"node scripts/build-example-react.js"},"_npmUser":{"name":"lakshay-the-gupta","email":"care@aznabee.com"},"repository":{"url":"git+https://github.com/Lakshay-art/freehand-ui.git","type":"git"},"_npmVersion":"11.16.0","description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","esbuild":"^0.25.0","react-dom":"^19.0.0"},"peerDependencies":{"react":">=17"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/freehand-ui_0.1.4_1786274082052_0.45223357477606263","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@aznabee/freehand-ui","version":"0.1.5","keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"author":{"name":"Lakshay Gupta"},"license":"MIT","_id":"@aznabee/freehand-ui@0.1.5","maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"homepage":"https://github.com/Lakshay-art/freehand-ui#readme","bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"dist":{"shasum":"0a4f2a05d4559093ae40c968dff1fdd81ac978f2","tarball":"https://registry.npmjs.org/@aznabee/freehand-ui/-/freehand-ui-0.1.5.tgz","fileCount":9,"integrity":"sha512-ooUTxyu2A7pTZvhBFVtUIHp1wFtTDi87o7HXROhsFLiqAr8nKcnfyBAx+VfZ1lUKjQJgMSNYlADCt3QNgUQ7Nw==","signatures":[{"sig":"MEUCIQDUiI+03W3mbZyx86my1bRzjt1pxqgEcqEnAdbhe7tivwIgAcweYkc1PiiJhjwhOe/1irvQVO2jief/dr71ttmk/6g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":250308},"main":"./dist/freehand-ui.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/freehand-ui.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/freehand-ui.js","require":"./dist/freehand-ui.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"gitHead":"cb24f566f4b6c919ec0ac96bf2e4de9d945dac5b","scripts":{"dev":"node scripts/build.js && node scripts/build-example-react.js && node scripts/serve.js","test":"node --test tests/*.test.js","build":"node scripts/build.js","prepublishOnly":"npm run build && npm test","build:example-react":"node scripts/build-example-react.js"},"_npmUser":{"name":"lakshay-the-gupta","email":"care@aznabee.com"},"repository":{"url":"git+https://github.com/Lakshay-art/freehand-ui.git","type":"git"},"_npmVersion":"11.16.0","description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","esbuild":"^0.25.0","react-dom":"^19.0.0"},"peerDependencies":{"react":">=17"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/freehand-ui_0.1.5_1786276589199_0.3374736220256038","host":"s3://npm-registry-packages-npm-production"}},"0.1.6":{"name":"@aznabee/freehand-ui","version":"0.1.6","keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"author":{"name":"Lakshay Gupta"},"license":"MIT","_id":"@aznabee/freehand-ui@0.1.6","maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"homepage":"https://github.com/Lakshay-art/freehand-ui#readme","bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"dist":{"shasum":"c13a0f47ab8f304a67b140015f8f4a64a0c367d9","tarball":"https://registry.npmjs.org/@aznabee/freehand-ui/-/freehand-ui-0.1.6.tgz","fileCount":9,"integrity":"sha512-GmdUMIO2o80FDFZz7sDSTxKEfz3V+p3WnQXA+US+cGj0r7my0AxxJPCgpt9PlL36JAOpmf22HKoyRMKDH4q27Q==","signatures":[{"sig":"MEUCIF3yVY9yKP7f2H4E71sZtkQiJCFadU92o9Nh44gXhM60AiEA8FXfrbxLW4bdKlii4tEmdxP4Y6E5QkXoK8cUPHUS73g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":316982},"main":"./dist/freehand-ui.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/freehand-ui.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/freehand-ui.js","require":"./dist/freehand-ui.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"gitHead":"019b97f1f75cf226ea84d0b0219892eac65d1efb","scripts":{"dev":"node scripts/build.js && node scripts/build-example-react.js && node scripts/serve.js","test":"node --test tests/*.test.js","build":"node scripts/build.js","prepublishOnly":"npm run build && npm test","build:example-react":"node scripts/build-example-react.js"},"_npmUser":{"name":"lakshay-the-gupta","email":"care@aznabee.com"},"repository":{"url":"git+https://github.com/Lakshay-art/freehand-ui.git","type":"git"},"_npmVersion":"11.16.0","description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","esbuild":"^0.25.0","react-dom":"^19.0.0"},"peerDependencies":{"react":">=17"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/freehand-ui_0.1.6_1786899632274_0.6490435039507045","host":"s3://npm-registry-packages-npm-production"}},"0.1.7":{"name":"@aznabee/freehand-ui","version":"0.1.7","keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"author":{"name":"Lakshay Gupta"},"license":"MIT","_id":"@aznabee/freehand-ui@0.1.7","maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"homepage":"https://github.com/Lakshay-art/freehand-ui#readme","bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"dist":{"shasum":"5fe02bb4cbe6d22984ce5dc52e0f12b7e164866d","tarball":"https://registry.npmjs.org/@aznabee/freehand-ui/-/freehand-ui-0.1.7.tgz","fileCount":9,"integrity":"sha512-x2f2nTGtjLzjB7pJKfioA7mmEpA4WRxRDoT5bx1inky8TiqGKSRl6O/7u9P63FrYT9JG3vqnXV852skvAdHH7w==","signatures":[{"sig":"MEUCIEX41m4bh7kGAMTQpAIIwTRfhVgvUEQc5Ygfc/1PmhFlAiEAjNANnQI+NPk6aW8HrW0Aif0QeJkYhh5WNCwhKxWU514=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":311048},"main":"./dist/freehand-ui.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/freehand-ui.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/freehand-ui.js","require":"./dist/freehand-ui.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"gitHead":"c9adf4c136fcdad344e2506c05b53886e9d153d1","scripts":{"dev":"node scripts/build.js && node scripts/build-example-react.js && node scripts/serve.js","test":"node --test tests/*.test.js","build":"node scripts/build.js","prepublishOnly":"npm run build && npm test","build:example-react":"node scripts/build-example-react.js"},"_npmUser":{"name":"lakshay-the-gupta","email":"care@aznabee.com"},"repository":{"url":"git+https://github.com/Lakshay-art/freehand-ui.git","type":"git"},"_npmVersion":"11.16.0","description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","esbuild":"^0.25.0","react-dom":"^19.0.0"},"peerDependencies":{"react":">=17"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/freehand-ui_0.1.7_1787508099095_0.8222244880339931","host":"s3://npm-registry-packages-npm-production"}},"0.1.8":{"name":"@aznabee/freehand-ui","version":"0.1.8","description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","type":"module","main":"./dist/freehand-ui.cjs","module":"./dist/freehand-ui.js","types":"./dist/index.d.ts","sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/freehand-ui.js","require":"./dist/freehand-ui.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"scripts":{"build":"node scripts/build.js","build:example-react":"node scripts/build-example-react.js","dev":"node scripts/build.js && node scripts/build-example-react.js && node scripts/serve.js","test":"node --test tests/*.test.js","prepublishOnly":"npm run build && npm test"},"author":{"name":"Lakshay Gupta"},"repository":{"type":"git","url":"git+https://github.com/Lakshay-art/freehand-ui.git"},"homepage":"https://github.com/Lakshay-art/freehand-ui#readme","bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"license":"MIT","peerDependencies":{"react":">=17"},"peerDependenciesMeta":{"react":{"optional":true}},"devDependencies":{"esbuild":"^0.25.0","react":"^19.0.0","react-dom":"^19.0.0"},"gitHead":"64cc415b3ad7a90eb4f696007b1310245c5baabb","_id":"@aznabee/freehand-ui@0.1.8","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-LaDhD+sWaMH2aLFYhJO1oIhosUmQL9T7QwpKp/25c3RIetnkGG/KKUJGd/MzB1QXwwk+GoLRF0JTrmlVWAphZQ==","shasum":"87374159799874a63f705508be774a976c03c60b","tarball":"https://registry.npmjs.org/@aznabee/freehand-ui/-/freehand-ui-0.1.8.tgz","fileCount":9,"unpackedSize":306834,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCEOvVfhsgz8rXc997P54dHAt4oRGmI8Vp7920moEd87QIgVuWtuX6XJSKCukGZAvko4BsGXm2pFn/BTrVQN8dyiNs="}]},"_npmUser":{"name":"lakshay-the-gupta","email":"care@aznabee.com"},"directories":{},"maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/freehand-ui_0.1.8_1787513426762_0.3318424375253959"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-08T18:52:03.799Z","modified":"2026-08-23T19:30:27.066Z","0.1.1":"2026-08-08T18:52:04.181Z","0.1.2":"2026-08-08T21:23:20.984Z","0.1.3":"2026-08-08T23:13:55.017Z","0.1.4":"2026-08-09T11:14:42.197Z","0.1.5":"2026-08-09T11:56:29.345Z","0.1.6":"2026-08-16T17:00:32.436Z","0.1.7":"2026-08-23T18:01:39.294Z","0.1.8":"2026-08-23T19:30:26.911Z"},"bugs":{"url":"https://github.com/Lakshay-art/freehand-ui/issues"},"author":{"name":"Lakshay Gupta"},"license":"MIT","homepage":"https://github.com/Lakshay-art/freehand-ui#readme","keywords":["doodle","hand-drawn","svg","annotation","sketch","ui","react","gpt-hand-drawn-style"],"repository":{"type":"git","url":"git+https://github.com/Lakshay-art/freehand-ui.git"},"description":"Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer","maintainers":[{"name":"lakshay-the-gupta","email":"care@aznabee.com"}],"readme":"![freehand-ui - make your UI delightfully human](docs/images/hero.png)\n\n# Freehand-ui\n\nWrap any existing web element with a beautiful, responsive, hand-drawn doodle layer.\n\n## What it does\n\n`freehand-ui` adds a subtle SVG overlay on top of your UI - thin pen strokes, handwritten notes, arrows, and small decorations. It does **not** change your HTML layout or block clicks (`pointer-events: none`).\n\n**Good for:** call-to-action buttons, feature cards, onboarding hints, playful marketing UI.\n\n| Feature         | One-liner                                                            |\n| --------------- | -------------------------------------------------------------------- |\n| **Border**      | Hand-drawn frame that follows the element's shape and border-radius  |\n| **Notes**       | Caveat handwriting beside the element                                |\n| **Arrows**      | Curved, straight, dotted, or looped pointers that draw themselves in |\n| **Decorations** | Corner accents, stars, hearts, and more                              |\n| **Localised**   | Notes in the visitor's own language, right-to-left included          |\n| **Responsive**  | Stays aligned on resize, scroll, and reflow                          |\n\n**Live on [Aznabee.com](https://aznabee.com)** - see it in production on the real product.\n\n## ![Basic hand-drawn border around a card](docs/images/basic.png)\n\n## Install\n\n```bash\nnpm install @aznabee/freehand-ui\n```\n\n---\n\n## Vanilla JavaScript\n\nImport from the main entry point and call `doodle()` on any element or CSS selector.\n\n### Quick start\n\n```javascript\nimport { doodle } from \"@aznabee/freehand-ui\";\n\ndoodle(document.querySelector(\".card\"));\n// or\ndoodle(\".card\");\n```\n\nThis draws a hand-drawn border around the element and keeps it aligned as the page resizes, scrolls, or reflows.\n\n### Configuration\n\nPass a second argument to `doodle()`:\n\n```javascript\ndoodle(\".card\", {\n  border: true,\n  color: \"#ffffff\",\n  strokeWidth: 1.5,\n  roughness: 1.5,\n  padding: 0, // gap between the element's edge and the frame\n  radius: 20, // optional override; auto-detected from CSS when omitted\n  opacity: 0.9,\n  addBreaks: false, // lift the pen at random points around the outline\n  fontFamily: null, // override the handwriting stack\n  autoLoadFont: true,\n  locale: null, // language for a multilingual note; defaults to the visitor's\n});\n```\n\nThe frame traces the element's own edge by default. Raise `padding` to stand it off - `padding: 8` leaves a comfortable margin around a card.\n\n### Handwriting font\n\nNotes are set in [Caveat](https://fonts.google.com/specimen/Caveat). The library loads it the first time a note is drawn, so handwriting looks right in a plain app with no font setup. Without it, text falls back to the generic `cursive` family (on macOS that's a calligraphic serif, not handwriting).\n\nIt skips the request when the page already provides the family, and only ever requests it once. To take over:\n\n```javascript\n// self-hosted, or already loaded elsewhere on the page\ndoodle(\".card\", { note: \"hi\", autoLoadFont: false });\n\n// your own family\ndoodle(\".card\", { note: \"hi\", fontFamily: \"var(--font-caveat)\" });\n```\n\nSetting `autoLoadFont: false` without a `fontFamily` falls back to system handwriting faces (Bradley Hand, Segoe Script, Comic Sans MS). Notes are re-measured when a font finishes loading, so the underline always matches the final glyph widths.\n\n### Broken outlines\n\n`addBreaks` cuts randomly sized gaps into the border so it reads as a few confident dashes rather than one closed loop:\n\n```javascript\ndoodle(\".cta\", { addBreaks: true });\n\ndoodle(\".cta\", { addBreaks: 5 }); // five gaps\n\ndoodle(\".cta\", {\n  addBreaks: {\n    count: 4, // omit for 2–5 gaps\n    min: 6, // shortest gap, px\n    max: 30, // longest gap, px\n  },\n});\n```\n\nGaps are spread one per section of the perimeter so they never clump, and their total is capped at a third of the outline so the frame still reads as a frame. On small elements the gaps shrink to fit.\n\n### Handwritten annotations\n\n```javascript\ndoodle(\".cta\", {\n  note: \"start chat ♡\",\n});\n\ndoodle(\".cta\", {\n  note: { text: \"try me\", position: \"bottom-right\", underline: false },\n});\n```\n\n**Positions:** `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`, `left`, `top-left`. Same names apply to `arrow.from` / `arrow.to`.\n\nNotes are measured from real glyph metrics and laid out so they never sit on top of the element. The underline is drawn to the measured text width; set `underline: false` for plain handwriting.\n\n### Notes in the visitor's language\n\nWrite the note once per language and it is drawn in the one the visitor reads - taken from `navigator.languages`, which is what their operating system is set to. No configuration, no i18n library.\n\n```javascript\ndoodle(\".cta\", {\n  note: {\n    en: \"start chat\",\n    \"pt-BR\": \"iniciar conversa\",\n    hi: \"चैट शुरू करें\",\n    ar: \"ابدأ الدردشة\",\n    default: \"start chat\", // for languages you have not covered\n  },\n});\n\n// with the rest of the note's options\ndoodle(\".cta\", {\n  note: {\n    text: { en: \"try me\", fr: \"essaie-moi\" },\n    position: \"bottom-right\",\n  },\n});\n```\n\nKeys are BCP-47 tags. Matching follows what the visitor asked for, most wanted language first:\n\n- **Exact tag** - `pt-BR` for a `pt-BR` reader.\n- **Less specific** - `zh-Hant-TW` takes `zh-Hant` over a bare `zh`, so the script is only given up after the region.\n- **A sibling of the same language** - `en-AU` reads `en-GB` rather than dropping to another language.\n- **`default`**, or failing that the first translation written.\n\nEach wanted language is exhausted before the next is tried, so a `fr, en` visitor gets any French on offer ahead of the English original. The page's own `<html lang>` is consulted last, behind the visitor's list.\n\nTake over from the browser - to follow your app's own i18n state - with `locale`:\n\n```javascript\ndoodle(\".cta\", {\n  note: { en: \"start chat\", fr: \"démarrer le chat\" },\n  locale: i18n.language, // or a list, in preference order\n});\n```\n\nNotes are redrawn when the browser fires `languagechange`, so switching language mid-session updates them without a reload.\n\n**Right-to-left** notes need no flag. The base direction is read off the text itself, by its first strong character - the rule behind `dir=\"auto\"` - so an Arabic or Hebrew note is laid out and underlined right-to-left whether it came from a locale map or a plain string.\n\nCaveat itself only ships Latin and Cyrillic. Devanagari, Arabic, CJK and the rest fall through to the system handwriting stack and, past that, to whatever the browser picks - so those notes are legible but not handwritten. Point `fontFamily` at a face that covers your script to keep the handwriting.\n\n### Placement behavior\n\n- **The side you ask for is the side you get.** When a note runs past the viewport edge it slides horizontally by exactly the amount it overhangs, so a narrowing viewport walks it gradually inward instead of snapping to the opposite side.\n- **Vertical placement is fixed** relative to the element and does not react to scrolling - a note stays pinned to what it annotates.\n- If an element is jammed against the viewport edge, a note may sit partly off screen - use a different `position` or an `offset` there.\n\nNudge a note with `offset`:\n\n```javascript\ndoodle(\".cta\", {\n  note: { text: \"start chat\", position: \"top\", offset: { x: 40, y: -12 } },\n});\n```\n\nThe offset moves the note from the spot `position` chose. A large horizontal offset slides back into the viewport rather than carrying the note off screen.\n\n### Arrows\n\n```javascript\ndoodle(\".cta\", { arrow: true });\n\ndoodle(\".cta\", {\n  arrow: {\n    from: \"top-right\", // note | any position name\n    to: \"center\",\n    style: \"curved\", // curved | straight | dotted | looped\n  },\n});\n```\n\n![Arrow styles - curved, straight, dotted, and looped](docs/images/arrows.png)\n\n**Styles:** `curved` (default), `straight`, `dotted`, `looped` - `looped` ties a curl into the sweep, crossing the shaft once on the inside of its bow.\n\nEvery arc sweeps _over_ the straight line and drops onto its target, the way a hand draws one. With nothing to steer around, that side is fixed rather than picked at random, so a redraw never mirrors the gesture.\n\n**`from`** decides where the arrow leaves:\n\n- **With a note** - names a side of the note (where the pen lifts off the handwriting), then travels to the element.\n- **`\"note\"`** (default) - picks the side of the note facing the element.\n- **Without a note** - `from` names a side of the element itself.\n\n```javascript\ndoodle(\".cta\", {\n  note: { text: \"start chat\", position: \"top-right\" },\n  arrow: { from: \"bottom-left\" }, // leaves the note's bottom-left corner\n});\n```\n\n**`to`:** `\"edge\"` (default) lands the tip just outside the frame; `\"center\"` points into the element.\n\n### Arrow animation\n\nArrows animate by default. Two gestures, both pure CSS:\n\n- **draw** - the shaft is revealed by winding its dash offset down to zero, so the pen appears to travel from the note to the tip. The arrowhead flicks in as the pen arrives.\n- **drift** - the whole arrow leans a few pixels along its own line of travel, toward what it points at.\n\n```javascript\ndoodle(\".cta\", { arrow: { style: \"looped\" } }); // both, at defaults\n\ndoodle(\".cta\", { arrow: { animate: false } }); // draw it static\n\ndoodle(\".cta\", {\n  arrow: {\n    animate: {\n      draw: true, // dash sweep along the shaft\n      drift: true, // lean toward the target\n      speed: 1, // multiplier over every duration - 2 is twice as fast\n      duration: 900, // shaft draw, ms, before `speed`\n      delay: 150, // wait before the first stroke appears, ms\n      distance: 5, // how far the lean travels, px\n      driftDuration: 2200, // one full lean-and-return, ms\n      repeat: true, // false leans in once and stays there\n    },\n  },\n});\n```\n\n`speed` scales every phase at once, so the whole gesture keeps its shape - use it in preference to setting durations individually.\n\nA `dotted` arrow spends its dash pattern on the dots, and one pattern cannot both space them and hide the undrawn tail, so its dots flow toward the tip instead of the shaft being drawn on.\n\nNotes:\n\n- **`prefers-reduced-motion: reduce` turns both gestures off** and leaves the arrow fully drawn.\n- The overlay is rebuilt on every scroll and resize. Animations are placed by how long the overlay has been on the page rather than restarted, so scrolling never replays a draw or jolts a lean mid-cycle.\n\n### Decorations\n\n```javascript\ndoodle(\".card\", { decorations: true });\n\ndoodle(\".card\", {\n  decorations: {\n    count: 2, // border marks, hard-capped at 2\n    style: \"corners\", // corners | sides - omit to pick automatically\n    types: [\"arcs\", \"twinkle\", \"heart\"],\n  },\n});\n```\n\n![Decoration types - corner accents, stars, handwriting marks, and more](docs/images/decorations.png)\n\nA frame stays readable with **at most two marks around it**. Default composition:\n\n- **one corner accent** - `arcs` or `emphasis` - hugging a corner, aimed outward\n- **one corner star** - `twinkle` or `star` - on the corner farthest from it\n- **one handwriting accent** - `heart` or `sparkle` - beside the note, not the component\n\nCorner marks follow the _rounded_ corner rather than the bounding box. Marks too large for their gap are pushed further out.\n\n### Handwriting accents\n\n`heart` and `sparkle` are drawn at the far end of the note, on the side away from the element. With no `note` they are skipped rather than moved to the border.\n\n### Small components\n\nChips and icon buttons get a mirrored pair of two-stroke `emphasis` marks - one either side - instead of corner marks. This is automatic when the element is under 48px on its short side or under 130px wide. `style: \"sides\"` forces it at any size; `style: \"corners\"` opts out.\n\nOther types - `star`, `glimmer`, `smiley` - fill a corner slot when no accent or star was requested.\n\n### Child selector mode\n\nDecorate multiple children from a single parent overlay:\n\n```javascript\ndoodle(\"#hero\", {\n  children: \".doodle-item\",\n});\n```\n\n### Lifecycle\n\n```javascript\nconst instance = doodle(\".card\");\n\ninstance.update(); // recalculate geometry and redraw\ninstance.destroy(); // remove overlay and disconnect observers\n```\n\nCalling `doodle()` twice on the same element replaces the previous overlay - no duplicates.\n\n---\n\n## React / Next.js\n\nImport from `@aznabee/freehand-ui/react`. Every option in the [Vanilla JavaScript](#vanilla-javascript) section works as a prop.\n\n### Component\n\n```jsx\nimport Doodle from \"@aznabee/freehand-ui/react\";\n\n<Doodle note=\"start chat\" strokeWidth={2}>\n  <Button />\n</Doodle>;\n```\n\nShips `\"use client\"` - drops into the Next.js App Router without a wrapper, and renders on the server without warnings.\n\nThe doodle is drawn around **the wrapped component as a whole** - children are never decorated individually. To decorate descendants instead, opt in with `childSelector`:\n\n```jsx\n<Doodle childSelector=\".card\">\n  {items.map((item) => (\n    <Card key={item.id} className=\"card\" {...item} />\n  ))}\n</Doodle>\n```\n\n### Configuration\n\nSame options as `doodle()`, passed as props:\n\n```jsx\n<Doodle\n  border\n  color=\"#ffffff\"\n  strokeWidth={1.5}\n  roughness={1.5}\n  padding={0}\n  radius={20}\n  opacity={0.9}\n  addBreaks={false}\n  autoLoadFont\n>\n  <Card />\n</Doodle>\n```\n\n### Wrapper element\n\n`<Doodle>` renders a `<span style=\"display:inline-flex\">` that shrink-wraps its child, so the frame hugs your component instead of stretching to the full width of its container. `inline-flex` specifically: an `inline-block` wrapper adds baseline leading beneath an inline-level child like a `<button>`, which would leave the frame visibly taller than the button.\n\nOverride when you need different layout - extra props go to the DOM node:\n\n```jsx\n<Doodle as=\"div\" style={{ display: \"block\" }} className=\"w-full\" note=\"hi\">\n  <Card />\n</Doodle>\n```\n\nBorder radius is picked up automatically: if the wrapper has none of its own, it inherits the radius of the child it hugs, so a wrapped pill button is still drawn as a pill.\n\n### Hook\n\nFor an element you already hold a ref to, skip the wrapper:\n\n```jsx\nimport { useDoodle } from \"@aznabee/freehand-ui/react\";\n\nfunction Card() {\n  const ref = useRef(null);\n  useDoodle(ref, { note: \"new\", decorations: true });\n  return <div ref={ref}>…</div>;\n}\n```\n\n### Handwriting font\n\nWith `next/font`:\n\n```jsx\nimport { Caveat } from \"next/font/google\";\nconst caveat = Caveat({ subsets: [\"latin\"], variable: \"--font-caveat\" });\n\n<Doodle note=\"start chat\" fontFamily=\"var(--font-caveat)\" autoLoadFont={false}>\n  <Button />\n</Doodle>;\n```\n\n### Localised notes\n\nSame as the core - a map of languages goes straight in as the note, and `locale` hands the choice to your own i18n state:\n\n```jsx\n<Doodle note={{ en: \"start chat\", fr: \"démarrer le chat\" }}>\n  <Button />\n</Doodle>;\n\n<Doodle\n  note={{ en: \"start chat\", fr: \"démarrer le chat\" }}\n  locale={i18n.language}\n>\n  <Button />\n</Doodle>;\n```\n\nServer-rendered markup is unaffected: the overlay is drawn in an effect, so the language is read on the client and there is nothing to mismatch on hydration.\n\n### React notes\n\n- Inline object props (`note={{ text: \"hi\" }}`) are compared by value, so a re-render does not tear down and redraw the overlay with a new random seed.\n- `disabled` skips drawing entirely - useful behind a reduced-motion check or a feature flag.\n- TypeScript definitions ship with the package for both entry points.\n\n---\n\n## Examples\n\nRun the dev server after building:\n\n```bash\nnpm install\nnpm run build\nnpm run dev\n```\n\nThen open:\n\n- http://localhost:5173/examples/basic/\n- http://localhost:5173/examples/annotations/\n- http://localhost:5173/examples/arrows/\n- http://localhost:5173/examples/children/\n- http://localhost:5173/examples/decorations/\n- http://localhost:5173/examples/react/\n\n---\n\n## Design principles\n\n- **Framework agnostic** - plain JavaScript at the core; React is an optional entry point\n- **SVG based** - hand-drawn paths, not CSS borders\n- **Non-invasive** - `pointer-events: none`, no layout changes\n- **Responsive** - `ResizeObserver`, scroll listeners, and `requestAnimationFrame`\n- **Lightweight** - zero runtime dependencies\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md"}