Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: Bug report
about: A bug in an animation, component, or the MCP server
labels: bug
---

**What happened**

**Repro** (animation/component + options, or the page)

**Expected**

**Environment:** browser/OS · GalaxyJS version
9 changes: 9 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
name: Feature request
about: A new animation, component, or capability
labels: enhancement
---

**The idea**

**What it looks like / API**
8 changes: 8 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## What & why
<!-- One sentence. -->

## Checklist
- [ ] `npm run validate` is green
- [ ] If animations/components changed: re-ran `npm run manifest` (no drift)
- [ ] Counts in sync (package.json / d.ts / llms.txt / manifest)
- [ ] CHANGELOG updated
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ Thumbs.db

# Logs
*.log

# Archives (never commit)
*.zip
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@
All notable changes are documented here. Format: [Keep a Changelog](https://keepachangelog.com/);
versioning: [SemVer](https://semver.org/).

## [3.4.0] "Optics" — 2026-08-07
### Added
- **An optional three.js tier — the library still has zero *required* dependencies.**
`registerAnimation` accepts `renderer: "three"`, and a new
`registerThree(name, { defaults, scene })` helper hands a scene the same lifecycle every other
animation gets. three.js is loaded **on demand, by dynamic `import()`, the first time such a
scene mounts** — once per page, shared by all of them. A page that never mounts one requests
nothing. If the load fails the surface paints a still poster, exactly as the WebGL2 tier
degrades when WebGL2 is missing. Bring your own copy with `Galaxy.useThree(THREE)`, or point one
scene elsewhere with `{ threeUrl }`.
- **Six scenes (80 → 86)**, each doing something the 2D and fragment-shader tiers cannot:
- **`eventHorizon`** — photons integrated through the Schwarzschild metric
(`d²u/dφ² = -u + 3/2·rs·u²`), so the photon ring, the Einstein ring of the background
starfield and the lensed far side of the disk emerge from the geodesics rather than being
drawn on. Relativistic Doppler shift and beaming (I ∝ δ⁴) give the disk its real asymmetry.
- **`molecularCloud`** — a 128³ `Data3DTexture` raymarched in the volume's local space, lit from
within by an embedded protostar via a secondary shadow ray (Beer–Lambert) and a
Henyey–Greenstein phase function. Rotation gives true parallax and self-occlusion.
- **`spiralForge`** — 340,000 stars, one draw call, orbits integrated entirely in the vertex
shader against a flat rotation curve; the density-wave arms shear because ω varies with
radius. Blackbody colour from a mass–temperature relation.
- **`ringedWorld`** — Rayleigh single scattering for a blue limb and a reddened terminator, plus
**mutual shadowing**: ring shadow on the planet (carrying its own gaps) and planet shadow on
the rings, both solved analytically from one shared density function.
- **`gravitySim`** — GPGPU. State lives in float render targets and never returns to the CPU; a
kick–drift symplectic leapfrog keeps orbits stable, and two attractors on an eccentric Kepler
orbit raise tidal bridges and tails at each pericentre. The pointer is a third mass.
- **`starGlare`** — a hand-rolled HDR chain with no addons: bright pass, progressive
downsample/upsample bloom, anamorphic streak, lateral chromatic aberration, filmic tonemap.
- `Galaxy.rendererOf(name)` reports `"2d" | "webgl2" | "three"`; the manifest now records each
animation's tier plus an `optionalDependency` block naming the six scenes that use three.js.

### Fixed
- **A shader whose program failed to build crashed instead of falling back.** `glPosterFallback`
reached for the 2D context unconditionally, but a canvas that already has a live WebGL2 context
can never hand one back — so the "graceful" path threw `Cannot set properties of null`. The
poster now paints the host element with the equivalent CSS gradient when there is no 2D context.
This affected the WebGL2 tier as shipped in 3.3.0, not just the new one.

## [3.3.0] "Deep Field" — 2026-07-28
### Added
- **A WebGL2 tier — still zero dependencies.** `registerAnimation` accepts
Expand Down
72 changes: 65 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,78 @@

**The universe, one line of code.**

A zero-dependency cosmic **animation** + **UI component** library. Beautiful
canvas backdrops, a themeable UI kit, and a single unified API — no build step,
no framework, ~20&nbsp;kB gzipped.
A cosmic **animation** + **UI component** library with **zero required
dependencies**. Beautiful canvas backdrops, a themeable UI kit, and a single
unified API — no build step, no framework, ~20&nbsp;kB gzipped.

Six of the 86 animations are three.js scenes. three.js is **optional and lazily
loaded**: nothing is requested unless you mount one of those six, and if it
cannot load they paint a still poster instead of an empty box.

[![CI](https://github.com/Edwson/GalaxyJS/actions/workflows/ci.yml/badge.svg)](https://github.com/Edwson/GalaxyJS/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
![Zero dependencies](https://img.shields.io/badge/dependencies-0-7c5cff)
![Version](https://img.shields.io/badge/version-3.3-22d3ee)
![Required dependencies](https://img.shields.io/badge/required%20dependencies-0-7c5cff)
![Optional](https://img.shields.io/badge/optional-three.js-22d3ee)
![Version](https://img.shields.io/badge/version-3.4-22d3ee)

> Live demo: https://edwson.github.io/GalaxyJS/ (mirror: https://edwson.com/GalaxyJS/) · Open [`index.html`](index.html) locally for the interactive playground.

---

## ✨ What's new in v3.3 "Deep Field"
## ✨ What's new in v3.4 "Optics"

**Six new animations (80 → 86) and an optional three.js tier.**

These six needed something the canvas and fragment-shader tiers genuinely cannot give: a scene
graph, a real 3D volume, GPU-resident simulation state, or a multi-pass post-processing chain.
So they declare `renderer: "three"`, and the library loads three.js **on demand, by dynamic
import, the first time one of them mounts** — once per page, shared across all six. A page that
never uses one downloads nothing extra.

- **`eventHorizon`** — a Schwarzschild black hole *solved*, not painted. Every pixel's photon is
integrated through curved spacetime (`d²u/dφ² = -u + 3/2·rs·u²`), so the photon ring, the
Einstein ring of the background stars, and the far side of the disk bent up over the top all
fall out of the geodesics. The disk carries relativistic Doppler shift and beaming (I ∝ δ⁴) —
the bright/dim asymmetry is physics, not a gradient.
- **`molecularCloud`** — a genuine 128³ `Data3DTexture` raymarched in the volume's own local
space, lit from within by an embedded protostar with Beer–Lambert extinction along a secondary
shadow ray and a Henyey–Greenstein phase function. Rotating it gives real parallax and
self-occlusion, which is exactly what a 2D noise field cannot do.
- **`spiralForge`** — 340,000 stars in one draw call. Each orbit is integrated *in the vertex
shader* against a flat rotation curve, so there is no per-frame CPU work at all; because ω
varies with radius the density-wave arms shear over time. Colour comes from a blackbody locus
driven by a mass–temperature relation, so rare hot O/B stars burn blue in the arms.
- **`ringedWorld`** — a gas giant with Rayleigh single scattering (blue limb, warm reddened
terminator) and **mutual shadowing**: the rings cast a real shadow onto the planet, carrying
their own Cassini-division gaps as bright lines across the disk, and the planet casts a real
shadow back onto the rings.
- **`gravitySim`** — true GPGPU. Particle state lives in float render targets and never returns
to the CPU; a kick–drift (symplectic leapfrog) integrator keeps the disks stable instead of
unwinding. Two attractors on an eccentric Kepler orbit raise real tidal bridges and tails at
every pericentre. The pointer becomes a third mass.
- **`starGlare`** — a hand-rolled HDR chain (no addons): bright pass → progressive
downsample/upsample bloom → anamorphic streak → lateral chromatic aberration → filmic tonemap.
A star's glare blooms and flares as an occluder slides across it.

```html
<div id="hole" style="height:70vh"></div>
<script>Galaxy.create('eventHorizon', '#hole', { tilt: 0.42 });</script>
```

Bring your own copy to skip the network entirely:

```js
import * as THREE from "three";
Galaxy.useThree(THREE); // now nothing is fetched
Galaxy.create("spiralForge", "#hero", { stars: 400000 });
```

`Galaxy.rendererOf(name)` reports `"2d"`, `"webgl2"` or `"three"` if you want to
check before mounting.

---

## v3.3 "Deep Field"

**Twenty new animations (60 → 80) and a WebGL2 tier — with no new dependencies.**

Expand Down Expand Up @@ -50,7 +108,7 @@ to leak. **If WebGL2 is unavailable the surface renders a 2D poster instead of a
## ✨ What's new in v3 "Nova"

- **One unified API** — `Galaxy.create(type, target, options)` for every animation.
- **80 canvas & WebGL2 animations** — starfield, warp, black hole, nebula, spiral galaxy, meteors, constellation, particle field, aurora, wormhole, orbits, pulsar, mesh gradient, fireflies, matrix rain, plasma, fireworks, snow, waves, DNA helix, lightning, ripples, comets, confetti, bubbles, fog, synthwave grid, rain, vortex, sparkle, neon tunnel, swarm, ribbons, flow field, dotted globe, heartbeat, equalizer, clock, light rays, radar, embers, typewriter, spirograph, **and 17 new in v3.1** — supernova, quasar, star cluster, cosmic web, eclipse, solar corona, galaxy merge, crystal lattice, moiré, starburst, nebula pillars, ion storm, stardust, orrery, oscilloscope, bokeh, magnetosphere, **and 20 new in v3.3** — lensing, accretion disk, n-body, tidal stream, inspiral, volumetric nebula, star surface, atmosphere, dust lanes, protoplanetary disk, spectrograph, transit curve, radio waterfall, H-R diagram, pulsar timing, gravity well, nebula paint, solar wind, star forge, relativistic jets.
- **86 animations** — starfield, warp, black hole, nebula, spiral galaxy, meteors, constellation, particle field, aurora, wormhole, orbits, pulsar, mesh gradient, fireflies, matrix rain, plasma, fireworks, snow, waves, DNA helix, lightning, ripples, comets, confetti, bubbles, fog, synthwave grid, rain, vortex, sparkle, neon tunnel, swarm, ribbons, flow field, dotted globe, heartbeat, equalizer, clock, light rays, radar, embers, typewriter, spirograph, **and 17 new in v3.1** — supernova, quasar, star cluster, cosmic web, eclipse, solar corona, galaxy merge, crystal lattice, moiré, starburst, nebula pillars, ion storm, stardust, orrery, oscilloscope, bokeh, magnetosphere, **and 20 new in v3.3** — lensing, accretion disk, n-body, tidal stream, inspiral, volumetric nebula, star surface, atmosphere, dust lanes, protoplanetary disk, spectrograph, transit curve, radio waterfall, H-R diagram, pulsar timing, gravity well, nebula paint, solar wind, star forge, relativistic jets, **and 6 new in v3.4 on the optional three.js tier** — event horizon, molecular cloud, spiral forge, ringed world, gravity sim, star glare.
- **A real UI kit** — buttons, cards, modals, toasts, tooltips, tabs, accordions, dropdowns, inputs, switches, progress, spinners — all driven by design tokens.
- **Theming** — light/dark out of the box, fully tokenized via CSS variables and `Galaxy.theme()`.
- **Accessible & efficient** — respects `prefers-reduced-motion`, one shared rAF loop for the whole page, auto-pause off-screen, HiDPI-aware.
Expand Down
63 changes: 62 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,68 @@ Galaxy.create("rain", "#sky");
```

The `host` object exposes `ctx`, `width`, `height`, `dpr`, `opts`, `mouse`
(`{x, y, active}`) and `t`. Return `{ draw, resize?, update?, destroy? }`.
(`{x, y, active}`), `reduced` and `t`. Return `{ draw, resize?, update?, destroy? }`.

---

## Renderer tiers

Every animation is drawn by one of three tiers. `Galaxy.rendererOf(name)` tells you
which: `"2d"`, `"webgl2"` or `"three"`.

| Tier | Count | Needs | If unavailable |
|---|---|---|---|
| `2d` | 72 | nothing | — |
| `webgl2` | 8 | a WebGL2 context | paints a still 2D poster |
| `three` | 6 | three.js, **loaded on demand** | paints a still poster |

Canvas 2D is the default and the fallback. **The library has zero *required*
dependencies** — nothing is fetched unless you mount one of the six three.js scenes.

### The optional three.js tier

`eventHorizon`, `molecularCloud`, `spiralForge`, `ringedWorld`, `gravitySim` and
`starGlare` need a scene graph, a real 3D volume, GPU-resident simulation state or a
multi-pass post-processing chain. They declare `renderer: "three"`, and three.js is
fetched by dynamic `import()` the first time one of them mounts — **once per page,
shared by all six**. If it cannot load, the surface paints a still poster instead of
an empty box, exactly as the WebGL2 tier degrades.

```js
Galaxy.create("eventHorizon", "#hero", { tilt: 0.42 }); // fetches three.js now
```

Three ways to control where it comes from:

```js
// 1. Bring your own — nothing is ever fetched.
import * as THREE from "three";
Galaxy.useThree(THREE);

// 2. Self-host or pin a different build, per scene.
Galaxy.create("spiralForge", "#hero", { threeUrl: "/vendor/three.module.js" });

// 3. Check before mounting, e.g. to avoid the download on a metered connection.
if (Galaxy.rendererOf(name) !== "three") Galaxy.create(name, "#hero");
```

Writing your own three scene works the same way as any other animation, except that
`scene(THREE, host)` runs once three.js has arrived and gets the namespace:

```js
Galaxy.register("myScene", {
renderer: "three",
defaults: { spin: 1 },
scene(T, host) {
const renderer = new T.WebGLRenderer({ canvas: host.canvas, context: host.gl });
/* ... */
return { draw(t, dt) { /* ... */ }, resize(w, h) { /* ... */ }, destroy() { /* ... */ } };
},
});
```

Never call `forceContextLoss()` in `destroy()`: a canvas hands back the same context
object forever, so losing it poisons every later mount on that canvas.

---

Expand Down
22 changes: 22 additions & 0 deletions galaxy.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ export type AnimationType =
| "solarWind"
| "starForge"
| "relativisticJets"
/* three.js tier — optional dependency, loaded on demand */
| "eventHorizon"
| "molecularCloud"
| "spiralForge"
| "ringedWorld"
| "gravitySim"
| "starGlare"
| "magnetosphere";

/** A CSS selector string or an existing element. */
Expand All @@ -104,6 +111,11 @@ export interface AnimationOptions {
interactive?: boolean;
/** Start automatically on create. Default: true. */
autoplay?: boolean;
/**
* Where to load three.js from, for `"three"`-tier scenes only. Defaults to a
* pinned jsDelivr build. Ignored once `Galaxy.useThree()` has been called.
*/
threeUrl?: string;
/** Any animation-specific numeric parameter (count, stars, rings, etc.). */
[key: string]: unknown;
}
Expand Down Expand Up @@ -151,6 +163,9 @@ export interface AnimationDefinition {
defaults?: AnimationOptions;
}

/** Which renderer draws an animation. */
export type AnimationRenderer = "2d" | "webgl2" | "three";

export type ToastType = "success" | "warning" | "danger" | "info";
export type ToastPosition =
| "top-right" | "top-left" | "bottom-right" | "bottom-left" | "top-center";
Expand Down Expand Up @@ -239,6 +254,13 @@ export interface GalaxyStatic {
list(): string[];
/** Get a copy of an animation's default options. */
defaults(name: string): AnimationOptions | null;
/** Which tier draws this animation. `"three"` scenes need the optional three.js. */
rendererOf(name: string): AnimationRenderer | null;
/**
* Supply your own three.js namespace so the optional scenes never touch the
* network. Call before mounting any `"three"` animation.
*/
useThree(three: unknown): GalaxyStatic;
/** Scan a DOM scope and wire all declarative components/animations. */
autoInit(scope?: Document | HTMLElement): void;

Expand Down
Loading
Loading