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

aether-dice

v0.3.0

Published

Standalone, framework-agnostic 3D dice roller that settles each die on a caller-supplied value.

Readme

AetherDice

A standalone, framework-agnostic 3D dice roller for the browser. Renders d4 · d6 · d8 · d10 · d12 · d20 · d100 as a physics-driven, transparent canvas overlay — and settles each die on a value you supply, so the visual always matches whatever your app's logic already decided.

Built to be dropped into any web app (including no-build, vanilla-ESM ones) as a single prebuilt module + assets.

AetherDice is a presentation layer. It does not roll randomly or do dice math — your app owns the RNG and modifiers; AetherDice shows the given face values with a believable tumble.

Features

  • All standard polyhedral dice + d100 (as tens/units d10s, with percentile mode).
  • Deterministic results — pass the exact value each die must land on.
  • Physics-based tumble (Three.js + cannon-es) with a guaranteed final face.
  • Dice never leave the frame — a hard containment guarantee (frustum-bounded tray + per-step clamp), independent of gravity / throwForce.
  • Mixed throws in one roll (e.g. 1d20 + 2d6).
  • Themeable with real HDR edge bloomaether (glowing cyan default), aether-neon, aether-molten, classic, or your own.
  • Respects prefers-reduced-motion; instant/skip mode; WebGL-absent fallback signal.
  • Ships as one ESM bundle + assets; no framework dependency.

Install

npm install aether-dice

Or load directly (no build step) via CDN:

import { AetherDice } from 'https://cdn.jsdelivr.net/npm/aether-dice/dist/aether-dice.esm.js';

Quick start

import { AetherDice } from 'aether-dice';

const dice = new AetherDice({
  container: document.getElementById('dice-overlay'), // a positioned element
  assetPath: '/assets/aether-dice/',                  // where textures/wasm live
  theme: 'aether',
});

await dice.init();

// The outcome is decided by YOUR app; AetherDice just renders it:
const result = await dice.roll(
  [{ sides: 20 }, { sides: 6 }, { sides: 6 }],
  { results: [17, 4, 2] },
);
// → { dice: [{sides:20,value:17}, {sides:6,value:4}, {sides:6,value:2}], total: 23 }

API

new AetherDice(options)

| Option | Type | Default | Description | |---|---|---|---| | container | HTMLElement | — | Element the canvas overlay mounts into (required). | | assetPath | string | bundle-relative | Base URL for textures (env/normal maps). Defaults to the assets/ folder shipped next to the bundle, so npm/jsDelivr consumers need no config; override with your own hosted path. | | theme | string \| object | 'aether' | Built-in theme name or a theme object. | | scale | number | 6 | Die size. | | gravity | number | 40 | Physics gravity. | | throwForce | number | 8 | Initial impulse strength. | | trayMargin | number | 0.5 | Extra inset of the containment tray from the frame edge (scene units). Larger keeps dice further from the edge. | | reducedMotion | boolean | auto | true skips the tumble and shows the result instantly. Defaults to the user's prefers-reduced-motion. | | maxDice | number | 20 | Safety cap on dice per roll. |

Methods

  • await dice.init() — load the engine + assets; resolves when ready.
  • await dice.roll(dice, opts) — render a roll. dice is a notation string ('2d8') or an array ([{ sides: 20 }, …]). opts.results is the array of values each die must land on (always pass this from a game). opts.instant skips the tumble for this call. Resolves to { dice: [{ sides, value }], total }.
  • dice.clear() — remove dice from the scene.
  • dice.destroy() — dispose all GPU resources (call on unmount).
  • dice.on(event, handler) / dice.off(event, handler).

Events

| Event | Payload | When | |---|---|---| | rollStart | { dice } | A roll begins. | | rollComplete | { dice, total, text } | Dice settle. text is the readable result (use it for logs / screen readers). | | webglUnavailable | {} | WebGL can't initialize — fall back to a 2D presentation. |

Determinism (the important part)

If you omit results, AetherDice may roll randomly and return the landed values — handy for demos. In a real game, always pass results so the rendered dice match your authoritative roll. Internally the tumble runs with a random throw, then a corrective rotation guarantees the requested face ends up. The result is also delivered as text via rollComplete, so the visual is never the only source of truth.

Theming

Pass a built-in theme name or a theme object. A theme object is merged over the aether default, so you only specify what you want to change:

new AetherDice({ theme: 'aether-neon' });          // built-in name

new AetherDice({ theme: {                          // custom (merged over aether)
  dieColor:      '#10131a',   // body color
  edgeGlow:      '#39d0ff',   // edge color (base hue of the glow)
  numberColor:   '#eaf6ff',   // numeral color
  numberFont:    '600 220px "Trebuchet MS", system-ui, sans-serif',
  metalness:     0.2,
  roughness:     0.35,
  opacity:       0.92,         // <1 → translucent body
  ambient:       '#22303a',    // ambient light color
  keyLight:      '#ffffff',    // key (directional) light color
  bloom:         true,         // enable the edge bloom post-process
  edgeIntensity: 2.2,          // HDR edge multiplier (>1 → brighter, punchier glow)
  bloomStrength: 1.5,          // overall bloom amount
  bloomRadius:   0.6,          // bloom spread
  bloomThreshold:0,            // luminance cutoff (0 = bloom all edge light)
  envMap:        'env.png',    // environment map for reflections (null to disable)
  envIntensity:  1.0,          // reflection strength
  normalMap:     'surface-normal.png', // subtle surface detail (null to disable)
  normalScale:   0.25,         // normal map strength
}});

envMap and normalMap are filenames resolved against assetPath. AetherDice ships default env.png (a studio environment giving the metallic dice reflections) and surface-normal.png (a faint cast-glass surface) in its assets/ folder; set either to null to skip it. Textures load during init() and failures are non-fatal — the dice just render without that map.

Built-in themes

| Preview | Name | Look | |---|---|---| | | aether (default) | Dark dice, glowing cyan edges, light numerals. | | | aether-neon | Deep violet body, searing magenta-purple edge glow. | | | aether-molten | Dark ember body, hot orange edge glow, warm numerals. | | | classic | Ivory/black, no glow. |

Edge bloom (the glow is real)

The edge glow is a true bloom post-process, not a flat outline. It uses selective bloom — only the die edges glow, never the body or numerals — and the edge color is pushed into HDR (edgeIntensity > 1) so the brightest part of each edge blows out to a white-hot core with a saturated colored halo, like neon. Two knobs shape it:

  • edgeIntensity — how over-bright the edge source is (character of the glow).
  • bloomStrength / bloomRadius — how much the glow spreads (amount and softness).

Set bloom: false (as classic does) to skip the post-processing pipeline entirely — the edges then render as a plain line, or are omitted if edgeGlow is transparent.

Supported dice

d4, d6, d8, d10, d12, d20, d100. The d4 is read by its apex/bottom convention; d100 renders as a tens die (00–90) + a units die (0–9) and can combine to a percentile.

Development

npm install
npm run dev      # demo page with live reload
npm test         # unit tests (value resolution, d4/d100, corrective orientation)
npm run build    # emits dist/aether-dice.esm.js + assets/

The /demo page rolls every die type, mixed throws, and lets you force a predetermined result to verify determinism.

Distribution

Published to npm and consumable via jsDelivr. The public API above is the semver stability contract. The build emits dist/aether-dice.esm.js + dist/assets/, and both ship in the npm tarball — the bundle resolves its default assetPath relative to itself, so textures load with no configuration. Override assetPath only if you host the assets elsewhere (e.g. a different CDN).

Releasing

One command runs the whole flow — preflight, quality gate, version bump + tag, push, npm publish, and a GitHub release with the bundle attached:

npm run release                # patch  (0.1.0 -> 0.1.1)
npm run release -- minor       # minor  (0.1.0 -> 0.2.0)
npm run release -- major       # major
npm run release -- 1.2.3       # explicit version
npm run release -- patch --dry-run   # preflight + gate + pack preview, no changes

It aborts before mutating anything if the tree is dirty, the branch isn't main, the branch is behind origin, npm isn't authenticated, or lint/tests/build fail.

License

MIT.