npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@braedonsaunders/appkit-avatars

v2.0.0

Published

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.

Downloads

164

Readme

@braedonsaunders/appkit-avatars

A parts library, a composition model, and the composer that arranges them — plus AI image generation through the shared provider layer.

The doctrine is one image per subject. A person has exactly one full-body composition; a portrait is not a second image but a viewport on that composition. Nothing to keep in sync, nothing to regenerate when a hairstyle changes.

Entry points

| Import | Environment | Contents | | --- | --- | --- | | @braedonsaunders/appkit-avatars | server | Image generation (generateImages, listImageModels), prompt builders, and a re-export of the composition model. | | @braedonsaunders/appkit-avatars/composition | anywhere | The pure model: types, sortLayers, resolvePartUrl, viewportTransform, DEFAULT_HEAD_VIEWPORT. No provider dependencies — safe in a browser bundle. | | @braedonsaunders/appkit-avatars/react | client | ComposedAvatar (the renderer) and AvatarComposer (the editor). |

Import composition helpers from /composition in client code. The root entry pulls the AI SDK provider factories; types erase at compile time, but values do not.

The coordinate model

One stage, 512 × 768 canvas units, origin top-left, +x right and +y down. Portrait orientation, because the composition is a standing figure.

A category declares a frame (the part's natural box at scale: 1) and a defaultTransform. A placement stores { x, y, scale, rotation }: x/y are the top-left of that frame, scale multiplies it, and rotation is degrees clockwise about the frame's centre.

box = {
  left:   transform.x,
  top:    transform.y,
  width:  category.frame.width  * transform.scale,
  height: category.frame.height * transform.scale,
}

Nothing stored depends on display size — the renderer and the editor both scale canvas units by a single factor.

Head framing

composition.headViewport is a rectangle in the same canvas units. viewportTransform(viewport, frame) returns the scale and translation that make that rectangle fill a frame, and ComposedAvatar variant="head" applies it. The composer's Head framing tab drags and resizes the rectangle directly, with everything outside it dimmed.

<ComposedAvatar composition={c} parts={parts} categories={categories}
  variant="head" size={32} rounded />          {/* a directory row       */}
<ComposedAvatar … variant="head" size={96} animate="talking" />  {/* a call */}
<ComposedAvatar … variant="full" size={170} animate="idle" />    {/* a scene */}

Why DOM layers, not canvas

The renderer paints absolutely positioned <img> elements inside a scaled stage rather than compositing to a <canvas>. Three things follow:

  • Per-layer motion. animate="talking" works the mouth layer independently of the figure — impossible once the layers are flattened.
  • Sharp at any size. The browser rasterizes the source art at the size actually shown, so one composition serves a 24px row and a 400px stage.
  • One code path. What the composer arranges is literally what ships; there is no export step that can drift from the editor.

Both animate modes honour prefers-reduced-motion and fall still.

Generating parts

buildPartPrompt carries the art direction that keeps a library coherent — isolated single object, transparent background, front-facing, flat colours — with a matching negative prompt. Parts must be alpha-cut: they stack.

const { prompt } = buildPartPrompt({
  description: 'a short curly hairstyle',
  categoryLabel: 'Hair',
  promptAddition: category.promptAddition,
})
const { images } = await generateImages(aiConfig, { prompt, model, count: 4 })

Application boundaries

The package does not own unlock or rarity policy, storage, or API routes. It uses a 512×768 portrait stage, composable DOM layers, centre-based rotation, a single aspect-preserving scale, and a head viewport stored with the composition.