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

blit386

v1.4.0

Published

A palette-first WebGPU retro engine for TypeScript, inspired by RetroBlit.

Downloads

820

Readme

BLIT386

CI npm version License: ISC WebGPU

A palette-first retro engine for the web. You draw with numbered colors instead of RGBA pixels – the same trick that made VGA games shimmer – and a modern GPU does the rest. Roll the palette and water flows, fire rises, the sky drifts at dusk, all without redrawing a single pixel. WebGPU when your browser has it, an automatic Canvas 2D fallback when it does not.

It is small, it is fast, and it is built to feel like a toy. That is the whole point.

BLIT386 logo

Quick overview

import { bootstrap, BT, Color32, Rect2i, Vector2i } from 'blit386';

class Game {
  // The box position, in pixels. update() changes it; render() only reads it.
  x = 140;
  speed = 1;

  // init() runs once at startup. Set up your colors and load things here.
  // Slot 0 is always transparent, so we start numbering at 1.
  // Think of the palette as a numbered paint box.
  async init() {
    const palette = BT.paletteCreate(16); // room for 16 colors

    palette.set(1, new Color32(32, 0, 128)); // a deep blue background
    palette.set(2, new Color32(255, 220, 90)); // a warm yellow

    BT.paletteSet(palette); // make this the palette the engine draws with

    return true; // tell the engine setup went fine
  }

  // update() is the THINKING step: change the world here (move things, read
  // input, run physics), but never draw. It runs at a FIXED rate – targetFPS,
  // 60 times a second by default – no matter how fast the screen is. The engine
  // runs it as many times per frame as it needs to hold that pace, so your game
  // moves at the same speed on every machine.
  update() {
    this.x += this.speed; // slide the box sideways

    if (this.x < 0 || this.x > 280) {
      this.speed = -this.speed; // bounce off the screen edges
    }
  }

  // render() is the DRAWING step: only paint the world as it is right now,
  // never change state. It runs ONCE PER SCREEN REFRESH – way faster on a
  // high-refresh monitor, slower on a struggling machine – so it is not locked
  // to update().
  render() {
    // Draw with slot numbers, not colors.
    BT.clear(1); // fill the screen with slot 1
    BT.drawRectFill(new Rect2i(this.x, 100, 40, 40), 2); // the moving box
    BT.systemPrint(new Vector2i(108, 160), 2, 'HELLO BLIT386'); // built-in font; bitmap fonts work too
  }
}

bootstrap(Game); // hand the class to the engine and start the loop

That is a whole game on a 320×240 screen: update() thinks, render() draws, and the box slides back and forth at the same speed on every machine – because update() ticks at a fixed rate while render() just follows your screen. No config, no scene graph, no ceremony.

Load a sprite and draw it

import { bootstrap, BT, Color32, Palette, SpriteSheet, Vector2i } from 'blit386';

class Game {
  async init() {
    this.palette = new Palette(256);
    this.palette.set(1, new Color32(20, 20, 40)); // background

    // loadIndexed() scans the PNG, drops its colors into the palette starting
    // at slot 10, and hands you back the sheet plus a rect for the whole image.
    this.hero = await SpriteSheet.loadIndexed('/sprites/hero.png', this.palette, 10);

    BT.paletteSet(this.palette); // activate AFTER loadIndexed returns

    return true;
  }

  update() {}

  render() {
    BT.clear(1);
    BT.drawSprite(this.hero.sheet, this.hero.srcRect, new Vector2i(140, 100));
  }
}

bootstrap(Game);

Make an ocean out of one palette

This is the party trick. Build eight shades of blue, then tell the engine to rotate them. The pixels never change – only the paint-box labels shuffle – and the whole sea starts to move, exactly like it did in DeluxePaint.

import { bootstrap, BT, Color32, Rect2i } from 'blit386';

const OCEAN_START = 1; // eight blue slots live in 1..8
const OCEAN_END = 8;

class Game {
  async init() {
    const palette = BT.paletteCreate(16);

    // A gradient from deep navy to bright cyan across the eight slots.
    for (let i = 0; i < 8; i++) {
      const t = i / 7; // 0..1
      palette.set(OCEAN_START + i, new Color32(0, Math.floor(40 + t * 160), Math.floor(100 + t * 155)));
    }

    BT.paletteSet(palette);

    // Roll those eight slots forward, ~4 steps a second. The engine keeps
    // doing this every frame on its own. We never touch a pixel again.
    BT.paletteCycle(OCEAN_START, OCEAN_END, 4);

    return true;
  }

  update() {}

  render() {
    // Eight horizontal bands, one per ocean slot. As the palette rolls, the
    // colors slide down the screen like a calm, glittering sea.
    for (let i = 0; i < 8; i++) {
      BT.drawRectFill(new Rect2i(0, i * 30, 320, 30), OCEAN_START + i);
    }
  }
}

bootstrap(Game);

What makes it fun

  • Draw with numbers, not pixels: A 256-color paint box; every primitive and sprite is just a slot index.
  • Animate colors, not geometry: Cycle, fade, flash, and swap give you water, lava, and lightning for the cost of one tiny palette upload.
  • Retro palettes in the box: VGA, CGA, C64, Game Boy, PICO-8, and NES presets.
  • Recolor without redrawing: Palette offsets turn one sprite sheet into team colors, day and night, or power-up states – no duplicate textures.
  • CRT when you want it: A two-tier post-process chain with bundled CRT presets for that curved-glass glow.
  • Everything a tiny engine needs: Pointer, keyboard, and gamepad input, a fixed-timestep loop with render-time interpolation for smooth motion between ticks, bitmap fonts, a camera, and one-call PNG frame capture.
  • Sound that plays, not just routes: Fire off sound effects and crossfading music through a three-bus mixer (sfx, music, main) with volume, mute, and fades, synthesize blips and booms from scratch or reach for a built-in preset, and read live levels off the overlay's audio meters – all while tracking the browser's autoplay-gesture unlock honestly instead of pretending it doesn't exist.
  • Loading progress: BT.loadingAssetsCount tracks in-flight image and audio loads so you can drive a loading screen.
  • Mobile polish: wake lock, screen orientation lock and detection, and opt-in pointer and keyboard scroll capture.
  • Hot reload: the blit386/vite plugin swaps code and assets in place during development without a full page reload.

Get started

The fastest way – easy enough that a pigeon would skip its dinner to try it – is the scaffolder. It writes a ready-to-run Vite project, installs the engine, and drops in a starter game plus local docs.

npm create blit386@latest my-game
cd my-game
npm run dev

Works with npm, pnpm, yarn, or bun – it uses whichever you ran it with. Open the address it prints and edit src/game.js. See create-blit386 for the options and what lands in the project.

Add it to a project you already have

pnpm add blit386

bootstrap() looks for a canvas inside #canvas-container:

<div id="canvas-container"><canvas id="blit386-canvas"></canvas></div>
<script type="module" src="/src/main.js"></script>

You need an ESM bundler (Vite, esbuild, webpack, and friends) and Node >=22.18.0. The engine wants a WebGPU browser and quietly falls back to Canvas 2D when there is not one – see Browser support for the version details.

On Vite, add the blit386/vite plugin to vite.config.js for hot reload during development – most code and asset edits apply to the running game without a page reload (a hardware setting change or an unrecognized asset type still falls back to one). See the Hot Reload guide.

Demos

Play the hosted demos at demos.blit386.dev, or read the source in the blit386-demos repo – dozens of small, heavily commented examples from a single moving square up to a full Snake game.

Documentation

The full, typeset documentation lives at blit386.dev/docs. The Markdown sources are in docs/ – start with API: Core for bootstrap() and initialization. The rest of the important pages:

| Documentation | What it covers | | ---------------------------------------------------------- | ------------------------------------------------------ | | API: Core | bootstrap, init, default configuration | | API: Game Loop | tick timing, present FPS, Timer | | Game Loop Guide | render-time interpolation, smoothing motion | | API: Camera | global pixel offset, world-clamp helpers | | API: Core Types | Vector2i, Rect2i, Color32 | | API: Rendering | primitives, sprites, text, post-process, frame capture | | API: Palette | palette setup, presets, effects, serialization | | API: Assets | sprite sheets, bitmap fonts, loading progress | | API: Audio | buses, SFX voice pool, music, procedural synthesis | | API: Overlay | overlay configure flags and style | | Overlay Guide | engine HUD subsystem, toggle, custom rows, layout | | API: Browser Support | WebGPU matrix, wake lock, orientation, build toolchain | | Input Guide | pointer, keyboard, gamepad, scroll capture | | Hot Reload Guide | blit386/vite plugin, hot-swap, asset hot-replace | | Palette Guide | the palette-first workflow, offsets, and effects | | Audio Guide | loading, playing, and designing sound | | Bitmap Fonts | .btfont format, BMFont conversion | | Post-Process Effects | the effect chain, built-in effects, CRT presets | | Changelog | release history in Keep a Changelog style |

The full index – performance, testing, security, contributor guides – lives in Developer Experience.

Inspiration

BLIT386 owes its whole philosophy to RetroBlit by Martin Cietwierkowski (@daafu) – a retro pixel framework for Unity that throws out the scene graph and hands you a clean, low-level demo loop. BLIT386 brings that same feeling to the web with WebGPU: no frameworks, just sprites, primitives, fonts, and a palette.

Community

Made by

BLIT386 is built by Václav Vančura (@vancura) – one person, so far. I am not a player, I am an engine maker.

Want to help? The contributor workflow, scripts, and release process live in Developer Experience and CONTRIBUTING.md.

License

ISC.