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

@ev3-art/epoch-one

v0.2.2

Published

Embeddable WebGL2 generative-art engine for displaying Epoch One pieces. Zero-dependency core with vanilla and React entry points.

Readme

@ev3-art/epoch-one

Embeddable WebGL2 generative-art engine for displaying Epoch One pieces. Zero-dependency core with vanilla and React entry points.

Install

bun add @ev3-art/epoch-one
# or
npm i @ev3-art/epoch-one

React and react-dom ≥ 19 are peer dependencies — required only when using the /react entry point.

Vanilla usage

import { mount } from '@ev3-art/epoch-one/vanilla';
import type { EpochOneNft } from '@ev3-art/epoch-one/vanilla';

const nft: EpochOneNft = {
  seed: 'abc123',
  jsonUri: 'https://example.com/metadata/1.json',
  thumbnailUrl: 'https://example.com/images/1.png',
  frameCount: 300,
};

// The host element must have explicit dimensions — it becomes the centering and
// clipping root for the canvas. An unsized element collapses (give it width/height).
const el = document.getElementById('art-container')!;

const { engine, destroy } = mount(el, {
  nft,
  isAdmin: false,         // optional — enables admin-only controls (reseed key)
  onReady(engine) {
    console.log('engine started', engine);
  },
  onFirstFrame(engine) {
    console.log('first frame painted');
  },
  // fetch: customFetch,  // optional — custom fetch for auth / gateway rewrite
  // container: menuRoot, // optional — separate element to attach menu + brush into
});

// Later: clean up
destroy();

The host element receives the ev3-epoch-one-root class (flex centering, overflow hidden, CSS container). When container is omitted the same element hosts the menu and brush overlay. When container is provided it must also be explicitly sized (both elements receive the root class).

mount returns { engine, destroy }. Call destroy() when done — it tears down the controller, menu, brush overlay, and canvas.

React usage

import { EpochOneEngine } from '@ev3-art/epoch-one/react';
import type { EpochOneNft } from '@ev3-art/epoch-one/react';

const nft: EpochOneNft = {
  seed: 'abc123',
  jsonUri: 'https://example.com/metadata/1.json',
  thumbnailUrl: 'https://example.com/images/1.png',
  frameCount: 300,
};

function ArtViewer() {
  return (
    <div style={{ width: 800, height: 600 }}>
      {/* EpochOneEngine fills its parent (width/height 100%) — size the parent */}
      <EpochOneEngine
        nft={nft}
        isAdmin={false}
        onReady={(engine) => console.log('ready', engine)}
        onFirstFrame={(engine) => console.log('first frame')}
        className="my-art-class"
      />
    </div>
  );
}

EpochOneEngine props (EpochOneEngineProps):

| Prop | Type | Required | Description | |------|------|----------|-------------| | nft | EpochOneNft | yes | NFT descriptor (see below) | | isAdmin | boolean | no | Enable admin-only controls (reseed key). Default false. | | onReady | (engine: Engine) => void | no | Called after NFT loads and engine starts. | | onFirstFrame | (engine: Engine) => void | no | Called on the first painted animation frame after start. | | fetch | typeof fetch | no | Custom fetch for NFT assets (auth headers / gateway rewrite). | | className | string | no | Extra CSS class on the root div. |

Changing nft or isAdmin remounts the engine. Changing onReady, onFirstFrame, or fetch does not (they are captured in refs).

Requires React ≥ 19 and react-dom ≥ 19.

The EpochOneNft contract

interface EpochOneNft {
  /** Deterministic seed string — drives all shader parameters. */
  seed: string;

  /** Off-chain metadata JSON URL. The hidden `engine` block is fetched from here. */
  jsonUri: string;

  /** Base image URL (the genesis frame / thumbnail). */
  thumbnailUrl: string;

  /** Fallback total frame count when the `engine` block is absent. */
  frameCount: number;

  /** Movement-buffer URL. Present on evolved NFTs. */
  movementBufferUrl?: string;

  /** Paint-buffer URL. Present on evolved NFTs. */
  paintBufferUrl?: string;

  /**
   * Post-processed ImageIn background URL (hand-crafted ImageIn NFTs only).
   * Immutable reset target — separate from the evolving thumbnail/ping-pong buffer.
   */
  imageInUrl?: string;
}

The consuming app derives this from its own chain or DAS result. The package is chain-agnostic (zero Solana dependencies). When both movementBufferUrl and paintBufferUrl are present, the engine fetches and restores the evolved state. Buffer fetch failures propagate as errors (they are load-bearing); engine block fetch failures fall back silently to descriptor values.

The engine block (authoring overrides)

jsonUri may carry an optional top-level engine block. loadNft fetches and parses it automatically — consumers never construct or pass it. Malformed or unknown fields are dropped, and any absent field falls back to its seed-derived value. A failed fetch or parse is non-fatal: the piece renders from its descriptor and seed.

{
  "engine": {
    "engineFrame": 300,    // resume/target frame; defaults to the descriptor frameCount
    "manualMode": false,   // load paused for manual stepping
    "globalFreeze": false, // freeze the simulation on load
    "overrides": { "aspect": "16:9", "orientation": "portrait" }
  }
}

Every overrides field is optional (type EngineBlockOverrides, exported from the main barrel):

| Field | Type | Default | Notes | |-------|------|---------|-------| | aspect | '4:3' \| '16:9' | '4:3' | Render aspect ratio. | | orientation | 'landscape' \| 'portrait' | 'landscape' | Pair '16:9' + 'portrait' for a 9:16 vertical piece. | | palette | string | seed | A premade palette name. Mutually exclusive with paletteColors. | | paletteColors | 3 × [number, number, number] | seed | Inline image-derived palette (three RGB rows). | | useColorCycle | boolean | false | Only paired with paletteColors. | | bloomIntensity | number | seed | Bloom strength. | | lumaWeight | number | seed | Luma weighting. | | baseChunkSize | number | seed | Unscaled units. | | hueSpeed | number | seed | Absolute cycle speed (turns/frame @60fps). | | moveSpeed | number | seed | Movement speed. |

import type { EngineBlockOverrides } from '@ev3-art/epoch-one';

From a Solana DAS asset (recommended)

If you already have a Metaplex DAS asset — e.g. from a Helius getAsset / getAssetsByGroup call — skip the manual construction above and use the converter:

import { epochOneNftFromDasAsset, type EpochOneNft } from '@ev3-art/epoch-one';

const nft: EpochOneNft | null = epochOneNftFromDasAsset(dasAsset);
if (!nft) {
  // Not an Epoch One asset — the converter already logged the reason via console.error.
  return;
}
// pass `nft` to mount() / loadNft() / <EpochOneEngine nft={nft} />

The converter is synchronous and chain-agnostic — it reads the DAS JSON only (no network, no Solana dependencies). It returns null (after a console.error) when the asset is not an Epoch One piece, detected from the Seed trait or a custom image/EpochOne-* file. The same epochOneNftFromDasAsset and DasAsset type are re-exported from @ev3-art/epoch-one/vanilla and @ev3-art/epoch-one/react.

Advanced: EngineController and /ui

For custom or non-React hosts that need direct control over the menu lifecycle.

EngineController

Exported from @ev3-art/epoch-one (main barrel).

import { EngineController } from '@ev3-art/epoch-one';
import type { EngineControllerOptions } from '@ev3-art/epoch-one';

EngineController is framework-neutral: it handles scoped-CSS injection, the tools menu, engine dispatch, recording-status sync, pointer routing, and the brush overlay. Both mount() and <EpochOneEngine> construct exactly this internally.

EngineControllerOptions:

| Option | Type | Required | Description | |--------|------|----------|-------------| | canvas | HTMLCanvasElement | yes | The canvas the engine renders to. | | engine | Engine | yes | The engine instance. | | menuHtml | string | yes | Raw menu markup string. Pass menuHtml from /ui. | | menuCss | string | yes | Menu stylesheet string. Pass menuCss from /ui. | | isAdmin | boolean | no | Enable admin-only controls. Default false. | | brushSizeOptions | number[] | no | Custom brush size ladder. | | onAppMenu | () => void | no | Called when the user triggers the system / app menu action. | | root | HTMLElement | no | Element to attach menu + brush into. Defaults to document.body. |

Call controller.destroy() to remove event listeners, the menu container, and the brush overlay.

/ui — raw menu assets

import { menuHtml, menuCss, setupMenu } from '@ev3-art/epoch-one/ui';
import type { MenuController, MenuOptions, MenuState } from '@ev3-art/epoch-one/ui';
  • menuHtml (string) — build-inlined menu markup. Inject into the DOM before calling setupMenu. Required by EngineController.
  • menuCss (string) — build-inlined menu stylesheet. Inject once into a <style> tag per page. Required by EngineController.
  • setupMenu(opts?: MenuOptions): MenuController | null — wires keyboard shortcuts, click delegation, and brush drag against the injected HTML. Returns null if #tt-menu-container is not found in the DOM. Most hosts use EngineController rather than calling setupMenu directly.

MenuController methods: getState(), setState(partial), open(), close(), show(), hide(), updateActiveStates(), updateBrushDisplay(), setRecordingStatus(status), setRotateAvailable(available), setRotateActive(active), setToolsEnabled(enabled), setDrawingEnabled(enabled), destroy().

Limitations

  • Single instance per page. The engine uses a global instance guard. Mounting a second engine before destroying the first logs a console warning — the tools menu and keyboard shortcuts resolve DOM by fixed ids, so concurrent instances collide. Destroy the active instance first.
  • ESM-only. Use <script type="module"> in HTML or a bundler (Vite, esbuild, Rollup). This package is not an IIFE and cannot be used as a plain <script> drop-in.

License

MIT © 2026 EV3