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

@colon-dev/pivotx

v1.2.0

Published

Lightweight 2D game development library — Vanilla JS, TypeScript, and React

Readme

pIvotX

Lightweight 2D game development library. One package, three ways to use it.

🌐 Website · 🎮 Sample Games & Tutorials · 📖 Guide · 📦 npm · 🐙 GitHub

| Target | Import style | Build required? | |---|---|---| | Vanilla JS | <script src="cdn">window.PivotX | No | | TypeScript | import { Canvas } from '@colon-dev/pivotx' | Yes (your project) | | React | import { PivotCanvas } from '@colon-dev/pivotx/react' | Yes (your project) |


Install

npm install @colon-dev/pivotx

Or via CDN (no npm, no build step):

<!-- Minified — for production -->
<script src="https://cdn.jsdelivr.net/npm/@colon-dev/pivotx/dist/pivotx.umd.min.js"></script>

<!-- Unminified — for development -->
<script src="https://cdn.jsdelivr.net/npm/@colon-dev/pivotx/dist/pivotx.umd.js"></script>

Usage

Vanilla JS (CDN)

Drop one <script> tag in and everything is on window.PivotX.

<canvas id="game" width="600" height="400"></canvas>
<script src="https://cdn.jsdelivr.net/npm/@colon-dev/pivotx/dist/pivotx.umd.min.js"></script>
<script>
  var { Canvas, Circle, Rectangle, Line, Label, Point } = PivotX;

  var canvas = new Canvas("game");
  var W = canvas.getWidth();
  var H = canvas.getHeight();

  var ball = { x: W/2, y: H/2, r: 24, vx: 200, vy: 150 };

  canvas.startLoop(function(dt) {
    canvas.clear();

    var bg = new Rectangle(Point(0,0), W, H);
    bg.fillColor = "#1a1a2e";
    canvas.add(bg);

    ball.x += ball.vx * dt;   // dt = seconds since last frame
    ball.y += ball.vy * dt;
    if (ball.x < ball.r || ball.x > W - ball.r) ball.vx *= -1;
    if (ball.y < ball.r || ball.y > H - ball.r) ball.vy *= -1;

    var circle = new Circle(Point(ball.x, ball.y), ball.r);
    circle.fillColor   = "#e94560";
    circle.strokeColor = "white";
    circle.lineWidth   = 2;
    canvas.add(circle);
  });
</script>

TypeScript (ESM)

import { Canvas, Circle, Rectangle, Line, Label, Point } from '@colon-dev/pivotx';
import type { IPoint } from '@colon-dev/pivotx';

const canvas = new Canvas('game');
const W      = canvas.getWidth();
const H      = canvas.getHeight();

interface Ball { pos: IPoint; vel: IPoint; radius: number; }

const ball: Ball = {
  pos:    Point(W / 2, H / 2),
  vel:    Point(220, 160),
  radius: 24,
};

canvas.startLoop((dt: number) => {
  canvas.clear();

  ball.pos.x += ball.vel.x * dt;
  ball.pos.y += ball.vel.y * dt;
  if (ball.pos.x < ball.radius || ball.pos.x > W - ball.radius) ball.vel.x *= -1;
  if (ball.pos.y < ball.radius || ball.pos.y > H - ball.radius) ball.vel.y *= -1;

  const shape       = new Circle(ball.pos, ball.radius);
  shape.fillColor   = '#e94560';
  shape.strokeColor = 'white';
  canvas.add(shape);
});

TypeScript will catch wrong types at compile time:

circle.radius = "big";    // ❌ Error: Type 'string' is not assignable to type 'number'
new Canvas(42);           // ❌ Error: Argument of type 'number' is not assignable to 'string'

React — JSX components

import { PivotCanvas, PivotCircle, PivotRectangle, PivotLabel } from '@colon-dev/pivotx/react';

function MyScene() {
  return (
    <PivotCanvas width={600} height={400} background="#1a1a2e">
      <PivotCircle
        center={{ x: 300, y: 200 }}
        radius={60}
        fill="#e94560"
        stroke="white"
        lineWidth={3}
      />
      <PivotLabel
        text="Hello pIvotX"
        position={{ x: 300, y: 360 }}
        font="20px Arial"
        fill="white"
      />
    </PivotCanvas>
  );
}

React — Animated with useGameLoop

import { useState, useRef }          from 'react';
import { PivotCanvas, PivotCircle, useGameLoop } from '@colon-dev/pivotx/react';

function BouncingBall() {
  // useRef for mutable game state — doesn't cause extra re-renders
  const ball = useRef({ x: 300, y: 200, vx: 200, vy: 150 });
  // useState(0) is just a frame counter — triggers the re-render each frame
  const [, tick] = useState(0);

  useGameLoop((dt) => {
    const b = ball.current;
    b.x += b.vx * dt;
    b.y += b.vy * dt;
    if (b.x < 24 || b.x > 576) b.vx *= -1;
    if (b.y < 24 || b.y > 376) b.vy *= -1;
    tick(n => n + 1);  // trigger re-render so shape props update
  });

  return (
    <PivotCanvas width={600} height={400} background="#1a1a2e">
      <PivotCircle
        center={{ x: ball.current.x, y: ball.current.y }}
        radius={24}
        fill="#e94560"
      />
    </PivotCanvas>
  );
}

API Reference

Point(x, y)

Creates a plain { x, y } coordinate object. Used everywhere positions are needed.

const p = Point(100, 200);

Canvas

Wraps a <canvas> DOM element.

const canvas = new Canvas("myCanvasId");

| Method | Returns | Description | |---|---|---| | getWidth() | number | Canvas width in pixels | | getHeight() | number | Canvas height in pixels | | getCenter() | IPoint | Centre point of the canvas | | clear() | void | Erase everything — call at start of each frame | | add(shape) | void | Draw any IDrawable immediately | | startLoop(fn) | void | Start rAF loop, fn(dt) called each frame | | stopLoop() | void | Stop the running loop | | ctx | CanvasRenderingContext2D | Raw 2D context for advanced use |


Circle

const c = new Circle(Point(x, y), radius);

| Property | Type | Description | |---|---|---| | centerPoint | IPoint | Centre position | | radius | number | Radius in pixels | | fillColor | string \| null | CSS fill colour | | strokeColor | string \| null | CSS outline colour | | lineWidth | number | Outline thickness |


Rectangle

const r = new Rectangle(Point(x, y), width, height);

Point(x, y) is the top-left corner.

| Property | Type | Description | |---|---|---| | position | IPoint | Top-left corner | | width | number | Width in pixels | | height | number | Height in pixels | | fillColor | string \| null | CSS fill colour | | strokeColor | string \| null | CSS outline colour | | lineWidth | number | Outline thickness |


Line

const l = new Line(Point(x1, y1), Point(x2, y2));

| Property | Type | Description | |---|---|---| | startPoint | IPoint | Start coordinate | | endPoint | IPoint | End coordinate | | strokeColor | string | Line colour | | lineWidth | number | Line thickness |


Label

const l = new Label("text", Point(x, y), "20px Arial");

font is optional, defaults to "16px Arial".

| Property | Type | Default | Description | |---|---|---|---| | text | string | — | Text to display | | position | IPoint | — | Anchor point | | font | string | "16px Arial" | CSS font string | | fillColor | string | "#000" | Text colour | | textAlign | "left" \| "center" \| "right" | "center" | Horizontal anchor | | textBaseline | "top" \| "middle" \| "bottom" | "middle" | Vertical anchor |


AssetLoader

Static utility for preloading image assets before the game loop starts.

import { AssetLoader } from '@colon-dev/pivotx';

| Method | Returns | Description | |---|---|---| | AssetLoader.loadImage(src) | Promise<HTMLImageElement> | Load a single image from a URL | | AssetLoader.loadAssets(manifest) | Promise<Record<K, HTMLImageElement>> | Load multiple images in parallel |

// Single image
const heroImg = await AssetLoader.loadImage('/hero.png');

// Batch — keys become properties on the result
const assets = await AssetLoader.loadAssets({
  hero:       '/sprites/hero.png',
  background: '/bg/sky.png',
  tileset:    '/tiles/ground.png',
});
// assets.hero, assets.background, assets.tileset — all HTMLImageElement

GameImage

Draws a static image on the canvas. Accepts a pre-loaded HTMLImageElement or a URL string (auto-loads in background; draw() skips until ready).

import { GameImage, AssetLoader, Point } from '@colon-dev/pivotx';

// Recommended: pre-load first
const img  = await AssetLoader.loadImage('/hero.png');
const hero = new GameImage(Point(100, 50), img);
hero.width  = 64;
hero.height = 64;
canvas.add(hero);

// Auto-load shorthand (draws once loaded)
const bg = new GameImage(Point(0, 0), '/background.png');
canvas.add(bg);

| Property | Type | Default | Description | |---|---|---|---| | position | IPoint | — | Top-left draw position | | width | number \| null | null | Display width (null = natural) | | height | number \| null | null | Display height (null = natural) | | opacity | number | 1 | 0 (transparent) to 1 (opaque) | | rotation | number | 0 | Rotation in radians (around centre) | | pixelPerfect | boolean | false | Disable image smoothing for crisp pixel art |

| Method / Getter | Returns | Description | |---|---|---| | loaded | boolean | true once the image is ready to draw | | imageElement | HTMLImageElement | The underlying image element | | setSrc(url) | void | Change the source at runtime |


Sprite & SpriteSheet

Renders a single frame from a grid-based spritesheet.

import { Sprite, AssetLoader, Point } from '@colon-dev/pivotx';
import type { SpriteSheet } from '@colon-dev/pivotx';

const img   = await AssetLoader.loadImage('/hero-sheet.png');
const sheet = Sprite.createSheet(img, 32, 32);   // 32×32 frame size
const hero  = new Sprite(Point(100, 200), sheet);
hero.frame  = 0;    // which frame to show
hero.scale  = 2;    // 2× size
hero.flipX  = true; // mirror horizontally
canvas.add(hero);

SpriteSheet interface

| Property | Type | Description | |---|---|---| | image | HTMLImageElement | The spritesheet image | | frameWidth | number | Width of one frame | | frameHeight | number | Height of one frame | | columns | number | Frames per row | | totalFrames | number | Total usable frames |

Sprite class

| Property | Type | Default | Description | |---|---|---|---| | position | IPoint | — | Top-left draw position | | frame | number | 0 | Current frame index (wraps) | | scale | number | 1 | Scale multiplier | | flipX | boolean | false | Mirror horizontally | | flipY | boolean | false | Mirror vertically | | opacity | number | 1 | 0–1 opacity | | pixelPerfect | boolean | true | Disable image smoothing for crisp pixel art |

| Method / Getter | Returns | Description | |---|---|---| | Sprite.createSheet(img, fw, fh, total?) | SpriteSheet | Build a sheet from a loaded image | | drawWidth | number | frameWidth × scale | | drawHeight | number | frameHeight × scale | | sheet | SpriteSheet | The sprite's SpriteSheet |


SpriteAnimator & AnimationClip

Named animation clip controller for a Sprite. Register clips, play them, and call update(dt) every frame.

import { SpriteAnimator } from '@colon-dev/pivotx';
import type { AnimationClip } from '@colon-dev/pivotx';

const animator = new SpriteAnimator(heroSprite);
animator
  .addClip('idle', { frames: [0, 1, 2, 3],    fps: 6,  loop: true })
  .addClip('run',  { frames: [4, 5, 6, 7, 8], fps: 10, loop: true })
  .addClip('jump', { frames: [9, 10],          fps: 4,  loop: false });

animator.play('idle');

// In game loop:
canvas.startLoop((dt) => {
  canvas.clear();
  animator.update(dt);     // advance the frame
  canvas.add(heroSprite);  // draw current frame
});

AnimationClip interface

| Property | Type | Description | |---|---|---| | frames | number[] | Ordered frame indices from the SpriteSheet | | fps | number | Playback speed (frames per second) | | loop | boolean | Loop or stop on last frame |

SpriteAnimator class

| Method | Returns | Description | |---|---|---| | addClip(name, clip) | this | Register a clip (chainable) | | removeClip(name) | this | Remove a clip (chainable) | | hasClip(name) | boolean | Check if a clip exists | | play(name) | void | Switch to a clip (resets only if different) | | stop() | void | Pause playback on current frame | | update(dt) | void | Advance timer — call once per frame |

| Getter | Type | Description | |---|---|---| | currentClip | string | Name of the active clip | | isPlaying | boolean | Currently playing | | isFinished | boolean | Non-looping clip reached last frame | | currentIndex | number | Index within the clip's frames array |


Camera

2D viewport that translates and scales the canvas context. Draw world objects between begin() and end(). Anything drawn after end() (HUD, score) stays fixed on screen.

import { Camera } from '@colon-dev/pivotx';

const camera = new Camera(600, 400); // viewport size

canvas.startLoop((dt) => {
  canvas.clear();

  camera.follow(player.position, 0.08); // smooth follow
  camera.clamp(worldWidth, worldHeight); // don't scroll past edges
  camera.begin(canvas.ctx);

  // World objects — scroll with camera
  canvas.add(tilemap);
  canvas.add(playerSprite);

  camera.end(canvas.ctx);

  // HUD — fixed on screen
  canvas.add(scoreLabel);
});

| Property | Type | Default | Description | |---|---|---|---| | position | IPoint | {x:0, y:0} | Top-left of viewport in world coords | | zoom | number | 1 | Zoom level (2 = 2× zoom in) | | viewportWidth | number | — | Viewport width | | viewportHeight | number | — | Viewport height |

| Method | Returns | Description | |---|---|---| | follow(target, lerp?) | void | Centre on target. lerp 0.05–0.15 = smooth, 1 = instant | | clamp(worldW, worldH) | void | Prevent scrolling past world edges | | begin(ctx) | void | Apply camera transform (call before world drawing) | | end(ctx) | void | Restore screen space (call after world drawing) | | worldToScreen(p) | IPoint | Convert world position to screen coordinates | | screenToWorld(p) | IPoint | Convert screen position to world coordinates |


TiledBackground

Draws a repeating, scrollable background image with parallax support. Stack multiple instances for multi-layer parallax.

import { TiledBackground, AssetLoader } from '@colon-dev/pivotx';

const skyImg = await AssetLoader.loadImage('/bg/sky.png');
const sky    = new TiledBackground(skyImg, 600, 400);
sky.parallaxFactor = 0.3; // distant — scrolls slowly

canvas.startLoop((dt) => {
  canvas.clear();
  sky.scroll(100 * dt);   // scroll speed (parallax applied automatically)
  canvas.add(sky);
});

| Property | Type | Default | Description | |---|---|---|---| | scrollX | number | 0 | Horizontal offset | | scrollY | number | 0 | Vertical offset | | opacity | number | 1 | 0–1 opacity | | parallaxFactor | number | 1 | 1 = full speed, 0.3 = slow (distant) |

| Method | Returns | Description | |---|---|---| | scroll(dx, dy?) | void | Advance scroll offset (parallax applied) | | setViewport(w, h) | void | Update viewport size on resize |


Platform

A rectangular shape with AABB collision support and a oneWay flag for jump-through platforms.

import { Platform, Point, aabbOverlap } from '@colon-dev/pivotx';

const ground = new Platform(Point(0, 350), 600, 50);
ground.fillColor = '#4a7c59';
canvas.add(ground);

const ledge = new Platform(Point(200, 260), 120, 16);
ledge.oneWay = true; // jump-through from below

if (aabbOverlap(playerBounds, ground.bounds)) {
  // collision!
}

| Property | Type | Default | Description | |---|---|---|---| | position | IPoint | — | Top-left corner | | width | number | — | Width in pixels | | height | number | — | Height in pixels | | fillColor | CSSColor \| null | '#555' | Fill colour | | strokeColor | CSSColor \| null | null | Outline colour | | lineWidth | number | 0 | Outline thickness | | oneWay | boolean | false | Jump-through from below |

| Getter | Type | Description | |---|---|---| | bounds | AABB | AABB for collision functions |


Tilemap

Grid-based tile map. Renders tiles from a SpriteSheet and provides collision queries.

import { Tilemap, Sprite, AssetLoader, Point } from '@colon-dev/pivotx';

const tileImg  = await AssetLoader.loadImage('/tiles/ground.png');
const sheet    = Sprite.createSheet(tileImg, 16, 16);

const mapData = [
  [-1, -1, -1, -1, -1],   // -1 = empty/air
  [-1, -1, -1, -1, -1],
  [ 0,  1,  1,  1,  2],   // frame indices from sheet
  [ 3,  4,  4,  4,  5],
];

const tilemap = new Tilemap(sheet, mapData, 32); // 32px rendered tile size
tilemap.solidTiles = new Set([0, 1, 2, 3, 4, 5]);

// Collision check
if (tilemap.isSolidAt(player.x, player.y + 32)) {
  // standing on solid ground
}

// Region query for nearby solid tiles
const nearby = tilemap.getSolidTilesInRegion(playerAABB);

| Property | Type | Default | Description | |---|---|---|---| | solidTiles | Set<number> | new Set() | Frame indices considered solid | | pixelPerfect | boolean | true | Disable image smoothing for crisp pixel art |

| Method | Returns | Description | |---|---|---| | getTileAt(worldX, worldY) | number | Frame index at world position (-1 if empty/OOB) | | isSolidAt(worldX, worldY) | boolean | True if tile at position is in solidTiles | | setTile(col, row, frame) | void | Change a tile at runtime (breakable blocks, pickups) | | getTileBounds(col, row) | AABB | AABB for a specific tile cell | | getSolidTilesInRegion(region) | AABB[] | All solid tile AABBs overlapping a region |

| Getter | Type | Description | |---|---|---| | rows | number | Number of rows | | cols | number | Number of columns | | tileSize | number | Rendered tile size | | widthInPixels | number | Total map width | | heightInPixels | number | Total map height | | mapData | number[][] | Underlying map data |


Collision Functions

AABB collision detection utilities. Works with Platform.bounds, Tilemap.getTileBounds(), or any AABB object.

import { aabbOverlap, aabbOverlapDepth, createAABB } from '@colon-dev/pivotx';
import type { AABB } from '@colon-dev/pivotx';

AABB interface

interface AABB {
  left:   number;
  right:  number;
  top:    number;
  bottom: number;
}

createAABB(x, y, width, height)

Convenience helper to build an AABB from position + dimensions.

const playerBox = createAABB(player.x, player.y, 32, 32);

aabbOverlap(a, b)

Returns true if two AABBs overlap.

if (aabbOverlap(playerBox, platform.bounds)) {
  // collision!
}

aabbOverlapDepth(a, b)

Returns { x, y } overlap depth (always positive), or null if no overlap. Use the smaller axis for minimum translation.

const depth = aabbOverlapDepth(playerBox, platform.bounds);
if (depth) {
  if (depth.y < depth.x) {
    player.y -= depth.y; // resolve vertically
    player.vy = 0;
  } else {
    player.x -= depth.x; // resolve horizontally
  }
}

React Components

<PivotCanvas>

The root component. All shape components must be inside it.

| Prop | Type | Default | Description | |---|---|---|---| | width | number | 600 | Width in pixels | | height | number | 400 | Height in pixels | | background | string | transparent | CSS background | | ref | PivotCanvasHandle | — | Access .ctx, .element, .clear() |

<PivotCircle>, <PivotRectangle>, <PivotLine>, <PivotLabel>

All accept the same props as their class equivalents, using React naming: fillfillColor, strokestrokeColor, centercenterPoint, start/endstartPoint/endPoint.

<PivotImage>

Draws an image on the canvas.

| Prop | Type | Default | Description | |---|---|---|---| | src | string \| HTMLImageElement | — | URL or pre-loaded image | | position | IPoint | — | Top-left draw position | | width | number | natural | Display width | | height | number | natural | Display height | | opacity | number | 1 | 0–1 opacity | | rotation | number | 0 | Rotation in radians | | pixelPerfect | boolean | false | Disable image smoothing for crisp pixel art |

<PivotImage src="/hero.png" position={{ x: 100, y: 50 }} width={64} height={64} />

<PivotSprite>

Draws a single sprite frame.

| Prop | Type | Default | Description | |---|---|---|---| | position | IPoint | — | Top-left position | | sheet | SpriteSheet | — | SpriteSheet to draw from | | frame | number | — | Frame index | | scale | number | 1 | Scale multiplier | | flipX | boolean | false | Mirror horizontally | | flipY | boolean | false | Mirror vertically | | opacity | number | 1 | 0–1 opacity | | pixelPerfect | boolean | true | Disable image smoothing for crisp pixel art |

<PivotSprite position={{ x: 100, y: 200 }} sheet={heroSheet} frame={currentFrame} scale={2} />

<PivotPlatform>

Draws a rectangular platform.

| Prop | Type | Default | Description | |---|---|---|---| | position | IPoint | — | Top-left corner | | width | number | — | Width | | height | number | — | Height | | fill | CSSColor | '#555' | Fill colour | | stroke | CSSColor | null | Stroke colour | | lineWidth | number | 0 | Stroke thickness | | oneWay | boolean | false | Jump-through |

<PivotPlatform position={{ x: 0, y: 350 }} width={600} height={50} fill="#4a7c59" />

<PivotTilemap>

Draws a grid-based tile map.

| Prop | Type | Default | Description | |---|---|---|---| | sheet | SpriteSheet | — | Tile SpriteSheet | | mapData | number[][] | — | 2D map data (-1 = empty) | | tileSize | number | — | Rendered tile size | | solidTiles | Set<number> | new Set() | Solid tile indices | | pixelPerfect | boolean | true | Disable image smoothing for crisp pixel art |

<PivotTilemap sheet={tileSheet} mapData={levelData} tileSize={32} />

useGameLoop(callback)

Starts an rAF loop for the lifetime of the component. Stops automatically on unmount.

useGameLoop((dt: number) => {
  // dt = seconds since last frame
  // update state here, then trigger re-render
});

Custom Shapes

Implement IDrawable to create shapes that work with canvas.add():

import type { IDrawable } from '@colon-dev/pivotx';

class Star implements IDrawable {
  readonly tag = 'star';

  constructor(
    public cx: number, public cy: number,
    public points: number,
    public outer: number, public inner: number,
    public color = 'gold'
  ) {}

  draw(ctx: CanvasRenderingContext2D): void {
    const step = Math.PI / this.points;
    ctx.beginPath();
    for (let i = 0; i < 2 * this.points; i++) {
      const r   = i % 2 === 0 ? this.outer : this.inner;
      const ang = i * step - Math.PI / 2;
      i === 0
        ? ctx.moveTo(this.cx + Math.cos(ang) * r, this.cy + Math.sin(ang) * r)
        : ctx.lineTo(this.cx + Math.cos(ang) * r, this.cy + Math.sin(ang) * r);
    }
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  }
}

canvas.add(new Star(300, 200, 5, 60, 25));

Build Outputs

After npm run build, the dist/ folder contains:

| File | Format | Use case | |---|---|---| | pivotx.umd.js | UMD | <script> tag, dev (unminified + source maps) | | pivotx.umd.min.js | UMD | <script> tag, production / CDN | | pivotx.esm.js | ESM | import in bundlers / TypeScript | | pivotx.cjs.js | CJS | require() in Node / older toolchains | | react.esm.js | ESM | React components + hooks | | react.cjs.js | CJS | React (CommonJS) | | index.d.ts | types | TypeScript types for core | | react.d.ts | types | TypeScript types for React layer |


Publishing to npm

# 1. Set your name in package.json
# 2. Login to npm
npm login

# 3. Publish — this runs type-check + build first automatically
npm publish

After publishing, users can use the CDN immediately:

<script src="https://cdn.jsdelivr.net/npm/@colon-dev/pivotx/dist/pivotx.umd.min.js"></script>
<!-- or -->
<script src="https://unpkg.com/@colon-dev/pivotx/dist/pivotx.umd.min.js"></script>

Sample Games & Tutorials

Learn pIvotX by building real games — from a bouncing ball to a full platformer. All tutorials include step-by-step code breakdowns.

👉 Browse all tutorials →

| Game | Level | What you'll learn | |---|---|---| | Bouncing Ball | Beginner | Canvas setup, game loop, simple physics | | Player Movement | Beginner | Keyboard input, WASD + arrow keys, boundary clamping | | Static Scene | Beginner | Layered rendering without a game loop | | Space Shooter | Intermediate | Enemies, waves, power-ups, explosions | | Dungeon of Shadows | Advanced | Procedural dungeons, melee & ranged combat, loot, bosses | | Nitro Highway | Advanced | Endless runner, police AI, nitro boost, wanted levels | | NEXUS 2500: The Last Signal | Advanced | 5-chapter story, 14 enemy types, boss phases, weapon upgrades | | Aetherdrift | Advanced | Wall-jumping, dashing, 3-hit combos, 3 realms, boss fights |


Links

| | | |---|---| | 🌐 Website | https://pivotx.colondev.com/ | | 🎮 Tutorials & Sample Games | https://pivotx.colondev.com/tutorials | | 📦 npm | https://www.npmjs.com/package/@colon-dev/pivotx | | 🐙 GitHub | https://github.com/ColonDev-Community/pIvotX | | 🐛 Issues | https://github.com/ColonDev-Community/pIvotX/issues |


License

MIT