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

@agencecinq/snake

v1.0.0

Published

Canvas Snake as a lightweight Web Component.

Readme

@agencecinq/snake

Canvas Snake as a lightweight Web Component.

<cinq-snake> runs the game loop and draws square cells into a consumer-owned <canvas>. Score, overlay, replay UI, layout, and colors stay in your page.

Installation

pnpm add @agencecinq/snake

Usage

Web Component (<cinq-snake>)

<cinq-snake cols="21" rows="15">
  <p>Score <span data-score aria-live="polite">0</span></p>
  <div class="snake-stage">
    <canvas tabindex="0" aria-label="Snake. Arrow keys to move."></canvas>
    <div data-overlay hidden>
      <p data-status>Game over</p>
      <button type="button" data-replay>Play again</button>
    </div>
  </div>
</cinq-snake>
import "@agencecinq/snake";

Importing @agencecinq/snake registers the Web Component automatically. No manual init() call required.

HTML is the source of truth. The component will not auto-set role, auto-migrate attributes, or warn about missing labels. Use an a11y linter (axe-core, Lighthouse) to catch invalid markup.

The [data-score] span, overlay, and replay button are yours. The host does not query them or write to them.

Required markup

| Attribute / element | Required | Role | | ------------------- | -------- | ---- | | <cinq-snake> | Yes | Host. Observes cols and rows. | | <canvas> | Yes | Playfield. One canvas per host. Give it tabindex="0" so arrow keys reach it. |

Layout and color

Size the canvas yourself. Use width: 100% and aspect-ratio: 21 / 15 (or cols / rows) for square tiles. After the canvas box changes, call sync() (a ResizeObserver on the canvas is enough).

Ink follows the canvas CSS color (inherited). background-color and any frame (border, outline, etc.) are yours. The component clears the bitmap each frame so that fill shows through. If CSS color is missing, the package falls back to #2a2a2a.

The component does not ship CSS.

cinq-snake {
  display: block;
}

cinq-snake canvas {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 21 / 15;
  touch-action: none;
  image-rendering: pixelated;
  background-color: #c5d0b8;
  color: #2a2a2a;
}

cinq-snake canvas:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

Attributes

| Attribute | Default | Description | | --------- | ------- | ----------- | | cols | 21 | Horizontal cell count. Observed. Resets the board when it changes. | | rows | 15 | Vertical cell count. Observed. Resets the board when it changes. |

Changing cols or rows also dispatches snake:replay.

API

const $host = document.querySelector("cinq-snake");

$host.init();
$host.sync();
$host.replay();
$host.destroy();
$host.score;
$host.cols;
$host.rows;
$host.$canvas;

Call destroy() before init() if the host is already bound and you mutated the light DOM.

Keyboard

| Key | Function | | --- | --- | | Arrow keys | Turn. 180-degree reverses are ignored. The first arrow starts the game. | | Enter or Space | After a collision, start again (when the canvas still holds focus). |

On a touch screen, swipe on the board to turn.

Events

Dispatched on the host <cinq-snake> (bubble, not cancelable). Prefer constants from @agencecinq/utils:

| Event | Constant | Detail | When | | ----- | -------- | ------ | ---- | | snake:eat | SNAKE_EAT | { score } | After food | | snake:over | SNAKE_OVER | { score } | After a wall or self hit | | snake:replay | SNAKE_REPLAY | { score } | After replay(), or after cols / rows change |

import { EVENTS } from "@agencecinq/utils";

const $host = document.querySelector("cinq-snake");
const $score = $host.querySelector("[data-score]");
const $overlay = $host.querySelector("[data-overlay]");
const $replay = $host.querySelector("[data-replay]");

$host.addEventListener(EVENTS.SNAKE_EAT, (event) => {
  $score.textContent = String(event.detail.score);
});

$host.addEventListener(EVENTS.SNAKE_OVER, () => {
  $overlay.hidden = false;
  $replay.focus();
});

$host.addEventListener(EVENTS.SNAKE_REPLAY, (event) => {
  $overlay.hidden = true;
  $score.textContent = String(event.detail.score);
});

$replay.addEventListener("click", () => {
  $host.replay();
});

Build setup

pnpm -C packages/snake build

Acknowledgments

See the interactive docs for live examples.