{"_id":"@alikhalilll/a-skeleton","_rev":"6-40188b9f438927a16d8f31e6d7206c69","name":"@alikhalilll/a-skeleton","dist-tags":{"latest":"1.3.1"},"versions":{"1.0.0":{"name":"@alikhalilll/a-skeleton","version":"1.0.0","keywords":["vue","vue3","nuxt","skeleton","loader","shimmer","placeholder","auto","headless"],"author":{"name":"alikhalilll"},"license":"MIT","_id":"@alikhalilll/a-skeleton@1.0.0","maintainers":[{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"}],"homepage":"https://alikhalilll.github.io/ali-nuxt-toolkit/ui","bugs":{"url":"https://github.com/alikhalilll/ali-nuxt-toolkit/issues"},"dist":{"shasum":"94ef8f5d565dd67d6106de6139738e677aa4fff4","tarball":"https://registry.npmjs.org/@alikhalilll/a-skeleton/-/a-skeleton-1.0.0.tgz","fileCount":37,"integrity":"sha512-Oh48QYZsVpExLg1bf5VHdq+jiV0lYVnTe55wdhtRdiJJx2qTX5utejgmjTQPpkN9U6hbEoga2gWsqLi8W4SRnQ==","signatures":[{"sig":"MEUCIE0VLpKjr5YNyXD+YeK6igSm0KRlrMUFxlibFeMZa76eAiEAmQw5ayx1x0jCLsBBKTQA+gUqlLxlGIsOJyaVdD1c600=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":779503},"type":"module","_from":"file:alikhalilll-a-skeleton-1.0.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./nuxt":{"import":{"types":"./dist/nuxt/index.d.ts","default":"./dist/nuxt/index.js"},"require":{"types":"./dist/nuxt/index.d.cts","default":"./dist/nuxt/index.cjs"}},"./resolver":{"import":{"types":"./dist/resolver/index.d.ts","default":"./dist/resolver/index.js"},"require":{"types":"./dist/resolver/index.d.cts","default":"./dist/resolver/index.cjs"}},"./styles.css":"./dist/styles.css","./package.json":"./package.json"},"scripts":{"build":"tsx ../../../scripts/build/run-component-build.ts","clean":"rimraf dist web-types.json","validate":"pnpm build && pnpm validate-dist && pnpm validate-consumer","typecheck":"vue-tsc --noEmit -p tsconfig.json","validate-dist":"tsx ../../../scripts/validate/dist-validate.ts --pkg a-skeleton","validate-consumer":"tsx ../../../scripts/validate/consumer-validate.ts --pkg a-skeleton"},"_npmUser":{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"},"_resolved":"/private/var/folders/4j/cgljlpzd0n550x2mkwwsrj6c0000gn/T/d960797aa5b05df4e364f7611712f9c0/alikhalilll-a-skeleton-1.0.0.tgz","web-types":"./web-types.json","_integrity":"sha512-Oh48QYZsVpExLg1bf5VHdq+jiV0lYVnTe55wdhtRdiJJx2qTX5utejgmjTQPpkN9U6hbEoga2gWsqLi8W4SRnQ==","repository":{"url":"git+https://github.com/alikhalilll/ali-nuxt-toolkit.git","type":"git","directory":"packages/ui-components/ASkeleton"},"_npmVersion":"11.12.1","description":"Self-generating Vue 3 / Nuxt 3+ skeleton loader. First paint mirrors the slot's HTML structure; second load replays a pixel-aligned shape captured from the real DOM. Themeable via CSS vars. Part of the @alikhalilll/a-* toolkit.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"26.0.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.0","rimraf":"^6.0.1","tsdown":"0.22.0","vue-tsc":"^3.2.4","@nuxt/kit":"^4.4.2","typescript":"^5.9.2","@tsdown/css":"0.22.0","@types/node":"^22.13.0","tailwindcss":"^4.0.0","@nuxt/schema":"^4.4.2","@vueuse/core":"^14.0.0","unplugin-vue":"7.2.0","@tailwindcss/cli":"^4.0.0","@alikhalilll/a-ui-base":"1.0.0","unplugin-vue-components":"^32.1.0"},"peerDependencies":{"vue":"^3.5.0","@nuxt/kit":"^3.0.0 || ^4.0.0","@vueuse/core":"^14.0.0","unplugin-vue-components":"^28.0.0 || ^29.0.0 || ^30.0.0 || ^31.0.0 || ^32.0.0"},"peerDependenciesMeta":{"@nuxt/kit":{"optional":true},"unplugin-vue-components":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/a-skeleton_1.0.0_1781108526377_0.12000661641722177","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@alikhalilll/a-skeleton","version":"1.1.0","keywords":["vue","vue3","nuxt","skeleton","loader","shimmer","placeholder","auto","headless"],"author":{"name":"alikhalilll"},"license":"MIT","_id":"@alikhalilll/a-skeleton@1.1.0","maintainers":[{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"}],"homepage":"https://alikhalilll.github.io/ali-nuxt-toolkit/ui","bugs":{"url":"https://github.com/alikhalilll/ali-nuxt-toolkit/issues"},"dist":{"shasum":"4df3c6cebbbd03f83947f7922ac36b459775eff4","tarball":"https://registry.npmjs.org/@alikhalilll/a-skeleton/-/a-skeleton-1.1.0.tgz","fileCount":37,"integrity":"sha512-e+EVuh8YVBWw64KLBfDOVDhuc8kvSCIRr54txQ2bzCsCP2ibj/PJ7AjteqmWOl+9kdw0uoXjmVEKRD/YikscsA==","signatures":[{"sig":"MEUCIARsRtLC7qFptrQg512e2NCsEv8G3ap0JYmUuQUYyIvDAiEAtgRuKLNOPC6kCqXcIo87JO+GuHHhd5OiUuSoOpLAohk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":790825},"type":"module","_from":"file:alikhalilll-a-skeleton-1.1.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./nuxt":{"import":{"types":"./dist/nuxt/index.d.ts","default":"./dist/nuxt/index.js"},"require":{"types":"./dist/nuxt/index.d.cts","default":"./dist/nuxt/index.cjs"}},"./resolver":{"import":{"types":"./dist/resolver/index.d.ts","default":"./dist/resolver/index.js"},"require":{"types":"./dist/resolver/index.d.cts","default":"./dist/resolver/index.cjs"}},"./styles.css":"./dist/styles.css","./package.json":"./package.json"},"scripts":{"build":"tsx ../../../scripts/build/run-component-build.ts","clean":"rimraf dist web-types.json","validate":"pnpm build && pnpm validate-dist && pnpm validate-consumer","typecheck":"vue-tsc --noEmit -p tsconfig.json","validate-dist":"tsx ../../../scripts/validate/dist-validate.ts --pkg a-skeleton","validate-consumer":"tsx ../../../scripts/validate/consumer-validate.ts --pkg a-skeleton"},"_npmUser":{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"},"_resolved":"/private/var/folders/4j/cgljlpzd0n550x2mkwwsrj6c0000gn/T/2f9e12458101012e95b69960d366f1d6/alikhalilll-a-skeleton-1.1.0.tgz","web-types":"./web-types.json","_integrity":"sha512-e+EVuh8YVBWw64KLBfDOVDhuc8kvSCIRr54txQ2bzCsCP2ibj/PJ7AjteqmWOl+9kdw0uoXjmVEKRD/YikscsA==","repository":{"url":"git+https://github.com/alikhalilll/ali-nuxt-toolkit.git","type":"git","directory":"packages/ui-components/ASkeleton"},"_npmVersion":"11.12.1","description":"Self-generating Vue 3 / Nuxt 3+ skeleton loader. First paint mirrors the slot's HTML structure; second load replays a pixel-aligned shape captured from the real DOM. Themeable via CSS vars. Part of the @alikhalilll/a-* toolkit.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"26.0.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.0","rimraf":"^6.0.1","tsdown":"0.22.0","vue-tsc":"^3.2.4","@nuxt/kit":"^4.4.2","typescript":"^5.9.2","@tsdown/css":"0.22.0","@types/node":"^22.13.0","tailwindcss":"^4.0.0","@nuxt/schema":"^4.4.2","@vueuse/core":"^14.0.0","unplugin-vue":"7.2.0","@tailwindcss/cli":"^4.0.0","@alikhalilll/a-ui-base":"1.0.0","unplugin-vue-components":"^32.1.0"},"peerDependencies":{"vue":"^3.5.0","@nuxt/kit":"^3.0.0 || ^4.0.0","@vueuse/core":"^14.0.0","unplugin-vue-components":"^28.0.0 || ^29.0.0 || ^30.0.0 || ^31.0.0 || ^32.0.0"},"peerDependenciesMeta":{"@nuxt/kit":{"optional":true},"unplugin-vue-components":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/a-skeleton_1.1.0_1781173066541_0.10545926979329967","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@alikhalilll/a-skeleton","version":"1.2.0","keywords":["vue","vue3","nuxt","skeleton","loader","shimmer","placeholder","auto","headless"],"author":{"name":"alikhalilll"},"license":"MIT","_id":"@alikhalilll/a-skeleton@1.2.0","maintainers":[{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"}],"homepage":"https://alikhalilll.github.io/ali-nuxt-toolkit/ui","bugs":{"url":"https://github.com/alikhalilll/ali-nuxt-toolkit/issues"},"dist":{"shasum":"2242c0c6e580ee2cb7525a50ce4163e4034636b7","tarball":"https://registry.npmjs.org/@alikhalilll/a-skeleton/-/a-skeleton-1.2.0.tgz","fileCount":61,"integrity":"sha512-54rqSvEQXBJSb323K8Jowp/wQdCQ73yfnj42Zmp1rZa5kmauy8HCL0vtJK+owXnadiq+fkpxZNIB1hO7AebhMg==","signatures":[{"sig":"MEUCIG48Ev0XDoKWffJLSZrEdB9ZZFIsEI86vCgrw5IuSg3MAiEAk0WO75HBOYjhrPOkM/Ony7/gzgPPAUWpRx7X2Q1ylWA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2848663},"type":"module","_from":"file:alikhalilll-a-skeleton-1.2.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./nuxt":{"import":{"types":"./dist/nuxt/index.d.ts","default":"./dist/nuxt/index.js"},"require":{"types":"./dist/nuxt/index.d.cts","default":"./dist/nuxt/index.cjs"}},"./resolver":{"import":{"types":"./dist/resolver/index.d.ts","default":"./dist/resolver/index.js"},"require":{"types":"./dist/resolver/index.d.cts","default":"./dist/resolver/index.cjs"}},"./styles.css":"./dist/styles.css","./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsx ../../../scripts/build/run-component-build.ts","clean":"rimraf dist web-types.json","validate":"pnpm build && pnpm validate-dist && pnpm validate-consumer","typecheck":"vue-tsc --noEmit -p tsconfig.json","test:watch":"vitest","validate-dist":"tsx ../../../scripts/validate/dist-validate.ts --pkg a-skeleton","validate-consumer":"tsx ../../../scripts/validate/consumer-validate.ts --pkg a-skeleton"},"_npmUser":{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"},"_resolved":"/private/var/folders/4j/cgljlpzd0n550x2mkwwsrj6c0000gn/T/d6d013121141680d13a89e538a5ec53d/alikhalilll-a-skeleton-1.2.0.tgz","web-types":"./web-types.json","_integrity":"sha512-54rqSvEQXBJSb323K8Jowp/wQdCQ73yfnj42Zmp1rZa5kmauy8HCL0vtJK+owXnadiq+fkpxZNIB1hO7AebhMg==","repository":{"url":"git+https://github.com/alikhalilll/ali-nuxt-toolkit.git","type":"git","directory":"packages/ui-components/ASkeleton"},"_npmVersion":"11.12.1","description":"Self-generating Vue 3 / Nuxt 3+ skeleton loader. First paint mirrors the slot's HTML structure; second load replays a pixel-aligned shape captured from the real DOM. Themeable via CSS vars. Part of the @alikhalilll/a-* toolkit.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"26.0.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.0","rimraf":"^6.0.1","tsdown":"0.22.0","vitest":"^3.1.0","vue-tsc":"^3.2.4","@nuxt/kit":"^4.4.2","happy-dom":"^15.10.2","typescript":"^5.9.2","@tsdown/css":"0.22.0","@types/node":"^22.13.0","tailwindcss":"^4.0.0","@nuxt/schema":"^4.4.2","@vueuse/core":"^14.0.0","unplugin-vue":"7.2.0","@vue/test-utils":"^2.4.6","@tailwindcss/cli":"^4.0.0","@alikhalilll/a-ui-base":"1.0.0","unplugin-vue-components":"^32.1.0"},"peerDependencies":{"vue":"^3.5.0","@nuxt/kit":"^3.0.0 || ^4.0.0","@vueuse/core":"^14.0.0","unplugin-vue-components":"^28.0.0 || ^29.0.0 || ^30.0.0 || ^31.0.0 || ^32.0.0"},"peerDependenciesMeta":{"@nuxt/kit":{"optional":true},"unplugin-vue-components":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/a-skeleton_1.2.0_1781374255389_0.4757647137667247","host":"s3://npm-registry-packages-npm-production"}},"1.2.1":{"name":"@alikhalilll/a-skeleton","version":"1.2.1","keywords":["vue","vue3","nuxt","skeleton","loader","shimmer","placeholder","auto","headless"],"author":{"name":"alikhalilll"},"license":"MIT","_id":"@alikhalilll/a-skeleton@1.2.1","maintainers":[{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"}],"homepage":"https://alikhalilll.github.io/ali-nuxt-toolkit/ui/skeleton#setup","bugs":{"url":"https://github.com/alikhalilll/ali-nuxt-toolkit/issues"},"dist":{"shasum":"d8790c9d82330438f51a5f6be64225cafa30359b","tarball":"https://registry.npmjs.org/@alikhalilll/a-skeleton/-/a-skeleton-1.2.1.tgz","fileCount":61,"integrity":"sha512-rsbaIeSRgiT/eVpTBZNfwTQ4d22s0zTlhsfCbc2CsrIvT8m7583Uuqw+kWjs/M6zbrEPj+dQ8fcDi4rAmQ+XvQ==","signatures":[{"sig":"MEQCIAnrFh7d+f15a57HAy+CEdBnggeS9dDVyAodit+vlI1sAiAJKd9UWy2RqcPotMQ1H2ERNVULSif9Ti/Hig+r35Zlnw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2848678},"type":"module","_from":"file:alikhalilll-a-skeleton-1.2.1.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./nuxt":{"import":{"types":"./dist/nuxt/index.d.ts","default":"./dist/nuxt/index.js"},"require":{"types":"./dist/nuxt/index.d.cts","default":"./dist/nuxt/index.cjs"}},"./resolver":{"import":{"types":"./dist/resolver/index.d.ts","default":"./dist/resolver/index.js"},"require":{"types":"./dist/resolver/index.d.cts","default":"./dist/resolver/index.cjs"}},"./styles.css":"./dist/styles.css","./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsx ../../../scripts/build/run-component-build.ts","clean":"rimraf dist web-types.json","validate":"pnpm build && pnpm validate-dist && pnpm validate-consumer","typecheck":"vue-tsc --noEmit -p tsconfig.json","test:watch":"vitest","validate-dist":"tsx ../../../scripts/validate/dist-validate.ts --pkg a-skeleton","validate-consumer":"tsx ../../../scripts/validate/consumer-validate.ts --pkg a-skeleton"},"_npmUser":{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"},"_resolved":"/private/var/folders/4j/cgljlpzd0n550x2mkwwsrj6c0000gn/T/9975fad14aa6aeb8ee96415c74f381f1/alikhalilll-a-skeleton-1.2.1.tgz","web-types":"./web-types.json","_integrity":"sha512-rsbaIeSRgiT/eVpTBZNfwTQ4d22s0zTlhsfCbc2CsrIvT8m7583Uuqw+kWjs/M6zbrEPj+dQ8fcDi4rAmQ+XvQ==","repository":{"url":"git+https://github.com/alikhalilll/ali-nuxt-toolkit.git","type":"git","directory":"packages/ui-components/ASkeleton"},"_npmVersion":"11.12.1","description":"Self-generating Vue 3 / Nuxt 3+ skeleton loader. First paint mirrors the slot's HTML structure; second load replays a pixel-aligned shape captured from the real DOM. Themeable via CSS vars. Part of the @alikhalilll/a-* toolkit.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"26.0.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.0","rimraf":"^6.0.1","tsdown":"0.22.0","vitest":"^3.1.0","vue-tsc":"^3.2.4","@nuxt/kit":"^4.4.2","happy-dom":"^15.10.2","typescript":"^5.9.2","@tsdown/css":"0.22.0","@types/node":"^22.13.0","tailwindcss":"^4.0.0","@nuxt/schema":"^4.4.2","@vueuse/core":"^14.0.0","unplugin-vue":"7.2.0","@vue/test-utils":"^2.4.6","@tailwindcss/cli":"^4.0.0","@alikhalilll/a-ui-base":"1.0.0","unplugin-vue-components":"^32.1.0"},"peerDependencies":{"vue":"^3.5.0","@nuxt/kit":"^3.0.0 || ^4.0.0","@vueuse/core":"^14.0.0","unplugin-vue-components":"^28.0.0 || ^29.0.0 || ^30.0.0 || ^31.0.0 || ^32.0.0"},"peerDependenciesMeta":{"@nuxt/kit":{"optional":true},"unplugin-vue-components":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/a-skeleton_1.2.1_1781382142221_0.7038502972656493","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@alikhalilll/a-skeleton","version":"1.3.0","keywords":["vue","vue3","nuxt","skeleton","loader","shimmer","placeholder","auto","headless"],"author":{"name":"alikhalilll"},"license":"MIT","_id":"@alikhalilll/a-skeleton@1.3.0","maintainers":[{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"}],"homepage":"https://alikhalilll.github.io/ali-nuxt-toolkit/ui/skeleton#setup","bugs":{"url":"https://github.com/alikhalilll/ali-nuxt-toolkit/issues"},"dist":{"shasum":"96e6307a357ec4c6c4bde73b5663e527ab94b257","tarball":"https://registry.npmjs.org/@alikhalilll/a-skeleton/-/a-skeleton-1.3.0.tgz","fileCount":61,"integrity":"sha512-Y37JXWtAopegieBRQcKkSQx40QAgRk/JFTiKiLUCW424XaLxis2n3Gn8RUfHRJRiNJYc3B43siPOa+rrrKDjHw==","signatures":[{"sig":"MEQCIC62jxJhWOmKcbidkgS+Ch1hreV3NREOefXwfyO/Ue/fAiAIyaIx/AQgfnOR+ToNwORsolsuWKtpz+ahHWPxDwykNg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2906149},"type":"module","_from":"file:alikhalilll-a-skeleton-1.3.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./nuxt":{"import":{"types":"./dist/nuxt/index.d.ts","default":"./dist/nuxt/index.js"},"require":{"types":"./dist/nuxt/index.d.cts","default":"./dist/nuxt/index.cjs"}},"./resolver":{"import":{"types":"./dist/resolver/index.d.ts","default":"./dist/resolver/index.js"},"require":{"types":"./dist/resolver/index.d.cts","default":"./dist/resolver/index.cjs"}},"./styles.css":"./dist/styles.css","./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsx ../../../scripts/build/run-component-build.ts","clean":"rimraf dist web-types.json","validate":"pnpm build && pnpm validate-dist && pnpm validate-consumer","typecheck":"vue-tsc --noEmit -p tsconfig.json","test:watch":"vitest","validate-dist":"tsx ../../../scripts/validate/dist-validate.ts --pkg a-skeleton","validate-consumer":"tsx ../../../scripts/validate/consumer-validate.ts --pkg a-skeleton"},"_npmUser":{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"},"_resolved":"/private/var/folders/4j/cgljlpzd0n550x2mkwwsrj6c0000gn/T/324e9a75b9572cc08459ca9aafeef195/alikhalilll-a-skeleton-1.3.0.tgz","web-types":"./web-types.json","_integrity":"sha512-Y37JXWtAopegieBRQcKkSQx40QAgRk/JFTiKiLUCW424XaLxis2n3Gn8RUfHRJRiNJYc3B43siPOa+rrrKDjHw==","repository":{"url":"git+https://github.com/alikhalilll/ali-nuxt-toolkit.git","type":"git","directory":"packages/ui-components/ASkeleton"},"_npmVersion":"11.12.1","description":"Self-generating Vue 3 / Nuxt 3+ skeleton loader. First paint mirrors the slot's HTML structure; second load replays a pixel-aligned shape captured from the real DOM. Themeable via CSS vars. Part of the @alikhalilll/a-* toolkit.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"26.0.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.0","rimraf":"^6.0.1","tsdown":"0.22.0","vitest":"^3.1.0","vue-tsc":"^3.2.4","@nuxt/kit":"^4.4.2","happy-dom":"^15.10.2","typescript":"^5.9.2","@tsdown/css":"0.22.0","@types/node":"^22.13.0","tailwindcss":"^4.0.0","@nuxt/schema":"^4.4.2","@vueuse/core":"^14.0.0","unplugin-vue":"7.2.0","@vue/test-utils":"^2.4.6","@tailwindcss/cli":"^4.0.0","@alikhalilll/a-ui-base":"1.0.0","unplugin-vue-components":"^32.1.0"},"peerDependencies":{"vue":"^3.5.0","@nuxt/kit":"^3.0.0 || ^4.0.0","@vueuse/core":"^14.0.0","unplugin-vue-components":"^28.0.0 || ^29.0.0 || ^30.0.0 || ^31.0.0 || ^32.0.0"},"peerDependenciesMeta":{"@nuxt/kit":{"optional":true},"unplugin-vue-components":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/a-skeleton_1.3.0_1781530111825_0.9194061663918764","host":"s3://npm-registry-packages-npm-production"}},"1.3.1":{"name":"@alikhalilll/a-skeleton","version":"1.3.1","description":"Self-generating Vue 3 / Nuxt 3+ skeleton loader. First paint mirrors the slot's HTML structure; second load replays a pixel-aligned shape captured from the real DOM. Themeable via CSS vars. Part of the @alikhalilll/a-* toolkit.","license":"MIT","author":{"name":"alikhalilll"},"repository":{"type":"git","url":"git+https://github.com/alikhalilll/ali-nuxt-toolkit.git","directory":"packages/ui-components/ASkeleton"},"homepage":"https://alikhalilll.github.io/ali-nuxt-toolkit/ui/skeleton#setup","bugs":{"url":"https://github.com/alikhalilll/ali-nuxt-toolkit/issues"},"type":"module","sideEffects":["**/*.css"],"publishConfig":{"access":"public"},"keywords":["vue","vue3","nuxt","skeleton","loader","shimmer","placeholder","auto","headless"],"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./nuxt":{"import":{"types":"./dist/nuxt/index.d.ts","default":"./dist/nuxt/index.js"},"require":{"types":"./dist/nuxt/index.d.cts","default":"./dist/nuxt/index.cjs"}},"./resolver":{"import":{"types":"./dist/resolver/index.d.ts","default":"./dist/resolver/index.js"},"require":{"types":"./dist/resolver/index.d.cts","default":"./dist/resolver/index.cjs"}},"./styles.css":"./dist/styles.css","./package.json":"./package.json"},"types":"./dist/index.d.ts","web-types":"./web-types.json","engines":{"node":">=18"},"peerDependencies":{"@nuxt/kit":"^3.0.0 || ^4.0.0","@vueuse/core":"^14.0.0","unplugin-vue-components":"^28.0.0 || ^29.0.0 || ^30.0.0 || ^31.0.0 || ^32.0.0","vue":"^3.5.0"},"peerDependenciesMeta":{"@nuxt/kit":{"optional":true},"unplugin-vue-components":{"optional":true}},"dependencies":{},"devDependencies":{"@nuxt/kit":"^4.4.2","@nuxt/schema":"^4.4.2","@tailwindcss/cli":"^4.0.0","@tsdown/css":"0.22.0","@types/node":"^22.13.0","@vueuse/core":"^14.0.0","rimraf":"^6.0.1","tailwindcss":"^4.0.0","tsdown":"0.22.0","typescript":"^5.9.2","unplugin-vue":"7.2.0","unplugin-vue-components":"^32.1.0","vue":"^3.5.0","vue-tsc":"^3.2.4","vitest":"^3.1.0","@vue/test-utils":"^2.4.6","happy-dom":"^15.10.2","@alikhalilll/a-ui-base":"1.0.0"},"scripts":{"clean":"rimraf dist web-types.json","build":"tsx ../../../scripts/build/run-component-build.ts","test":"vitest run","test:watch":"vitest","typecheck":"vue-tsc --noEmit -p tsconfig.json","validate-dist":"tsx ../../../scripts/validate/dist-validate.ts --pkg a-skeleton","validate-consumer":"tsx ../../../scripts/validate/consumer-validate.ts --pkg a-skeleton","validate":"pnpm build && pnpm validate-dist && pnpm validate-consumer"},"_id":"@alikhalilll/a-skeleton@1.3.1","_integrity":"sha512-2OuCe9fbrmAvItdfznM+kVUxlztsJxJqU6r4Hwqkx/3RUY9g5+DXCdywXL2CiO9DwcANLCtaOlUBCT1YVUNWEA==","_resolved":"/private/var/folders/4j/cgljlpzd0n550x2mkwwsrj6c0000gn/T/32d52597035ab180f46e4e8b8b22e021/alikhalilll-a-skeleton-1.3.1.tgz","_from":"file:alikhalilll-a-skeleton-1.3.1.tgz","_nodeVersion":"26.0.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-2OuCe9fbrmAvItdfznM+kVUxlztsJxJqU6r4Hwqkx/3RUY9g5+DXCdywXL2CiO9DwcANLCtaOlUBCT1YVUNWEA==","shasum":"b8599762fd5df0c4a645f2626351f38b7bbc05fe","tarball":"https://registry.npmjs.org/@alikhalilll/a-skeleton/-/a-skeleton-1.3.1.tgz","fileCount":61,"unpackedSize":2914991,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHJKO7t21NTIyWV6aaBtpE6nizy6y/J41DKAaEHEgQY2AiAXo+MhzdPspS+UDQmtEcRXqTQXEMQ1Dn5IVycxe0GtUA=="}]},"_npmUser":{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"},"directories":{},"maintainers":[{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/a-skeleton_1.3.1_1781619098845_0.8823750286559828"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-10T16:22:06.203Z","modified":"2026-06-16T14:11:39.226Z","1.0.0":"2026-06-10T16:22:06.630Z","1.1.0":"2026-06-11T10:17:46.726Z","1.2.0":"2026-06-13T18:10:55.582Z","1.2.1":"2026-06-13T20:22:22.433Z","1.3.0":"2026-06-15T13:28:31.974Z","1.3.1":"2026-06-16T14:11:39.098Z"},"bugs":{"url":"https://github.com/alikhalilll/ali-nuxt-toolkit/issues"},"author":{"name":"alikhalilll"},"license":"MIT","homepage":"https://alikhalilll.github.io/ali-nuxt-toolkit/ui/skeleton#setup","keywords":["vue","vue3","nuxt","skeleton","loader","shimmer","placeholder","auto","headless"],"repository":{"type":"git","url":"git+https://github.com/alikhalilll/ali-nuxt-toolkit.git","directory":"packages/ui-components/ASkeleton"},"description":"Self-generating Vue 3 / Nuxt 3+ skeleton loader. First paint mirrors the slot's HTML structure; second load replays a pixel-aligned shape captured from the real DOM. Themeable via CSS vars. Part of the @alikhalilll/a-* toolkit.","maintainers":[{"name":"alikhalilll_dev","email":"alikhalilll.dev@gmail.com"}],"readme":"# `@alikhalilll/a-skeleton`\n\n> A self-generating skeleton loader for Vue 3 / Nuxt 3+.\n> Three rendering strategies (clone, mirror, structural) · pixel-identical computed-style snapshot ·\n> per-line text geometry via `Range` API · 15 named variants · themeable via CSS variables · SSR-safe mirror mode.\n\n<p align=\"center\">\n  <img\n    src=\"./.media/hero.png\"\n    alt=\"ASkeleton — a real profile card on the left, its shimmering skeleton replica on the right, captured and replayed pixel-aligned to the same layout\"\n    width=\"820\"\n  />\n</p>\n\n<p align=\"center\">\n  <sub>One wrapper, three engines — clone (default, pixel-identical) · mirror (SSR-safe) · structural (normal flow).</sub>\n</p>\n\n[![npm version](https://img.shields.io/npm/v/%40alikhalilll%2Fa-skeleton.svg?style=for-the-badge&label=npm&labelColor=0a0a0a&color=635bff)](https://www.npmjs.com/package/@alikhalilll/a-skeleton)\n[![license](https://img.shields.io/npm/l/%40alikhalilll%2Fa-skeleton.svg?style=for-the-badge&labelColor=0a0a0a&color=635bff)](./LICENSE)\n[![types](https://img.shields.io/npm/types/%40alikhalilll%2Fa-skeleton.svg?style=for-the-badge&labelColor=0a0a0a&color=635bff)](https://www.npmjs.com/package/@alikhalilll/a-skeleton)\n\n## Setup\n\n### Nuxt 3 / 4\n\n```bash\npnpm add @alikhalilll/a-skeleton\n# npm install @alikhalilll/a-skeleton\n# yarn add @alikhalilll/a-skeleton\n# bun add @alikhalilll/a-skeleton\n```\n\n```ts\n// nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@alikhalilll/a-skeleton/nuxt'],\n  css: ['@alikhalilll/a-skeleton/styles.css'],\n});\n```\n\n`<ASkeleton>`, `<ASkeletonLayer>`, `<ASkeletonBlock>`, and all variant primitives (`<ASkeletonCard>`, `<ASkeletonText>`, …) are auto-imported — no `import` statement needed in your `.vue` files.\n\n### Vue + Vite\n\n```bash\npnpm add @alikhalilll/a-skeleton\n```\n\n```ts\n// main.ts\nimport '@alikhalilll/a-skeleton/styles.css';\n```\n\nOptional auto-resolve via `unplugin-vue-components`:\n\n```ts\n// vite.config.ts\nimport Components from 'unplugin-vue-components/vite';\nimport ASkeletonResolver from '@alikhalilll/a-skeleton/resolver';\n\nexport default { plugins: [Components({ resolvers: [ASkeletonResolver()] })] };\n```\n\n---\n\n## Why this component\n\n- **Self-generating** — wrap your real component and the engine derives the placeholder from the slot you're already rendering. No hand-drawn shimmer markup, no parallel \"loading state\" templates to maintain.\n- **Three rendering strategies, one wrapper** — `mode=\"clone\"` (default) snapshots `getComputedStyle()` for pixel-identical replay regardless of styling pipeline; `mode=\"mirror\"` walks the vnode tree for SSR-safe placeholders; `useSkeleton()` + `<ASkeletonLayer>` for structural / normal-flow skeletons that reflow with their parent.\n- **Comprehensive surface capture** — per-edge borders, per-corner radii, background colour + image, box-shadow, opacity, filter, transform, mix-blend-mode, typography — every visible CSS property carries through so the skeleton reads as the real element, not as a generic shimmer.\n- **Per-line text geometry** — `Range.getClientRects()` captures the exact rendered text rect of every line. Wrapped paragraphs, centred multi-line headings, and RTL last-line positions replay 1:1 — no heuristics.\n- **15 named variants** — `<ASkeletonCard>`, `<ASkeletonText>`, `<ASkeletonHeading>`, `<ASkeletonAvatar>`, `<ASkeletonImage>`, `<ASkeletonVideo>`, `<ASkeletonButton>`, `<ASkeletonInput>`, `<ASkeletonChip>`, `<ASkeletonListItem>`, `<ASkeletonTable>`, `<ASkeletonChart>`, `<ASkeletonForm>`, `<ASkeletonArticle>`, `<ASkeletonDivider>`. Each accepts `class` / inline `style` and shares the same animation + theming pipeline.\n- **Bounded cost** — every walker enforces `maxDepth` / `maxNodes` / `minSize`. A 5 000-row table will not lock up the main thread. Captured nodes carry frozen pre-computed styles so the render loop is allocation-free.\n- **Themeable via CSS variables** — `--ak-skel-base`, `--ak-skel-highlight`, `--ak-skel-radius`, `--ak-skel-duration`, `--ak-skel-pulse-min`, `--ak-skel-ring`, `--ak-skel-icon`. Override on `:root`, a wrapper class (multi-tenant), or inline.\n- **Empty-interpolation aware** — `<h3>{{ data?.name }}</h3>` with null data still shimmers at the heading's natural rendered height. Both `mode=\"clone\"` and `mode=\"mirror\"` classify empty text-owner tags (`<h1>`-`<h6>`, `<p>`, `<span>`, …) as text bars rather than generic blocks.\n- **`prefers-reduced-motion`** disables animation automatically.\n- **SSR-safe (mirror)** — no `window` access during the structural pass; hydration is clean.\n- **TypeScript-first** — every prop, slot, type, and composable fully typed; web-types ship for JetBrains IDEs.\n\n---\n\n## Table of contents\n\n- [Setup](#setup)\n- [Quick start](#quick-start)\n- [Lists & grids — `v-for`, `#prototype`, `repeat`](#lists--grids--v-for-prototype-repeat)\n- [Three rendering strategies](#three-rendering-strategies)\n  - [Clone (default)](#clone-default)\n  - [Mirror](#mirror)\n  - [Structural / normal-flow](#structural--normal-flow)\n- [Authoring rule](#authoring-rule)\n- [API reference](#api-reference)\n  - [`<ASkeleton>` props](#askeleton-props)\n  - [Slots](#slots)\n  - [DOM escape hatches](#dom-escape-hatches)\n  - [Variant primitives](#variant-primitives)\n  - [`<ASkeletonBlock>`](#askeletonblock)\n  - [`<ASkeletonLayer>`](#askeletonlayer)\n  - [Composables](#composables)\n  - [Pure utilities](#pure-utilities)\n  - [Cache primitives](#cache-primitives)\n- [Theming](#theming)\n  - [CSS custom properties](#css-custom-properties)\n  - [Multi-tenant + dark mode](#multi-tenant--dark-mode)\n- [Animations](#animations)\n- [Accessibility](#accessibility)\n- [Performance](#performance)\n- [SSR](#ssr)\n- [TypeScript](#typescript)\n- [Browser support](#browser-support)\n- [Troubleshooting](#troubleshooting)\n- [License](#license)\n\n---\n\n## Quick start\n\n```vue\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue';\n\nconst user = ref(null);\nconst loading = computed(() => user.value === null);\n\nasync function load() {\n  user.value = await fetch('/api/me').then((r) => r.json());\n}\nload();\n</script>\n\n<template>\n  <ASkeleton :loading=\"loading\">\n    <!-- Keep TAGS unconditional (the walker sees the same shape in both\n         states); gate per-leaf CONTENT via interpolation. -->\n    <div class=\"flex items-start gap-4 p-4\">\n      <img\n        :src=\"user?.avatar\"\n        :alt=\"user?.name ?? ''\"\n        class=\"size-16 shrink-0 rounded-full object-cover\"\n      />\n      <div class=\"flex-1\">\n        <h3 class=\"text-base font-semibold\">{{ user?.name }}</h3>\n        <p class=\"text-sm leading-relaxed\">{{ user?.bio }}</p>\n      </div>\n    </div>\n  </ASkeleton>\n</template>\n```\n\nThat's the whole API for most cases. Every layer underneath is also a public export — see the recipes below.\n\n---\n\n## Lists & grids — `v-for`, `#prototype`, `repeat`\n\nThe most common loading state is a list of cards. Two details make it work cleanly:\n\n1. **Always supply a `#prototype` when the default slot iterates.** The component does **not** auto-detect `v-for` and pick the first sibling — heuristics over keyed siblings misfire on legitimate-but-similar markup, and you're the only one who can say authoritatively which element is the prototype. The `#prototype` slot is the explicit single-instance template; it takes precedence over the default slot during loading and is never rendered when `loading=false`. Without `#prototype`, the default slot is used as the shape source verbatim — so an empty `v-for` during the initial load produces a blank skeleton.\n2. **`:repeat=\"N\"` × the prototype fills the layout.** The wrapper carries your layout class (`grid grid-cols-3 gap-4`, `flex flex-wrap`, …), and the prototype copies become direct children. Set `:repeat` to the expected item count so the loading state occupies the same visual area as the loaded state — no shift when data arrives.\n\nThe exact failing case from issue reports — a responsive grid of cards with a switch, a counter, a heading, and two buttons:\n\n```vue\n<ASkeleton\n  :loading=\"loading\"\n  :repeat=\"6\"\n  class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\"\n>\n  <template #prototype>\n    <article class=\"p-5 rounded-lg bg-white flex flex-col gap-3 ring-1 ring-gray-200\">\n      <div class=\"flex items-center justify-between\">\n        <p class=\"text-sm text-gray-500\">{{ t('users_count', { n: 0 }) }}</p>\n        <UiSwitch :model-value=\"false\" />\n      </div>\n      <h3 class=\"text-lg text-gray-900 font-bold\">{{ t('placeholder') }}</h3>\n      <div class=\"pt-2 flex gap-2 items-center justify-end\">\n        <UiButton type=\"button\" variant=\"outline\">{{ t('view') }}</UiButton>\n        <UiButton type=\"button\" variant=\"ghost\">{{ t('edit') }}</UiButton>\n      </div>\n    </article>\n  </template>\n\n  <article\n    v-for=\"role in roles\"\n    :key=\"role?.id\"\n    class=\"p-5 rounded-lg bg-white flex flex-col gap-3 ring-1 ring-gray-200\"\n  >\n    <!-- …real card content… -->\n  </article>\n</ASkeleton>\n```\n\nWhat you get:\n\n- **Grid container shared across states.** The `grid grid-cols-…` lives on `<ASkeleton>` itself, so the grid is the same DOM element loading or loaded. The breakpoints kick in identically in both.\n- **Six skeleton cards** during loading, each shaped like one real card. When data lands, the same grid populates with the `v-for` items — no layout shift.\n- **Components walked correctly.** `<UiSwitch>`, `<UiButton>`, `<Icon>` are recursed into via their slot children — button text shimmers inside a button shape; the switch becomes a switch-shaped block; the icon becomes an icon-sized block. Components whose rendered children all fall below `minNodeSize` fall back to a single shimmer block at the component's own rect (so they never disappear).\n- **No skeleton classes leak when not loading.** With `loading=false` the wrapper carries only the grid class — no `a-skeleton`, no `__capture`, no `aria-busy`.\n- **`display: contents` opt-out.** If you don't pass a `class`, the wrapper is `display: contents` when not loading — invisible to `parent > child` selectors, so flex/grid parents still target your real items as direct children.\n\nThe shorter spelling — when the default slot has a single, static, representative element (no `v-for`) — drops the `#prototype` and uses the default slot as the shape directly:\n\n```vue\n<ASkeleton :loading=\"loading\" :repeat=\"3\" class=\"grid grid-cols-3 gap-4\">\n  <article class=\"…\">…one static card…</article>\n</ASkeleton>\n```\n\nAnything iterated (`v-for`, async-loaded lists, conditional groups) should go through `#prototype` so the shape stays predictable.\n\n---\n\n## Three rendering strategies\n\nPick the one that matches how your styles get to the DOM:\n\n| Strategy              | Surface                              | How geometry is derived                                                                                                                                                                         | Best when                                                                                                                   |\n| --------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |\n| **Clone** _(default)_ | `<ASkeleton :loading>`               | Mounts the slot off-screen, snapshots every leaf's **computed style** via `getComputedStyle()`, replays as positioned divs each carrying its captured inline CSS.                               | Styles come from any pipeline — class names, inline `style`, CSS-in-JS, scoped styles. Pixel-identical. Client-side only.   |\n| **Mirror**            | `<ASkeleton mode=\"mirror\" :loading>` | Walks the slot's **vnode** tree at render time. Preserves every tag and `class`; leaves become shimmer bars.                                                                                    | SSR-safe placeholders (no DOM read needed) or when the static class is enough to drive the surface.                         |\n| **Structural**        | `useSkeleton()` + `<ASkeletonLayer>` | Walks the real **DOM** tree, preserves container tags + classes + layout CSS (`display`, `flex-*`, `grid-*`, `gap`, `padding`), replaces leaves with `<div class=\"a-skel\">` in **normal flow**. | You want the skeleton to reflow with its parent, or you're orchestrating cache + capture outside a single wrapper instance. |\n\nAll three share one cache module, one theming surface, and the same a11y baseline.\n\n### Clone (default)\n\nThe slot mounts off-screen inside a `visibility: hidden` capture host. After mount, `captureSnapshot()` reads `getComputedStyle()` for every element under the host — capturing the **final** background, per-edge border, per-corner radius, box-shadow, padding, opacity, filter, transform, typography — plus per-line text rects via `Range.getClientRects()`. `<ASkeletonClone>` then replays the snapshot as a tree of positioned divs each carrying its captured inline style.\n\n```vue\n<ASkeleton :loading=\"loading\">\n  <SomeRichComponent />\n</ASkeleton>\n```\n\nThe capture API is a public export — power-users can roll their own capture / replay flow:\n\n```ts\nimport { captureSnapshot, type CaptureSnapshot } from '@alikhalilll/a-skeleton';\n\nconst snap: CaptureSnapshot = captureSnapshot(myRootEl, {\n  maxDepth: 12,\n  maxNodes: 800,\n  minSize: 4,\n});\n```\n\n### Mirror\n\nSwitch to `mode=\"mirror\"` when you need the placeholder on the server, or when your styles already drive the surface correctly from class names alone:\n\n```vue\n<ASkeleton mode=\"mirror\" :loading=\"loading\">\n  <SomeRichComponent />\n</ASkeleton>\n```\n\n`buildStructuralSkeleton()` walks the slot's vnode tree at render time, preserves every element with its real `class` / inline `style`, and replaces text-bearing leaves with `<span class=\"a-skel-text-content\">` (transparent text + skeleton background, exact rendered width via `box-decoration-break: clone`). Atomic / interactive tags (`<img>`, `<button>`, `<svg>`) become `<div class=\"a-skel-block\">` sized from their original class.\n\nText-owner tags with empty content (`<h3>{{ data?.name }}</h3>` during loading) auto-shimmer at the heading's natural rendered height — the walker injects a placeholder text-content span so the bar's height tracks the tag's font-size / line-height.\n\n### Structural / normal-flow\n\nFor cases where the wrapper isn't your unit of orchestration — caching shapes across instances, persisting between sessions, or wanting the skeleton to reflow with its parent — reach for `useSkeleton()` + `<ASkeletonLayer>`:\n\n```vue\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue';\nimport { useSkeleton } from '@alikhalilll/a-skeleton';\n\nconst props = defineProps<{ userId: string }>();\nconst user = ref(null);\nconst loading = computed(() => user.value === null);\nconst containerRef = ref<HTMLElement | null>(null);\n\nconst { shape, clear } = useSkeleton({\n  cacheKey: `user-card:${props.userId}`,\n  // While loading, target is null → no capture. When real content mounts,\n  // target returns the wrapper → ResizeObserver + capture.\n  target: () => (loading.value ? null : containerRef.value),\n  persist: true,\n});\n\nfetchUser(props.userId).then((u) => (user.value = u));\n</script>\n\n<template>\n  <div ref=\"containerRef\">\n    <ASkeletonLayer v-if=\"loading && shape\" :shape=\"shape\" />\n    <ColdStartFallback v-else-if=\"loading\" />\n    <UserCard v-else :data=\"user\" />\n  </div>\n</template>\n```\n\n`walkStructural()` produces a frozen tree where containers preserve their tag, original `class`, and captured layout CSS (`display`, `flex-*`, `gap`, `padding`, `grid-*`, `box-sizing`), and leaves carry inline `width` / `height` + visual signals. `<ASkeletonLayer>` replays the tree in **normal flow**, so the skeleton lives inside its parent's layout instead of overlaying it with absolute coordinates — it reflows on viewport resize and never collapses to a flat positioned grid.\n\nGiven real markup like:\n\n```html\n<div class=\"flex flex-col gap-4 p-4\">\n  <h3>…</h3>\n  <p>…</p>\n  <button>…</button>\n</div>\n```\n\n…the layer replays as:\n\n```html\n<div\n  class=\"flex flex-col gap-4 p-4\"\n  style=\"display: flex; flex-direction: column; gap: 16px; padding: 16px; box-sizing: border-box\"\n>\n  <div class=\"a-skel\" style=\"width: 200px; height: 24px; …\" />\n  <div class=\"a-skel\" style=\"width: 280px; height: 16px; …\" />\n  <div class=\"a-skel\" style=\"width: 120px; height: 36px; …\" />\n</div>\n```\n\nThe original class is preserved so utility-first CSS still applies; the resolved layout CSS is inlined as a fallback so the skeleton looks correct even when the stylesheet isn't at the mount point.\n\nThe structural cache lives in its own `localStorage` namespace (`a-skeleton:s:` prefix, schema `v: 3`), so it can never collide on the same `cacheKey` with the flat `CachedShape` cache. Mismatched schema versions auto-purge on read.\n\n---\n\n## Authoring rule\n\nKeep **tags** unconditional; gate per-leaf **content** via interpolation. Two safe patterns:\n\n1. **Always render the same tag**, gate its content.\n   - `<img :src=\"user?.avatar\">` renders an `<img>` in both states (walker treats it as atomic → sized shimmer block).\n   - `<h3>{{ user?.name }}</h3>` renders an empty `<h3>` during loading and the walker auto-injects a placeholder shimmer bar at the heading's natural width.\n2. **Use explicit primitives** with `v-if` / `v-else` (see [`<ASkeletonBlock>`](#askeletonblock) and the [Variant primitives](#variant-primitives)) when the loading and loaded states genuinely have different markup.\n\nThe pattern to **avoid** is swapping whole branches (`<img v-if><div v-else>`). The walker sees one shape now and a different shape later — the skeleton can't predict the placeholder geometry.\n\n---\n\n## API reference\n\n### `<ASkeleton>` props\n\n| Prop          | Type                             | Default            | Description                                                                                                                                                                                                                                                         |\n| ------------- | -------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `loading`     | `boolean`                        | —                  | When `true`, show the skeleton.                                                                                                                                                                                                                                     |\n| `mode`        | `'clone' \\| 'mirror'`            | `'clone'`          | Rendering strategy. `clone` snapshots `getComputedStyle()`; `mirror` walks the vnode tree (SSR-safe).                                                                                                                                                               |\n| `cacheKey`    | `string`                         | auto, per-instance | Auto-generated as `<slot-fingerprint>:<useId()>` so each instance has its own slot. Pass explicitly to share a captured shape across instances (e.g. a list of identical cards) or to differentiate prop-variant shapes from the same component.                    |\n| `maxDepth`    | `number`                         | `16`               | Max recursion depth when capturing.                                                                                                                                                                                                                                 |\n| `maxNodes`    | `number`                         | `600`              | Hard cap on captured / structural nodes. Walks bail beyond this with `truncated: true` and a one-time `console.warn` per `cacheKey`.                                                                                                                                |\n| `minNodeSize` | `number`                         | `4`                | Skip elements smaller than this many CSS pixels (either axis). Drops hairlines / spacer dots.                                                                                                                                                                       |\n| `persist`     | `boolean`                        | `false`            | Mirror captured shape to `localStorage`. Schema-versioned; entries from older releases auto-purge on read.                                                                                                                                                          |\n| `animation`   | `'shimmer' \\| 'pulse' \\| 'none'` | `'shimmer'`        | Animation variant. `prefers-reduced-motion` disables animation automatically.                                                                                                                                                                                       |\n| `fallback`    | `'shimmer' \\| 'block'`           | `'shimmer'`        | Default cache-miss UI when no `#fallback` slot is provided (mirror mode only).                                                                                                                                                                                      |\n| `repeat`      | `number`                         | `1`                | Number of prototype copies to render while loading. Pair with a `grid` / `flex` class on `<ASkeleton>` to fill a layout 1:1 with the eventual loaded content — no shift when data arrives.                                                                          |\n| `class`       | `HTMLAttributes['class']`        | —                  | Class on the wrapper. Persists across `loading=true` ↔ `false` so layout classes (`grid grid-cols-3 gap-4`, …) keep working in both states. When omitted and `loading=false`, the wrapper falls back to `display: contents` and is invisible to surrounding layout. |\n\n### Slots\n\n| Slot        | Description                                                                                                                                                                            |\n| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `default`   | The real content. Rendered when `loading` is false; the slot's first iteration is also used as the skeleton's shape source when no `#prototype` is provided.                           |\n| `prototype` | Optional single-instance template used as the shape source while loading. Takes precedence over the default slot. Pair with `:repeat=\"N\"` when the default slot iterates with `v-for`. |\n| `fallback`  | Custom UI for cache misses (mirror mode). Defaults to a single full-width shimmer block.                                                                                               |\n\n### DOM escape hatches\n\nMark elements during the walk via data attributes — applies to all three strategies:\n\n| Attribute              | Effect                                                                                                                                                     |\n| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `data-skeleton-stop`   | Stop recursing into this element — render as a single block carrying its outer `class` / `style`.                                                          |\n| `data-skeleton-ignore` | Skip the element entirely (no block emitted). Use for decorative chrome (background SVGs, dividers, persistent badges) that should always render verbatim. |\n\nAuthoring rule for branded chrome — skeleton-ise **content**, not **chrome**. If a component's identity is its gradient background / decorative SVGs, wrap only the inner content with `<ASkeleton>` and let the container always render. Mark decorations with `data-skeleton-ignore` so the walker treats them as invisible.\n\n### Variant primitives\n\nWhen auto-capture isn't the right fit (loading states without real content to wrap, very dense layouts, screens you'd rather author once), drop in a named variant. Each accepts `animation=\"pulse | shimmer | wave | none\"`, `class`, and inline `style`. Every variant ships `role=\"status\"`, `aria-busy=\"true\"`, and a visually-hidden `<span class=\"a-skel-sr-only\">Loading…</span>`.\n\n| Component             | Maps to                                    | Key props                                              |\n| --------------------- | ------------------------------------------ | ------------------------------------------------------ |\n| `<ASkeletonText>`     | n stacked bars, last line shorter          | `lines`, `width`                                       |\n| `<ASkeletonHeading>`  | one bar sized to heading level             | `level` (1–6), `width`                                 |\n| `<ASkeletonAvatar>`   | circle / square / rounded                  | `size`, `shape`                                        |\n| `<ASkeletonImage>`    | aspect-ratio rect + image-icon placeholder | `ratio`, `width`, `height`, `showIcon`                 |\n| `<ASkeletonVideo>`    | rect + play-icon placeholder               | `ratio`, `width`, `height`, `showIcon`                 |\n| `<ASkeletonButton>`   | rounded rect, filled or outlined           | `width`, `height`, `outlined`                          |\n| `<ASkeletonInput>`    | bordered rect with caret bar inside        | `width`, `height`                                      |\n| `<ASkeletonChip>`     | small pill                                 | `width`, `height`                                      |\n| `<ASkeletonListItem>` | avatar + n text lines + trailing slot      | `avatar`, `lines`, `trailing`                          |\n| `<ASkeletonCard>`     | media + heading + paragraph + actions      | `media`, `heading`, `lines`, `actions`, `footerAvatar` |\n| `<ASkeletonTable>`    | header row + n × m body cells              | `rows`, `columns`, `showHeader`                        |\n| `<ASkeletonChart>`    | n vertical bars of varying heights         | `bars`, `height`, `showHeader`                         |\n| `<ASkeletonForm>`     | label + input pairs + submit               | `fields`, `showSubmit`                                 |\n| `<ASkeletonArticle>`  | heading + media + n paragraphs             | `media`, `paragraphs`, `linesPerParagraph`             |\n| `<ASkeletonDivider>`  | thin shimmer rule                          | `thickness`                                            |\n\n### `<ASkeletonBlock>`\n\nSingle-block primitive for hand-crafted skeletons. Flow-layout friendly — composes with flex, grid, stacks.\n\n```vue\n<template>\n  <div v-if=\"loading\" class=\"flex items-start gap-4 p-4\">\n    <ASkeletonBlock type=\"circle\" :w=\"64\" :h=\"64\" />\n    <div class=\"flex-1 space-y-2\">\n      <ASkeletonBlock type=\"text\" :w=\"160\" :h=\"18\" />\n      <ASkeletonBlock type=\"text\" :w=\"100\" :h=\"12\" />\n      <ASkeletonBlock type=\"text\" :lines=\"3\" :h=\"14\" class=\"!mt-3\" />\n    </div>\n  </div>\n  <UserCard v-else :data=\"user\" />\n</template>\n```\n\n| Prop        | Type                                       | Default     | Notes                                                               |\n| ----------- | ------------------------------------------ | ----------- | ------------------------------------------------------------------- |\n| `type`      | `'block' \\| 'text' \\| 'image' \\| 'circle'` | `'block'`   | `circle` defaults `border-radius: 50%`.                             |\n| `w`         | `number \\| string`                         | —           | Width (number = px).                                                |\n| `h`         | `number \\| string`                         | —           | Height (number = px).                                               |\n| `radius`    | `number \\| string`                         | —           | Border radius (number = px).                                        |\n| `lines`     | `number`                                   | `1`         | For `type='text'`, render N stacked bars; last is 70% width.        |\n| `animation` | `'shimmer' \\| 'pulse' \\| 'none'`           | `'shimmer'` | Animation variant.                                                  |\n| `class`     | `HTMLAttributes['class']`                  | —           | Class on the root (single block, or the stack for multi-line text). |\n\n### `<ASkeletonLayer>`\n\nRenders a `StructuralShape` (from `useSkeleton()` or `walkStructural()`) in normal flow. The layer is a transparent shell — captured containers carry the layout.\n\n| Prop        | Type                             | Default     | Description                       |\n| ----------- | -------------------------------- | ----------- | --------------------------------- |\n| `shape`     | `StructuralShape \\| undefined`   | —           | Renders nothing when `undefined`. |\n| `animation` | `'shimmer' \\| 'pulse' \\| 'none'` | `'shimmer'` | Animation variant.                |\n| `class`     | `HTMLAttributes['class']`        | —           | Class on the layer wrapper.       |\n\n### Composables\n\n#### `useSkeleton(options) → { shape, captureNow, clear }`\n\nWires the DOM probe + structural cache + reactivity around a target element. The reactive `shape` feeds `<ASkeletonLayer>`.\n\n| Option             | Type                        | Default | Description                                                          |\n| ------------------ | --------------------------- | ------- | -------------------------------------------------------------------- |\n| `cacheKey`         | `string`                    | —       | Required. Identifier for the shape cache.                            |\n| `target`           | `() => HTMLElement \\| null` | —       | Getter for the element to measure. Return `null` to disable capture. |\n| `persist`          | `boolean`                   | `false` | Mirror captured shape to `localStorage`.                             |\n| `maxDepth`         | `number`                    | `12`    | Forwarded to `walkStructural`.                                       |\n| `maxNodes`         | `number`                    | `500`   | Forwarded to `walkStructural`.                                       |\n| `minSize`          | `number`                    | `4`     | Forwarded to `walkStructural`.                                       |\n| `resizeDebounceMs` | `number`                    | `150`   | `ResizeObserver` re-capture debounce.                                |\n\nReturns `{ shape: Readonly<Ref<StructuralShape | undefined>>, captureNow: () => StructuralShape | undefined, clear: () => void }`.\n\n#### `useShapeProbe(getTarget, options)`\n\nLower-level — `ResizeObserver` + debounced capture without the cache. You manage persistence (Pinia, API, server-rendered geometry). The `capture` option lets you swap in any strategy:\n\n```ts\nimport { useShapeProbe, walkStructural } from '@alikhalilll/a-skeleton';\n\nuseShapeProbe(() => containerRef.value, {\n  maxDepth: 12,\n  resizeDebounceMs: 200,\n  capture: walkStructural, // default is walkDom (flat); pass walkStructural for the tree shape.\n  onCapture: (shape) => myStore.saveShape('user-card', shape),\n});\n```\n\n### Pure utilities\n\nWhen none of the components fit, drop down to the pure functions:\n\n| Symbol                                     | Returns                  | Purpose                                                                               |\n| ------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------- |\n| `walkDom(el, options)`                     | `CachedShape` (flat)     | One-shot synchronous capture — positioned-block model, root-relative absolute coords. |\n| `walkStructural(el, options)`              | `StructuralShape` (tree) | One-shot synchronous capture — preserves container layout, normal-flow replay.        |\n| `captureSnapshot(el, options)`             | `CaptureSnapshot` (tree) | Comprehensive computed-style snapshot used by clone mode.                             |\n| `buildStructuralSkeleton(vnodes, options)` | `VNode[]`                | Mirror-mode renderer for any vnode tree (e.g. inside a render-function component).    |\n| `fingerprintSlot(vnodes)`                  | `string`                 | Slot-name fragment of the auto `cacheKey` (`'UserCard'`, `'div'`, or `'anonymous'`).  |\n\n### Cache primitives\n\n| Symbol                                     | Namespace                                   | Purpose                                                            |\n| ------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------ |\n| `getCached(key, persist)`                  | flat (`a-skeleton:` prefix, `v: 2`)         | Lookup for the legacy flat-shape cache (mirror cache-replay path). |\n| `setCached(key, value, persist)`           | flat                                        | Store a flat shape.                                                |\n| `clearCached(key?)`                        | flat **and** structural                     | Wipes both namespaces. Pass a `key` to drop one entry from each.   |\n| `getCachedStructural(key, persist)`        | structural (`a-skeleton:s:` prefix, `v: 3`) | Lookup for the tree-shape cache (Recipe 3).                        |\n| `setCachedStructural(key, value, persist)` | structural                                  | Store a tree shape.                                                |\n| `clearCachedStructural(key?)`              | structural only                             | Wipes only the structural namespace.                               |\n\nPersisted entries carry a schema version. Mismatched versions auto-purge on read — upgrades can't replay wrong geometry from a previous version's cache.\n\n---\n\n## Theming\n\n### CSS custom properties\n\nSet these on `:root` (or any ancestor) to retint every primitive:\n\n| Token                 | Used for                                                      |\n| --------------------- | ------------------------------------------------------------- |\n| `--ak-skel-base`      | Block fill (also used by text bars and variant primitives).   |\n| `--ak-skel-base-soft` | Secondary endpoint for variants that use a vertical gradient. |\n| `--ak-skel-highlight` | Shimmer / wave sweep colour.                                  |\n| `--ak-skel-radius`    | Default block border radius.                                  |\n| `--ak-skel-radius-sm` | Tighter radius for text bars, chips.                          |\n| `--ak-skel-radius-lg` | Wider radius for cards, images.                               |\n| `--ak-skel-duration`  | Animation cycle length.                                       |\n| `--ak-skel-pulse-min` | Opacity at the trough of the pulse cycle.                     |\n| `--ak-skel-ring`      | Subtle 1-px inset ring colour.                                |\n| `--ak-skel-icon`      | Placeholder icon colour (image / video variants).             |\n\nBackward-compat aliases for v1 token names (`--ak-skeleton-block`, `--ak-skeleton-shimmer`, `--ak-skeleton-radius`, `--ak-skeleton-duration`, `--ak-skeleton-pulse-opacity`, `--ak-skeleton-ring`) are kept — existing consumer overrides continue to work.\n\n```css\n/* Per-tenant override — applies to anything inside .tenant-acme */\n.tenant-acme {\n  --ak-skel-base: hsl(220 30% 18%);\n  --ak-skel-highlight: hsl(220 60% 60% / 0.35);\n  --ak-skel-radius: 0.5rem;\n  --ak-skel-duration: 2s;\n}\n```\n\n```vue\n<!-- Or inline, scoped to one tree -->\n<ASkeleton :loading style=\"--ak-skel-base: hotpink; --ak-skel-radius: 9999px;\">\n  <UserCard />\n</ASkeleton>\n```\n\n### Multi-tenant + dark mode\n\nLight / dark is driven entirely by the `--ak-skel-*` tokens. Three strategies, all built in:\n\n- **`.dark` class scope** — apply `.dark` to any ancestor (Tailwind / shadcn / `nuxt-color-mode` convention). The package ships `:where(.dark) { --ak-skel-base: hsl(220 13% 22%) … }` so dark tokens kick in automatically.\n- **`@media (prefers-color-scheme: dark)`** — falls back to OS preference when no explicit `.dark` / `.light` class is on an ancestor.\n- **Explicit `.light` class** — wins back light tokens (consumer override).\n\nMulti-tenant CSS can override per scope by setting the variables under a wrapper class. Variant-specific overrides also work — e.g. `<ASkeletonImage style=\"--ak-skel-base: hsl(200 50% 90%)\">` retints one instance.\n\n---\n\n## Animations\n\n| Value     | What                                                               | Default? |\n| --------- | ------------------------------------------------------------------ | -------- |\n| `shimmer` | gradient sweep ::after, contained per block via `overflow: hidden` | yes      |\n| `pulse`   | opacity 1 → `--ak-skel-pulse-min` → 1 over `--ak-skel-duration`    | —        |\n| `wave`    | gradient via `background-position` (sliding)                       | —        |\n| `none`    | static blocks                                                      | —        |\n\n`prefers-reduced-motion: reduce` disables every animation automatically (`animation: none !important` + the shimmer pseudo-element drops to a static low-opacity overlay).\n\n---\n\n## Accessibility\n\n- Every wrapper / layer / variant root carries `role=\"status\"` while loading.\n- `aria-busy=\"true\"` mirrors the loading state.\n- `aria-live=\"polite\"` so screen readers announce the loading state without interrupting the user.\n- A visually-hidden `<span class=\"a-skel-sr-only\">Loading…</span>` ships with every variant primitive so the loading state is read out by screen readers.\n- Every emitted shimmer surface carries `aria-hidden=\"true\"` — the placeholder is decorative; the announcement is the wrapper's job.\n- Mirror-mode skeletons disable `user-select` and `pointer-events` on the slot tree so the placeholder can't be interacted with mid-load.\n- `prefers-reduced-motion` strips animation.\n\n---\n\n## Performance\n\nDesigned for components with hundreds of leaf elements — busy dashboards, long lists, dense forms.\n\n- **Walk budget** — every walker (`walkDom`, `walkStructural`, `captureSnapshot`) enforces `maxNodes` (defaults 500 / 500 / 800) and reports `truncated: true`. A 5 000-row table will not lock up the main thread. `<ASkeleton>` logs a one-time `console.warn` per `cacheKey` whenever a capture truncates so missing nodes surface during development.\n- **Min-size filter** — `minSize` (default 4 px) drops hairlines / spacer dots.\n- **One-layout reads** — `getBoundingClientRect()` + `getComputedStyle()` happen in a single top-down pass with no intervening writes. One layout up front, then cached values for the rest of the walk.\n- **Allocation-free render** — captured nodes carry frozen pre-computed styles. The render loop reads them directly with no per-node function calls.\n- **Debounced re-capture** — initial measurement via `requestAnimationFrame`; subsequent `ResizeObserver` callbacks debounced 150 ms so a drag-resize doesn't trigger a re-walk per frame.\n- **CSS containment** — `.a-skeleton[data-loading]` uses `overflow: clip` + `contain: paint`, so shadows / filters / transforms can't bleed outside the box.\n- **Composited shimmer** — only `transform: translateX(...)` changes each frame on the shimmer pseudo-element, with `will-change: transform` lifting blocks to their own compositor layer up front.\n\n---\n\n## SSR\n\n- **`mode=\"mirror\"`** is fully SSR-safe — the walker runs on vnodes, no `window` access required.\n- **`mode=\"clone\"`** is client-side only (needs `getComputedStyle()` + a real DOM). The wrapper renders the slot's normal markup on the server; the snapshot + replay kick in on hydration.\n- **`useSkeleton()`** runs in `onMounted` and bails out cleanly when `window` is undefined.\n\nIf you need a server-rendered placeholder before hydration finishes, use `mode=\"mirror\"` or hand-craft with `<ASkeletonBlock>` / variant primitives.\n\n---\n\n## TypeScript\n\nImport the public types from the main entry:\n\n```ts\nimport type {\n  ASkeletonProps,\n  ASkeletonSlots,\n  ASkeletonLayerProps,\n  ASkeletonBlockProps,\n  CachedShape,\n  ShapeNode,\n  ShapeNodeType,\n  StructuralShape,\n  StructuralNode,\n  ContainerNode,\n  LeafNode,\n  LeafKind,\n  CaptureSnapshot,\n  CapturedNode,\n  UseSkeletonOptions,\n  UseSkeletonReturn,\n  ShapeProbeOptions,\n  CaptureStrategy,\n  WalkOptions,\n  WalkStructuralOptions,\n  CaptureOptions,\n  BuildOptions,\n  SkeletonAnimation,\n  SkeletonFallback,\n} from '@alikhalilll/a-skeleton';\n```\n\nSlot prop types are inferable in templates:\n\n```vue\n<ASkeleton #fallback>…</ASkeleton>\n```\n\n---\n\n## Browser support\n\nModern evergreen browsers — last two versions of Chrome, Edge, Firefox, Safari, and the matching mobile WebViews. Uses `Range.getClientRects()` (universal since 2017), `ResizeObserver` (universal since 2020), and CSS `contain: paint` (universal since 2022). No polyfills required.\n\nThe `overflow: clip` containment falls back to `overflow: hidden` on older browsers via the standard CSS cascade — no JavaScript fallback needed.\n\n---\n\n## Troubleshooting\n\n**The skeleton is blank.**\nMake sure your slot's tags are rendered unconditionally. If the slot is `<div v-if=\"data\">…</div>`, the walker sees one comment during loading and falls back to a generic shimmer. Gate **content** per leaf via `{{ data?.field }}`, not the entire template on `v-if`.\n\n**An empty `<h3>{{ data?.name }}</h3>` doesn't shimmer.**\nThis was the v1 walker's behaviour. v2+ classifies empty text-owner tags (`<h1>`-`<h6>`, `<p>`, `<span>`, …) as text bars rather than generic blocks, so the heading shimmers at its natural rendered height. Make sure you're on the latest version.\n\n**My `<button>` loses its background colour.**\nMirror mode keeps real `bg-*` classes — the engine detects an explicit background and skips the skeleton fallback. If your button has no `bg-*` class and no inline `background`, the walker assumes you want the default skeleton fill. Either add `bg-emerald-600` (or whatever) explicitly, or wrap the inner text in `<ASkeletonBlock>` and let the real `<button>` render around it.\n\n**The clone-mode replay drifts after a viewport resize.**\nClone mode captures absolute coordinates at the moment of snapshot. Resize-aware skeletons should use the structural strategy (`useSkeleton()` + `<ASkeletonLayer>`) — captured containers preserve their flex/grid layout and reflow with the parent.\n\n**The structural-mode skeleton looks like a single block, not a tree.**\nThe cache may be stale from an older version. `localStorage` entries with schema version `v: 2` (from `walkDom`-flat-model) won't load as `v: 3` structural shapes. Call `clearCachedStructural()` once after upgrading, or wait for the auto-purge on next read.\n\n**A decorative SVG / background image renders as a giant skeleton block.**\nMark it with `data-skeleton-ignore` so the walker treats it as invisible. The slot's chrome (gradients, decorative shapes) should always render verbatim; the skeleton is for **content**.\n\n**The structural skeleton's flex/grid layout breaks when rendered in a different CSS context.**\nContainers preserve both the original `class` (for utility CSS) and the resolved layout CSS as inline `style`. If your styles aren't present at the mount point, the inline `display: flex; flex-direction: column; gap: 16px;` fallback still drives the layout.\n\n**TypeScript can't find `StructuralShape` / `walkStructural`.**\nThese are exported from the main entry from v2.0+. Re-run `pnpm install`, restart your TS server, and confirm the package version in `node_modules/@alikhalilll/a-skeleton/package.json` matches what's in your `package.json`.\n\n---\n\n## License\n\n[MIT](./LICENSE) © alikhalilll\n","readmeFilename":"README.md"}