{"_id":"@braedonsaunders/appkit-avatars","_rev":"2-860899e98373cc0037bfa796a5cb834c","name":"@braedonsaunders/appkit-avatars","dist-tags":{"latest":"2.0.0"},"versions":{"1.0.0":{"name":"@braedonsaunders/appkit-avatars","version":"1.0.0","keywords":["appkit","application-framework","avatars","image-generation","typescript"],"author":{"name":"Braedon Saunders"},"license":"AGPL-3.0-or-later","_id":"@braedonsaunders/appkit-avatars@1.0.0","maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/avatars#readme","bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"dist":{"shasum":"1e5663239efb78c29faf3d98449c7f0bb7977e9e","tarball":"https://registry.npmjs.org/@braedonsaunders/appkit-avatars/-/appkit-avatars-1.0.0.tgz","fileCount":56,"integrity":"sha512-PTdiGD6XWOoaJYNurnSDt1Mdid+Z0fH27NOulATNUd9mBGX4Ju9F7adudBEVZe1uQrS5annBDSWpJgyIMe8L/g==","signatures":[{"sig":"MEYCIQC74DQEkFZ4J82iyl6k4ASOu/xEg4Vi3WetvjlLoFstsAIhAO1JM7pEFQ1QWd3x1vQI10avSeKMFvV6m46o9++Wq6e/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@braedonsaunders%2fappkit-avatars@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":307242},"main":"./index.js","type":"module","_from":"file:braedonsaunders-appkit-avatars-1.0.0.tgz","types":"./index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"},"./react":{"types":"./react.d.ts","import":"./react.js","default":"./react.js"},"./styles.css":"./styles.css","./composition":{"types":"./composition.d.ts","import":"./composition.js","default":"./composition.js"},"./package.json":"./package.json"},"_npmUser":{"name":"braedonsaunders","email":"bsaunders@rassaun.com"},"_resolved":"/tmp/f7fbfe2062641bfa4908b03518ce9a86/braedonsaunders-appkit-avatars-1.0.0.tgz","_integrity":"sha512-PTdiGD6XWOoaJYNurnSDt1Mdid+Z0fH27NOulATNUd9mBGX4Ju9F7adudBEVZe1uQrS5annBDSWpJgyIMe8L/g==","repository":{"url":"git+https://github.com/braedonsaunders/appkit.git","type":"git","directory":"packages/avatars"},"_npmVersion":"10.9.8","description":"Avatar parts library, composition model, and the composer — one full-body figure per subject, portraits derived by head viewport; plus AI image generation through the AI SDK provider layer.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"ai":"^6.0.224","@ai-sdk/google":"^3.0.91","@ai-sdk/openai":"^3.0.84"},"_hasShrinkwrap":false,"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","lucide-react":"^1.24.0","framer-motion":"^12.34.0","@braedonsaunders/appkit-ui":"^0.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true},"lucide-react":{"optional":true},"framer-motion":{"optional":true},"@braedonsaunders/appkit-ui":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/appkit-avatars_1.0.0_1787007443295_0.5911335733824696","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@braedonsaunders/appkit-avatars","version":"2.0.0","description":"Avatar parts library, composition model, and the composer — one full-body figure per subject, portraits derived by head viewport; plus AI image generation through the AI SDK provider layer.","license":"AGPL-3.0-or-later","type":"module","exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"},"./composition":{"types":"./composition.d.ts","import":"./composition.js","default":"./composition.js"},"./react":{"types":"./react.d.ts","import":"./react.js","default":"./react.js"},"./styles.css":"./styles.css","./package.json":"./package.json"},"main":"./index.js","types":"./index.d.ts","peerDependencies":{"@braedonsaunders/appkit-ui":"^0.3.0","framer-motion":"^12.34.0","lucide-react":"^1.24.0","react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"@braedonsaunders/appkit-ui":{"optional":true},"framer-motion":{"optional":true},"lucide-react":{"optional":true},"react":{"optional":true},"react-dom":{"optional":true}},"author":{"name":"Braedon Saunders"},"repository":{"type":"git","url":"git+https://github.com/braedonsaunders/appkit.git","directory":"packages/avatars"},"homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/avatars#readme","bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"engines":{"node":">=22"},"keywords":["appkit","application-framework","avatars","image-generation","typescript"],"dependencies":{"@ai-sdk/google":"^3.0.91","@ai-sdk/openai":"^3.0.84","ai":"^6.0.224"},"_id":"@braedonsaunders/appkit-avatars@2.0.0","_integrity":"sha512-Nqi8l6WBrQ2yNbtKwAvxzrtY+zs4w1HZct7YCF60gEIdzts0rLX6boiGguwahwnNgjEYKURkpwFVR/UV8gZ4Lw==","_resolved":"/tmp/ffd8fd438c2c6a65554086f6c3d9e5c6/braedonsaunders-appkit-avatars-2.0.0.tgz","_from":"file:braedonsaunders-appkit-avatars-2.0.0.tgz","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-Nqi8l6WBrQ2yNbtKwAvxzrtY+zs4w1HZct7YCF60gEIdzts0rLX6boiGguwahwnNgjEYKURkpwFVR/UV8gZ4Lw==","shasum":"571b3d1e76a2983008a806a7fa00a16ac29e4ff6","tarball":"https://registry.npmjs.org/@braedonsaunders/appkit-avatars/-/appkit-avatars-2.0.0.tgz","fileCount":56,"unpackedSize":302485,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@braedonsaunders%2fappkit-avatars@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC2mRNTGB5w5K4g/tPFdLYWpG7qyIj+V4JU05Wu/TzsEgIhALTtRjNa8nHudUZz1puIFi7hWjfRzr4LpdK32PbbKO7P"}]},"_npmUser":{"name":"braedonsaunders","email":"bsaunders@rassaun.com"},"directories":{},"maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/appkit-avatars_2.0.0_1789182633523_0.26358570005081816"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-17T22:57:22.996Z","modified":"2026-09-12T03:10:34.000Z","1.0.0":"2026-08-17T22:57:23.447Z","2.0.0":"2026-09-12T03:10:33.697Z"},"bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"author":{"name":"Braedon Saunders"},"license":"AGPL-3.0-or-later","homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/avatars#readme","keywords":["appkit","application-framework","avatars","image-generation","typescript"],"repository":{"type":"git","url":"git+https://github.com/braedonsaunders/appkit.git","directory":"packages/avatars"},"description":"Avatar parts library, composition model, and the composer — one full-body figure per subject, portraits derived by head viewport; plus AI image generation through the AI SDK provider layer.","maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"readme":"# @braedonsaunders/appkit-avatars\n\nA parts library, a composition model, and the composer that arranges them —\nplus AI image generation through the shared provider layer.\n\nThe doctrine is one image per subject. A person has exactly one **full-body\ncomposition**; a portrait is not a second image but a **viewport** on that\ncomposition. Nothing to keep in sync, nothing to regenerate when a hairstyle\nchanges.\n\n## Entry points\n\n| Import | Environment | Contents |\n| --- | --- | --- |\n| `@braedonsaunders/appkit-avatars` | server | Image generation (`generateImages`, `listImageModels`), prompt builders, and a re-export of the composition model. |\n| `@braedonsaunders/appkit-avatars/composition` | anywhere | The pure model: types, `sortLayers`, `resolvePartUrl`, `viewportTransform`, `DEFAULT_HEAD_VIEWPORT`. No provider dependencies — safe in a browser bundle. |\n| `@braedonsaunders/appkit-avatars/react` | client | `ComposedAvatar` (the renderer) and `AvatarComposer` (the editor). |\n\nImport composition helpers from `/composition` in client code. The root entry\npulls the AI SDK provider factories; types erase at compile time, but values do\nnot.\n\n## The coordinate model\n\nOne stage, **512 × 768 canvas units**, origin top-left, +x right and +y down.\nPortrait orientation, because the composition is a standing figure.\n\nA category declares a `frame` (the part's natural box at `scale: 1`) and a\n`defaultTransform`. A placement stores `{ x, y, scale, rotation }`: `x`/`y` are\nthe top-left of that frame, `scale` multiplies it, and `rotation` is degrees\nclockwise about the frame's centre.\n\n```ts\nbox = {\n  left:   transform.x,\n  top:    transform.y,\n  width:  category.frame.width  * transform.scale,\n  height: category.frame.height * transform.scale,\n}\n```\n\nNothing stored depends on display size — the renderer and the editor both scale\ncanvas units by a single factor.\n\n## Head framing\n\n`composition.headViewport` is a rectangle in the same canvas units.\n`viewportTransform(viewport, frame)` returns the scale and translation that make\nthat rectangle fill a frame, and `ComposedAvatar variant=\"head\"` applies it. The\ncomposer's **Head framing** tab drags and resizes the rectangle directly, with\neverything outside it dimmed.\n\n```tsx\n<ComposedAvatar composition={c} parts={parts} categories={categories}\n  variant=\"head\" size={32} rounded />          {/* a directory row       */}\n<ComposedAvatar … variant=\"head\" size={96} animate=\"talking\" />  {/* a call */}\n<ComposedAvatar … variant=\"full\" size={170} animate=\"idle\" />    {/* a scene */}\n```\n\n## Why DOM layers, not canvas\n\nThe renderer paints absolutely positioned `<img>` elements inside a scaled\nstage rather than compositing to a `<canvas>`. Three things follow:\n\n- **Per-layer motion.** `animate=\"talking\"` works the mouth layer independently\n  of the figure — impossible once the layers are flattened.\n- **Sharp at any size.** The browser rasterizes the source art at the size\n  actually shown, so one composition serves a 24px row and a 400px stage.\n- **One code path.** What the composer arranges is literally what ships; there\n  is no export step that can drift from the editor.\n\nBoth `animate` modes honour `prefers-reduced-motion` and fall still.\n\n## Generating parts\n\n`buildPartPrompt` carries the art direction that keeps a library coherent —\nisolated single object, transparent background, front-facing, flat colours —\nwith a matching negative prompt. Parts **must** be alpha-cut: they stack.\n\n```ts\nconst { prompt } = buildPartPrompt({\n  description: 'a short curly hairstyle',\n  categoryLabel: 'Hair',\n  promptAddition: category.promptAddition,\n})\nconst { images } = await generateImages(aiConfig, { prompt, model, count: 4 })\n```\n\n## Application boundaries\n\nThe package does not own unlock or rarity policy, storage, or API routes. It\nuses a 512×768 portrait stage, composable DOM layers, centre-based rotation, a\nsingle aspect-preserving scale, and a head viewport stored with the composition.\n","readmeFilename":"README.md"}