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

spinner-games

v0.0.4

Published

Tiny playable web-component games to use as loaders/spinners instead of the classic one.

Downloads

257

Readme

spinner-games

npm version npm downloads license

spinner-games preview — Pong, Breakout, Bubbles and Flappy mini-games

Tiny, playable web-component games you can drop in as loaders/spinners instead of the classic spinning circle. Built with Lit + TypeScript + Vite, shippable as a standalone library.

Status

Playable mini-games (<spinner-pong>, <spinner-breakout>, <spinner-bubbles>, <spinner-flappy>, <spinner-plinko>) are in place, usable as interactive loaders or self-running demos. |

USAGE

Install the package:

npm install spinner-games

The games are framework-agnostic custom elements. Import the package once to register the tags, then for Angular, React, Vue and Web Components:

import 'spinner-games'
<spinner-pong></spinner-pong>

Documentation

Per-environment install & usage guides live in docs/:

Framework wrappers

The same components ship with thin, typed wrappers for React, Vue, and Angular via subpath exports — no customElements boilerplate, full prop type-checking. Each subpath registers the underlying custom elements for you. Install the package alongside your framework (declared as optional peer dependencies):

npm install spinner-games

React (spinner-games/react)

Built on @lit/react, so props map to the element's reactive properties (not stringified attributes) and ref points at the element instance.

import { Pong, Flappy } from 'spinner-games/react'

export function Loader() {
  return (
    <>
      <Pong autoplay difficulty={0.6} />
      <Flappy obstacles="cave" />
    </>
  )
}

Vue (spinner-games/vue)

Typed defineComponent wrappers — use them straight from a SFC:

<script setup lang="ts">
import { Bubbles } from 'spinner-games/vue'
</script>

<template>
  <Bubbles autoplay :show-new-line="30" />
</template>

Prefer the raw tags instead? Tell Vue they're custom elements and the bundled GlobalComponents types will type them in templates:

// vite.config.ts
vue({ template: { compilerOptions: { isCustomElement: (t) => t.startsWith('spinner-') } } })

Angular (spinner-games/angular)

Standalone directives matching each tag — import the one(s) you need (or the SPINNER_GAMES_DIRECTIVES bundle). The directive makes the tag a known element (no CUSTOM_ELEMENTS_SCHEMA needed) and gives its inputs full type-checking, while rendering the real custom element with no extra DOM node:

import { Component } from '@angular/core'
import { SpinnerPong } from 'spinner-games/angular'

@Component({
  standalone: true,
  imports: [SpinnerPong],
  template: `<spinner-pong autoplay [difficulty]="0.6"></spinner-pong>`,
})
export class LoaderComponent {}

Shared conventions

Every game is responsive (fills its parent, height from a --spinner-*-aspect ratio) and shares the --spinner-color token. These opt-in attributes are common to the playable games (<spinner-pong>, <spinner-breakout>, <spinner-bubbles>, <spinner-flappy>, <spinner-plinko>):

| Attribute | Default | What it does | | ---------------------- | ------- | ---------------------------------------------------------------------------- | | autoplay | off | Demo mode: the game plays itself forever and user input is disabled. | | show-background | on | Paint the panel background behind the game (a faint --spinner-color tint, overridable via --spinner-bg). Set it off to drop the background so the game floats transparently over whatever is behind it. | | track-outside | off | Keep following the mouse even after the pointer leaves the canvas (pointer-steered games only — <spinner-flappy> flaps and ignores it). | | allowOutsideControls | off | Extend the game's pointer controls past the canvas edge — a superset of track-outside. The mouse keeps steering off the element and a click outside still acts: it fires a shot in <spinner-bubbles> and flaps in <spinner-flappy> (the only game where it adds a control without track-outside). <spinner-pong>/<spinner-breakout> have no in-game click, so for them it just extends steering. |

<!-- a real game you play -->
<spinner-breakout></spinner-breakout>

<!-- a self-running demo/loader (not interactive) -->
<spinner-breakout autoplay></spinner-breakout>
<spinner-pong autoplay></spinner-pong>

Color

Color isn't an attribute or prop — every game reads the --spinner-color CSS custom property. Set it inline, in a stylesheet, or on any ancestor (it cascades), and it works identically in HTML, React, Vue and Angular:

<!-- inline, on a single instance -->
<spinner-pong autoplay style="--spinner-color: #4ade80"></spinner-pong>
/* in a stylesheet — recolors every game on the page */
spinner-pong {
  --spinner-color: #4ade80; /* green */
}

/* …or set it once on a parent and it cascades into every game inside */
.loaders {
  --spinner-color: #e879f9;
}

The panel background is a faint tint of this color; override it on its own with --spinner-bg. See each framework's guide in docs/ for idiomatic snippets (e.g. React needs a React.CSSProperties cast for the inline form).

<spinner-bubbles> also takes showNewLine="<seconds>" — every that-many seconds a new row of bubbles drops in from the top and the stack slides down, ramping up the difficulty (0, the default, disables it):

<spinner-bubbles showNewLine="30"></spinner-bubbles>

<spinner-flappy> takes obstacles to choose what the ball threads through — walls (the default: classic paired pipes with a gap) or cave (an irregular rocky ceiling and floor with a winding passage between them). It works in both the playable and autoplay modes:

<spinner-flappy obstacles="cave"></spinner-flappy>
<spinner-flappy autoplay obstacles="cave"></spinner-flappy>

<spinner-plinko> is a Plinko / Galton board: a ball drops from the top, bounces through a staggered grid of pegs and lands in a scoring slot. The default 7-slot board subtracts 1 · 10 · 50 · 100 · 50 · 10 · 1 (biggest in the center). You start at startScore (default 500) and burn it down: aim with the mouse and click (or press Space) to drop each ball, and reach 0 within your balls (default 10) to win — a hit fires a confetti burst; run out of balls first and the run is over. Tune it with rows (peg rows), slots (scoring bins), balls (drops you get) and startScore (where the countdown begins):

<spinner-plinko rows="10" slots="9" balls="12" start-score="800"></spinner-plinko>
<spinner-plinko autoplay></spinner-plinko>

Contributions

npm install
npm run dev        # open the playground (Vite dev server)

Scripts

| Script | What it does | | --------------------- | ---------------------------------------------------------- | | npm run dev | Dev server + playground (index.html) | | npm run build | Build everything: core + React/Vue + Angular into dist/ | | npm run build:lib | Build core web components + React/Vue wrappers (Vite + tsc) | | npm run build:angular | Build the Angular wrappers (ng-packagr, partial-Ivy) | | npm run preview | Preview the production build | | npm run typecheck | Type-check only, no emit

Adding a game

Each game is a self-registering custom element:

  1. Create src/components/spinner-<name>.ts extending LitElement.
  2. Register it with @customElement('spinner-<name>').
  3. Re-export it from src/index.ts.

Project structure

index.html                    # dev playground
src/                          # the core web components (the published `spinner-games`)
  index.ts                    # library entry — re-exports all components
  components/
    spinner-pong.ts           # self-playing Pong loader
    spinner-breakout.ts       # self-playing Arkanoid/Breakout loader
    spinner-bubbles.ts        # self-playing Puzzle Bobble / bubble-shooter loader
    spinner-flappy.ts         # self-playing Flappy Bird loader (a ball for a bird)
    spinner-plinko.ts         # self-playing Plinko / Galton board loader
wrappers/                     # framework wrappers — each imports the core as `spinner-games`
  react/index.ts              #   React wrappers (subpath: spinner-games/react)
  vue/index.ts                #   Vue wrappers (subpath: spinner-games/vue)
  angular/                    #   Angular wrappers — separate ng-packagr build
    src/                      #     standalone directives + public-api
    ng-package.json           #     (subpath: spinner-games/angular)
    tsconfig.lib.json
vite.config.ts                # core build (src → dist/index.*)
vite.config.wrappers.ts       # React/Vue build (wrappers → dist/react, dist/vue)