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

@idle-screens/saver-metaquarium

v0.9.0

Published

Metaquarium screensaver for idle-screens: a three.js fish tank with seeded analytic swim, GLB fish streamed from the Metaquarium farm, seeded neon palette coats, and a steerable camera. The screen is a tank.

Readme

@idle-screens/saver-metaquarium

A three.js aquarium saver: skinned GLB fish swim seeded Catmull-Rom spline paths through a dark, fogged tank.

Core animation loop

mount
  ├─ WebGLRenderer (stencil off, high-performance, sRGB, linear tone)
  ├─ Scene: fog (fogNear/fogFar) + terrain floor, and — when `environment`
  │         is not `void` — a terrain silhouette and light shafts (a water
  │         ceiling too, for the rooms that have one: reef, kelp, ice, lagoon)
  ├─ PerspectiveCamera on param-steered spherical orbit
  └─ populate():
       for each fish index:
         1. fetch + parse GLB → FishTemplate (module-level cache)
         2. SkeletonUtils.clone() → per-fish skinned mesh
         3. applyNpcMaterials(): seeded palette body + glow colors (all unlit)
         4. compileSwimPlan(rng.fork(i), BOUNDS) → closed Catmull-Rom loop
            on the chosen pathShape, with arc-length table + speed-wobble
            harmonics; swimStyle assigns depth band, formation slot or bond
         5. add to scene

frame loop (rAF or renderFrame(t)):
  1. governor: median frame time > 21ms → render scale ×0.8 (floor 0.56);
     back under 14ms → step it up again
  2. setState(t):
     - sample control track → live params
     - camera orbit from cameraAzimuth + autoRotate * t
     - fog color from fogColor param
     - for each fish:
         distance = distanceAt(plan, tSec, speed)   // closed-form integral
         pose = swimPoseAtDistance(plan, distance)   // arc-length → spline param
         group.position ← pose.xyz
         group.lookAt ← pose.forward
         group.rotateZ ← pose.roll (bank into turns)
         + maneuver displacement (seeded per-fish event schedule)
         mixer.setTime ← beat * 0.045, wrapped to clip length (tail beat)
  3. renderer.render(scene, camera)

Architecture

  • Deterministic: seeded RNG only, closed-form swim. renderFrame(t, seed) is frame-addressable — same inputs, same frame.
  • Steerable: camera, cast, room, swim style, maneuvers and palette — every param below rides the control track.
  • Additive by default: each param's default reproduces the behaviour that existed before it was added, so a bump never changes a scene already running.
  • Device-tiered: @idle-screens/capabilities scales pixel ratio, AA, and fish cap per device.
  • Adaptive governor: steps render resolution down when frames exceed budget.
  • Zero-dep manifest subpath: servers validate params without pulling three.js.
  • Lofi backend: createMetaquarium({ backend: 'lofi' }) swaps three.js for the Apple TV's 2D aquarium — each fish's _transparent_icon.png swimming a Canvas2D After Dark tank (kelp, bubbles, light shafts). Same seed, same layout as the TV. It reads only environment and fishMix, and always swims 8 fish (13 on high-tier devices) like the TV — so a default scene is one hero fish in WebGL and a full tank in lofi, by design. Other params (swimSpeed, camera, fog, …) are no-ops there. A host choice for QA and nostalgia, not a scene param; the playground exposes it as ?lofi=1.

Params

The paramSpace in src/manifest.ts is the source of truth — it carries the bounds, eases and the reasoning behind each default. Every param defaults to the behaviour that existed before it was added, so a scene already on a wall never changes because a dependency was bumped.

Camera

| Param | Type | Default | Description | |-------|------|---------|-------------| | cameraAzimuth | number | 35 | Orbit angle (degrees), 0–360 | | cameraElevation | number | 15 | Height angle above the waterline, −5–60 | | cameraDistance | number | 110 | Distance from tank center, 80–400 | | autoRotate | number | 0 | Continuous orbit speed (deg/s), 0–12 |

Cast

| Param | Type | Default | Description | |-------|------|---------|-------------| | fishCount | number | 1 | Visible fish, 1–24 (step). Default 1 = hero mode; the pool grows on demand and never shrinks | | fishUrl | string | ipfs://…/fish_257_….glb | GLB model URL, single-breed mode (ipfs:// supported; the playground overrides to a local asset) | | fishMix | string | "" | Mixed population DSL: id[:count][@style] comma-separated, catalog ids or breed aliases ("257:2,100:1", "457:3@hover,257:6@school"). A minted id is an INDIVIDUAL — no id twice in a scene. Non-empty overrides fishUrl + fishCount; counts absolute, tier-capped | | dracoPath | string | "" | Where the Draco decoder lives (most Metaquarium models are Draco-compressed). Empty = the copy shipped beside this package |

Motion

| Param | Type | Default | Description | |-------|------|---------|-------------| | swimSpeed | number | 1 | Swim time-scale multiplier, 0.2–3 | | swimStyle | enum | loop | loop (pre-style), school, drift, hover, patrol, bottom, surface; relationship styles follow / pair / chase bond a fish to the nearest preceding unbonded fish; auto gives each untagged token its breed's default | | pathShape | enum | wander | The shape a loop is drawn on: wander, orbit, eight, helix, canyon, crossing (camera-relative parade lane) | | formationShape | enum | phalanx | How a school holds together: phalanx, line, ring, wedge, ball, wheel. Ignored by non-formation styles | | swimVariance | number | 0 | Per-fish spread, 0–1: 0 a uniform shoal, 1 every fish its own animal (±40% speed, ±25% size, own phase) | | bodyWiggle | number | 0 | Procedural body yaw for models with no animation clip, 0–1. Clipped models ignore it; 0.3–0.4 recommended for a clip-less cast | | maneuver | enum | none | Named event layered over the swim style: dart, startle, graze, curious, zoomies. Each fish runs its own seeded schedule | | maneuverRate | number | 0.5 | How often events fire, 0–3: 0 never, 1 the maneuver's own tempo (~14–20 s per fish), 3 nearly back to back | | maneuverIntensity | number | 0.7 | How hard — scales the surge, the kick and the tail flurry together, 0–1 | | lightSeek | number | 0 | Free fish drawn toward the room's light shafts, each to its own pool, 0–1 (needs rays) | | formationBreathe | number | 0 | The school relaxes outward and back on a ~15 s cycle, 0–1; only ever expands | | look | | | The next three are the renderer's defaults, not something a scene sets: every tank is lit, glowing and reflective with no params. They exist to opt out. | | fishGlow | number | 0.6 | The fish's own GLOW-* parts as light sources: bloom card, white-hot breathing core, colour on the floor under low swimmers. 0 is the flat colour + thin halo | | fishLighting | enum | lit | lit: fish take light — key + fill so voxel faces shade, a generated studio environment for metal to reflect, point lights riding the glow parts nearest the camera (4 / 3 / 0 by tier). flat is the original unlit look | | fishMetal | enum | on | Metallic plates wear a generated chrome matcap (reflection with no env map, no lights); off is the flat unlit atlas | | eyeLife | number | 0 | Eyes blink, look and emote — each token's own pixel-grid eye redrawn in a fragment function, black and white only. 0 is the stock eye program, byte for byte; a scene opts in with 1 |

Scenery

Crystals and the props built from them. Everything here is generated from the seed (nothing fetched); the world layers that stand on it are under Mineral worlds.

| Param | Type | Default | Description | |-------|------|---------|-------------| | propMix | string | '' | Scenery, kind[#id][:count][@habit][/palette][*size] (*6 = a tower-sized crystal, planted past the swim space). Kind crystal (generated, never fetched); habits lotus · spire · druse · scatter · coral; palettes env · rainbow · glass · a named colour. Empty builds nothing | | envProps | enum | off | on lets a named environment bring its own crystals when propMix is empty | | crystalScale | number | 1 | Cluster size, 0.4–2.5 (rebuilds the layout) | | crystalWild | number | 0.7 | How individual each cluster is: 0 is the regular measured rosette; toward 1 clusters lean, go bald on one side, grow lopsided and branch like coral | | crystalGlow | number | 0.8 | Halo, glow card and the floor pools, 0–1 | | crystalPulse | number | 0.3 | Slow breathing of the glow (0.12 Hz, ≤15 %) | | crystalTint | number | 0 | Opt-in: fish near a cluster pick up its colour. 0 never touches a fish material |

The room

| Param | Type | Default | Description | |-------|------|---------|-------------| | environment | enum | void | The ROOM: void (exactly the pre-environment scene), abyss, reef, kelp, ice, vent, lagoon, universe. Adds terrain and light shafts, plus a water ceiling for the rooms that have one (reef, kelp, ice, lagoon); never overrides your palette params | | floorKind | enum | auto | Override the environment's terrain: auto, flat, dunes, ridges, basin | | waterY | number | −1 | Water-ceiling height, −1–220. −1 follows the environment (step, not smooth — the sentinel can't be interpolated through) | | rayStrength | number | −1 | Light-shaft strength, −1–1. −1 follows the environment, 0 = off (step, same sentinel reason) |

Palette

| Param | Type | Default | Description | |-------|------|---------|-------------| | fogColor | color | #030009 | Water / atmosphere (background + fog) | | fogNear | number | 60 | Fog start distance, 20–200 | | fogFar | number | 500 | Fog full-opacity distance, 120–1100; the tank enforces far > near + 20 | | floorColor | color | #0a1d33 | Floor disc color | | moteDensity | number | 0 | Plankton motes, 0–1 of the tier budget. 0 = off | | moteColor | color | #7fd6ff | Mote tint |

File map

ls src/ is the truth if this drifts; the roles below are what each module owns.

| File | Role | |------|------| | tank.ts | Renderer, scene, fish spawn, environments, setState, governor, dispose | | lofi.ts | Lofi backend, pure half: the Apple TV's 2D aquarium layout, poses, palettes | | lofi-tank.ts | Lofi backend, canvas half: Canvas2D draw + transparent-icon loading | | plan.ts | Catmull-Rom swim: compile, arc-length, pose-at-distance, path shapes | | swim.ts | Swim styles, formations, depth bands, relationship bonds | | maneuver.ts | Named seeded events (dart, startle, graze, curious, zoomies) | | environments.ts | The named rooms: water ceiling, terrain, light shafts, cost budget | | materials.ts | Seeded palette coat (unlit MeshBasicMaterial) | | ipfs.ts | ipfs:// resolution, gateway ladder, fishMix parsing, fish catalog | | farm.ts | Metaquarium farm lookup (breed aliases, minted token ids) | | asset-cids.ts | Pinned CIDs for the default catalog | | tank-draco.ts | Draco decoder wiring, scoped to a decode | | runtime.ts | three.js runtime resolution seam | | manifest.ts | Param space, palettes, manifest metadata (zero-dep subpath) | | quality.ts | Device-tier quality caps | | metaquarium.ts | Plugin factory + demo track |

Mineral worlds

The WebGL tank has a set of independent, opt-in world parameters. They work with createMetaquarium({ params }), the normal control track, and the playground's World controls. They do not change the 2D SaverSpec format or the lofi renderer. Every one of them defaults to off — 0, none, −1 for followSpot, empty for vignette — except rockVeins, which defaults to 0.7 but only applies once rockDensity is above zero. So existing scenes retain their composition.

| Parameter | Range | Effect | | --- | --- | --- | | rockDensity | 0–1 | Crystal-root boulders, sparse satellite rocks, a distant ridge and an open arch. Any positive value provides foundations; density adds satellites. | | rockVeins | 0–1 (0.7) | How fractured the stone is. Fissures are cut from the rock's own facets (so they always lie on the surface), fork, carry a white-hot core, and sprout small crystals. 0 is plain stone. | | geodeHomes | 0–3 | Geode homes: a broken boulder with an agate rind and a throat of crystal teeth, and a voxel house recessed inside — round door, lit window, lamp, steps, a chimney that vents bubbles. Habits cycle cottage / hall / tower; each home lights the floor at its door. Weak devices retain at most two. | | interior | none · geode | Sets the scene INSIDE a geode home: crystal-lined dome with agate strata, plank floor and rug, voxel furniture, a chandelier / lamp / stove / window that light the floor and the fish. Use with the void environment; the default orbit camera stays indoors (keep cameraDistance ≲ 130). | | followSpot | -1…23 | A follow-spot on one fish (its cast slot; -1 off): a beam from the rig, a pool of moving caustics on the floor beneath it, a light that rides with the fish, and the house lights down — a performer on a stage. spotStrength 0–1, spotColor. Closed-form: it follows the fish's own deterministic path. | | vignette | string | A small scene for the first 2–3 fish: a preset (tea, bedtime, seek) or a script of beats — a =table, b =door \| b >table @a \| a @b talk, b @a nod \| a b circle rug. =mark start there, >mark go there, @x face a mark or actor, circle x, follow x, gestures talk nod shake hop spin wiggle bow peek rest, leading 6s: sets a beat's length. Marks indoors: rug table bed shelf stove lamp armchair door window chest chandelier; outdoors: centre left right front back high low. Closed-form, looping; other fish swim as usual. | | floraDensity | 0–1 | Three voxel species — kelp, reed clumps, lantern bulbs — that lean toward and take the colour of the nearest crystal; sway grows with height, a gust travels across the field, tips breathe. | | bubbleVents | 0–1 | Bubbles in puffs from geode chimneys, fissure crowns and crystal bases; they quicken, swell and wander as they rise. | | marineSnow | 0–1 | Slowly sinking particles sampling the crystals' coloured light field. Separate from the original single-colour moteDensity. |

Minerals are faceted; living flora and the inhabitants' furnishings are voxel. crystalScale also scales the world geometry. The layers work without crystals, using a seeded set of fallback anchors. With crystals, their layout supplies the anchors and palette. Rock foundations lift crystal roots; floor-hugging fish use scenery clearance in addition to terrain and crystal clearance. Normal depth buffering lets fish pass behind the scenery; this is not a general solid-body collision simulation.

createMetaquarium({ params: {
  propMix: 'crystal:2@druse/hotpink,crystal:2@spire/orange',
  rockDensity: 0.55, geodeHomes: 3, floraDensity: 0.35,
  bubbleVents: 0.85, marineSnow: 0.6,
  cameraDistance: 220, cameraElevation: 11, cameraAzimuth: 8,
  fogColor: '#080718', floorColor: '#10182b',
} });

In the playground, Metaquarium → Worlds offers mineral garden, geode harbor, and moonlit grove. Direct link suffixes are ?saver=metaquarium-world-mineral-garden#dev, ?saver=metaquarium-world-geode-harbor#dev, and ?saver=metaquarium-world-moonlit-grove#dev.

All animation is analytic in the scene clock (pause/seek/replay safe), with independent seeded forks per layer. Geometry is built only when structural parameters change. The complete world adds at most eight batched draws, no textures, downloads, shadow maps or additional lights. Flora, shards, snow and bubbles scale with the existing device prop budget. inspect().props.scenery reports actual populations; prop draw-call/triangle totals include the world.

For agents (MCP)

Everything an agent needs to author a scene is data, importable without three.js:

import { RECIPES, recipeTrack, PARAM_DOCS, GRAMMAR, validateMetaquariumParams } from '@idle-screens/saver-metaquarium/manifest';

const scene = RECIPES.find((r) => r.id === 'stage-duet')!;
publishScene({ spec: { id: 'metaquarium' }, track: recipeTrack(scene.params) });
validateMetaquariumParams({ vignette: 'a >home1', geodeHomes: 0 }); // → [{ path: 'vignette', message: 'beat 1: no mark "home1" …' }]

PARAM_DOCS has one line per param (a test keeps it complete), GRAMMAR documents the five small DSLs and the marks a vignette can name, and the playground mounts every recipe unchanged on its "recipes" shelf.