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

invadrs

v1.3.1

Published

Deterministic space-invaders-style pixel avatars from any string.

Readme

invadrs

Deterministic space-invaders-style pixel avatars from any string. Zero dependencies. Same id in, same avatar out, forever.

🕹 Live demo & Storybook →

invadrs sample avatars — the invadr() and spawn() primitives, shown across every built-in palette

import { invadr, spawn, dataUri } from "invadrs";

invadr("matt");                       // hand-drawn creature, SVG string
spawn("matt");                        // procedural creature, SVG string
invadr("matt", { palette: "sunset", size: 32 });
dataUri(invadr("matt"));              // data:image/svg+xml,...

Primitives

  • invadr(id, options?) — one of 16 hand-drawn creatures.
  • spawn(id, options?) — a unique procedural symmetric creature.

Options

{ size?, palette?, padding?, background?, title?, resolution?, sprite?, color?, salt?, accent?, tint?, pixelRatio? } (resolution is spawn-only and sprite is invadr-only; when size is omitted no width/height is set, so host CSS controls the size.)

color picks a palette index for the body. salt shifts every avatar (empty means none). accent fills the eyes and mouth with a second color. tint adds a faint background in a palette color (a background wins).

pixelRatio with size puts every cell edge on a whole device pixel. At a size that does not divide the grid evenly (20px is 13 cells of about 1.5px), Safari draws each cell over every pixel it touches and fills in the eyes and gaps; snapped, it matches Chrome. Pass window.devicePixelRatio. The React components snap on their own, measuring the size as laid out, so one that mounts inside a scaling animation (a menu opening) still snaps to its final size.

A distinct avatar per person

Two ids can land on the same creature and color. For a known set of people (a team, a chat room), distinctAvatars hands each id its own pair:

import { distinctAvatars, invadr } from "invadrs";

const avatars = distinctAvatars(["ana", "ben", "cy"], { palette: "neon" });
invadr("ben", { palette: "neon", ...avatars.get("ben") });

Pass the same palette and salt you render with. Each id keeps its own pair unless an earlier id holds it, so appending a person never changes anyone before them. A 6-color palette gives 96 distinct pairs; past that, pairs repeat.

Each pick also carries fill, the exact color the avatar is drawn with (a var(--...) string under css-vars), so the rest of your UI can match it: a cursor, a border, a "Ben is here" dot.

const { fill } = avatars.get("ben")!;
cursor.style.borderColor = fill;

By default only the pair is unique, so two people can share a color on different creatures. When color alone has to tell people apart, pass unique: "both": the first min(16, palette size) people each get their own creature and their own color, and anyone past that falls back to distinct pairs.

const avatars = distinctAvatars(memberIds, { palette, unique: "both" });

Picks follow list order, so removing or reordering people can move the people after them. To keep assignments fixed, keep the list in a stable order (say, by join time) or store each person's pick when they join.

Palettes & theming

Built-ins, each its own colour mood: tokyoNight (default, balanced rainbow), neon (electric, dark-bg), sunset (warm), ocean (cool), forest (greens/earth), mono (grayscale). Pass a name, your own string[], a full { colors, background } object, or "css-vars" to emit var(--accent)… fills that follow your theme.

import { palettes } from "invadrs";
const brand = { colors: [...palettes.tokyoNight.colors, "#ff00aa"] };
invadr("matt", { palette: brand });

React

import { Invadr, Spawn, InvadrsProvider } from "invadrs/react";

<Invadr id="matt" palette="css-vars" />   {/* hand-drawn creature */}
<Spawn id="matt" />                         {/* procedural creature */}

{/* app-wide defaults; explicit props on a component win */}
<InvadrsProvider palette="tokyoNight" size={20}>…</InvadrsProvider>

The components measure their drawn size and the screen's pixel ratio after mount and snap the cells to device pixels, so a CSS-sized avatar stays sharp with its eyes and gaps in every browser. A server render is unsnapped until it hydrates.

Stability contract

These are frozen:

  • the hash (FNV-1a finished with murmur3's fmix32)
  • the salt format (salt + "\u0000" + id; an empty salt means none)
  • the seed bit ranges (creature % 16, color >>> 4, accent >>> 12, tint >>> 20)
  • the accent rule (picked among the colors other than the body color; drawn in enclosed holes, found by a 4-way flood fill from the grid edge)
  • the tint opacity (0.18)
  • the built-in creatures (their art and order)
  • the built-in palette colors and their order
  • the procedural generator
  • the css-vars color order
  • the distinctAvatars assignment order

Changing any of them alters existing avatars and is only ever done in a major release.

Upgrading from 0.x

1.0 changes the hash, so every avatar changes. Replace distinctSprites by spreading the whole pick: const avatars = distinctAvatars(ids), then invadr(id, { ...avatars.get(id) }). Pass the same palette and salt you render with. The pick's .sprite alone is not equivalent, because only the (creature, color) pair is distinct. Pin 0.3.x to keep the old avatars.