@jayf0x/fluidity-js 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,148 @@
1
+ # AGENTS.md — fluidity-js
2
+
3
+ > Universal agent instructions for GitHub Copilot, Cursor, GPT-4, Gemini, and all AI coding assistants.
4
+ > Claude Code users: see [CLAUDE.md](./CLAUDE.md) for deeper architecture notes.
5
+
6
+ ---
7
+
8
+ ## What this repo is
9
+
10
+ `@jayf0x/fluidity-js` — a WebGPU-first fluid simulation library for React. Implements a real-time Navier-Stokes solver (advection, divergence, pressure, vorticity confinement) that renders onto a `<canvas>` element. Falls back automatically to WebGL2 → WebGL1. Runs the heavy simulation loop in a Web Worker via OffscreenCanvas by default.
11
+
12
+ Two components: `<FluidText>` (text as obstacle/background) and `<FluidImage>` (bitmap as obstacle/background).
13
+
14
+ **Stack:** TypeScript · React 17+ · Vite 4 · Vitest · Bun
15
+
16
+ ---
17
+
18
+ ## Repo layout (critical paths)
19
+
20
+ ```
21
+ src/
22
+ index.ts ← public exports
23
+ globals.d.ts ← ambient global types (FluidConfig, FluidHandle…)
24
+ core/
25
+ config.ts ← DEFAULT_CONFIG, presets, mergeConfig
26
+ gl-utils.ts ← WebGL context + FBO helpers
27
+ gpu-utils.ts ← WebGPU helpers
28
+ shaders.ts ← GLSL shader strings
29
+ wgsl-shaders.ts ← WGSL shader strings
30
+ simulation.ts ← FluidSimulation class (dual WebGPU/WebGL)
31
+ textures.ts ← texture creation for text + image modes
32
+ worker/index.ts ← Web Worker message handler
33
+ fluid-controller.ts ← worker vs main-thread abstraction
34
+ react/
35
+ FluidText.tsx ← React component
36
+ FluidImage.tsx ← React component
37
+ useFluid.ts ← core hook: canvas lifecycle + controller
38
+ tests/ ← Vitest + jsdom (83 tests)
39
+ demo/ ← standalone Vite 5 demo site (NOT the library)
40
+ dist/ ← built output (do not edit)
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Commands
46
+
47
+ ```bash
48
+ # Run tests (library root, Node 16 compat)
49
+ bun test:claude # preferred: runs vitest, tails last 8 lines
50
+
51
+ # Build library
52
+ bun build
53
+
54
+ # Demo (requires Node 20)
55
+ cd demo
56
+ PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun dev
57
+ PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun build
58
+ PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun deploy # → gh-pages
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Public API
64
+
65
+ ### Components
66
+
67
+ ```tsx
68
+ <FluidText text="Hello" fontSize={120} color="#fff" algorithm="glass" preset="neon" />
69
+ <FluidImage src="/hero.jpg" algorithm="aurora" quality={{ dpr: 1, sim: 0.5 }} />
70
+ ```
71
+
72
+ ### FluidHandle ref
73
+
74
+ ```ts
75
+ interface FluidHandle {
76
+ reset(): void;
77
+ move(opts: { x: number; y: number; strength?: number }): void;
78
+ splat(x: number, y: number, vx: number, vy: number, strength?: number): void;
79
+ updateConfig(config: Partial<FluidConfig>): void;
80
+ }
81
+ ```
82
+
83
+ ### Algorithms
84
+ `'standard'` · `'glass'` · `'ink'` · `'aurora'` · `'ripple'`
85
+
86
+ ### Presets
87
+ `'calm'` · `'sand'` · `'wave'` · `'neon'` · `'smoke'`
88
+
89
+ ---
90
+
91
+ ## Code style / conventions
92
+
93
+ - **TypeScript strict** — no `any`, no non-null assertions without a comment
94
+ - **No file extensions** on imports within `src/` (e.g. `from './config'`)
95
+ Exception: worker import keeps `.js?worker&inline` for Vite query string
96
+ - **No default exports** from library modules; named exports only
97
+ - **No comments** unless the *why* is non-obvious (hidden constraint, invariant, workaround)
98
+ - **Formatting:** Prettier with `@trivago/prettier-plugin-sort-imports`
99
+ - **Tests:** All 83 must pass before any commit. Add tests for new behaviour.
100
+
101
+ ---
102
+
103
+ ## Architecture invariants (do not violate)
104
+
105
+ | Rule | Reason |
106
+ |------|--------|
107
+ | `transferControlToOffscreen` called at most once per canvas | Irreversible — double-mount in StrictMode would crash |
108
+ | `useFluid` creates a **fresh** `<canvas>` element each mount inside a container `<div>` | StrictMode safety |
109
+ | `createBlit` sets up vertex buffers **once** | Performance — never call `vertexAttribPointer` per draw |
110
+ | Worker `.terminate()` always deferred 50ms after `postMessage({type:'destroy'})` | Lets the worker flush its destroy sequence |
111
+ | Display shader outputs **premultiplied alpha** | Transparent canvas compositing correctness |
112
+ | Coverage texture ref: `text` mode → `coverageTex === obstacleTex`; guard against double-delete | Memory safety |
113
+
114
+ ---
115
+
116
+ ## What agents MUST NOT do
117
+
118
+ - **Do not** edit files under `dist/` — these are build artefacts
119
+ - **Do not** add `// eslint-disable` or `@ts-ignore` without explaining why in the PR
120
+ - **Do not** introduce new peer dependencies without updating `peerDependencies` in `package.json`
121
+ - **Do not** call `vertexAttribPointer` inside the render loop
122
+ - **Do not** call `transferControlToOffscreen` more than once per canvas element
123
+ - **Do not** use `window.*` APIs directly inside `worker/index.ts` (worker context)
124
+ - **Do not** commit if `bun test:claude` fails
125
+ - **Do not** change the module format from ESM to CJS — consumers depend on tree-shaking
126
+ - **Do not** modify `CLAUDE.md` unless explicitly asked — it is the authoritative agent guide
127
+
128
+ ---
129
+
130
+ ## Testing notes
131
+
132
+ - Tests run under jsdom with a WebGL mock (`tests/setup.js`). `navigator.gpu` is absent — all tests exercise the WebGL path.
133
+ - `FluidText`/`FluidImage` are `forwardRef` ExoticComponents — check `$$typeof`, not `typeof`.
134
+ - React component mocks require `.ts` extension: `vi.mock('../../src/fluid-controller.ts', ...)`
135
+ - Worker mock: `vi.mock('../src/worker/index.ts?worker&inline', ...)`
136
+
137
+ ---
138
+
139
+ ## Where to find more
140
+
141
+ | Resource | Path |
142
+ |----------|------|
143
+ | Deep architecture guide (Claude-specific) | [CLAUDE.md](./CLAUDE.md) |
144
+ | Prioritised bug/feature backlog | [backlog.md](./backlog.md) |
145
+ | Version history | [changelog.md](./changelog.md) |
146
+ | Live demo source | [demo/src/examples/](./demo/src/examples/) |
147
+ | npm package | https://www.npmjs.com/package/@jayf0x/fluidity-js |
148
+ | Live demo | https://jayf0x.github.io/fluidity |
@@ -0,0 +1,45 @@
1
+ # Contributing to fluidity-js
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/jayf0x/fluidity
7
+ cd fluidity
8
+ bun install
9
+ ```
10
+
11
+ Run tests:
12
+
13
+ ```bash
14
+ bun test:claude
15
+ ```
16
+
17
+ Run the demo (requires Node 20):
18
+
19
+ ```bash
20
+ cd demo
21
+ PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun install
22
+ PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun dev
23
+ ```
24
+
25
+ ## Before opening a PR
26
+
27
+ - All 83 tests pass (`bun test:claude`)
28
+ - No new peer dependencies without prior discussion
29
+ - Code formatted with `bun format`
30
+ - See [AGENTS.md](./AGENTS.md) for code style and architecture constraints
31
+
32
+ ## Where to look
33
+
34
+ | Goal | Files |
35
+ |------|-------|
36
+ | Add/change a visual effect | `src/core/shaders.ts`, `src/core/wgsl-shaders.ts`, `src/core/simulation.ts` |
37
+ | Add a new prop | `src/globals.d.ts`, `src/react/FluidText.tsx` or `FluidImage.tsx`, `src/core/config.ts` |
38
+ | Add a preset | `src/core/config.ts` |
39
+ | Fix a simulation bug | `src/core/simulation.ts`, `src/core/gl-utils.ts`, `src/core/gpu-utils.ts` |
40
+ | Fix React lifecycle | `src/react/useFluid.ts`, `src/fluid-controller.ts` |
41
+ | Fix worker communication | `src/worker/index.ts`, `src/fluid-controller.ts` |
42
+
43
+ ## Reporting issues
44
+
45
+ Use the GitHub issue templates — include a minimal repro snippet and your browser/renderer info.
package/README.md CHANGED
@@ -1,87 +1,81 @@
1
- # fluidity-js - 🔥Upgrade your UX🔥
1
+ # fluidity-js Upgrade your UX
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@jayf0x/fluidity-js)](https://www.npmjs.com/package/@jayf0x/fluidity-js)
3
+ [![npm version](https://img.shields.io/npm/v/@jayf0x/fluidity-js)](https://www.npmjs.com/package/@jayf0x/fluidity-js)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@jayf0x/fluidity-js)](https://www.npmjs.com/package/@jayf0x/fluidity-js)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@jayf0x/fluidity-js)](https://bundlephobia.com/package/@jayf0x/fluidity-js)
4
6
  [![license](https://img.shields.io/npm/l/@jayf0x/fluidity-js)](./LICENSE)
5
- [![size](https://img.shields.io/bundlephobia/minzip/@jayf0x/fluidity-js)](https://bundlephobia.com/package/@jayf0x/fluidity-js)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](./tsconfig.json)
8
+ [![CI](https://github.com/jayf0x/fluidity/actions/workflows/ci.yml/badge.svg)](https://github.com/jayf0x/fluidity/actions/workflows/ci.yml)
6
9
 
7
- <a href="https://raw.githubusercontent.com/jayf0x/fluidity/main/assets/preview.gif">
10
+ <a href="https://jayf0x.github.io/fluidity">
8
11
  <p align="center">
9
- <img src="assets/preview.gif" alt="preview" height="300px"/>
12
+ <img src="assets/preview.gif" alt="Fluid text and image effects in React" height="300px"/>
10
13
  </p>
11
- <p align="center"><strong>Live demo →</strong></p>
14
+ <p align="center"><strong>Demo & Examples →</strong></p>
12
15
  </a>
13
16
 
17
+ ## Quickstart
18
+
14
19
  ```bash
15
20
  bun add @jayf0x/fluidity-js
16
- # or: npm install / yarn add / pnpm add
21
+ # npm / pnpm / aube
17
22
  ```
18
23
 
19
- > **WebGPU-first** 🔥 — falls back automatically to WebGL2 / WebGL1.
20
-
21
- ---
22
-
23
- ## React examples
24
-
25
- **Text that moves with your cursor:**
26
-
27
24
  ```tsx
28
- import { FluidText } from '@jayf0x/fluidity-js';
25
+ import { FluidImage, FluidText } from '@jayf0x/fluidity-js';
29
26
 
30
- export function Hero() {
27
+ // Fluid text — reacts to cursor movement
28
+ export const Hero = () => {
31
29
  return (
32
30
  <div style={{ width: '100%', height: 400 }}>
33
31
  <FluidText text="Hello World" fontSize={140} color="#ffffff" />
34
32
  </div>
35
33
  );
36
- }
37
- ```
38
-
39
- **Full-bleed image cover — one line to make it alive:**
34
+ };
40
35
 
41
- ```tsx
42
- import { FluidImage } from '@jayf0x/fluidity-js';
43
-
44
- export function Cover() {
36
+ // Full-bleed fluid image
37
+ export const Cover = () => {
45
38
  return (
46
39
  <div style={{ width: '100%', height: '100vh' }}>
47
40
  <FluidImage src="/hero.jpg" algorithm="aurora" />
48
41
  </div>
49
42
  );
50
- }
43
+ };
51
44
  ```
52
45
 
53
- **Go further — presets, algorithms, live config:**
46
+ ---
54
47
 
55
- ```tsx
56
- <FluidText text="Wicked" preset="neon" algorithm="glass" />
57
- <FluidImage src="/poster.jpg" algorithm="ripple" config={{ curl: 0.4, splatRadius: 0.008 }} />
58
- <FluidText text="Chill" preset="calm" quality={{ dpr: 1, sim: 0.75 }} />
59
- ```
48
+ ## Programmatic control
60
49
 
61
- **Trigger effects programmatically:**
50
+ Use `ref` to trigger effects from code — scroll-driven splats, click bursts, attract particles:
62
51
 
63
52
  ```tsx
64
53
  import { useRef } from 'react';
65
54
 
66
55
  import { FluidText } from '@jayf0x/fluidity-js';
67
56
 
68
- export function Interactive() {
57
+ export const Interactive = () => {
69
58
  const fluid = useRef<FluidHandle>(null);
70
59
 
71
60
  return (
72
61
  <>
73
62
  <div style={{ width: '100%', height: 400 }}>
74
- <FluidText ref={fluid} text="Touch me" fontSize={120} color="#fff" />
63
+ <FluidText ref={fluid} text="Splash!" fontSize={120} color="#fff" />
75
64
  </div>
76
65
  <button onClick={() => fluid.current?.splat(200, 200, 8, -4)}>Splat</button>
77
66
  <button onClick={() => fluid.current?.updateConfig({ curl: 0.5 })}>Swirl</button>
78
67
  <button onClick={() => fluid.current?.reset()}>Reset</button>
79
68
  </>
80
69
  );
81
- }
70
+ };
82
71
  ```
83
72
 
84
- Official examples [`demo/src/examples/`](./demo/src/examples/)
73
+ | Method | What it does |
74
+ | -------------------------------- | --------------------------------------------------------- |
75
+ | `reset()` | Restart the simulation |
76
+ | `updateConfig(patch)` | Change any config value live |
77
+ | `move({ x, y, strength? })` | Simulate a pointer move |
78
+ | `splat(x, y, vx, vy, strength?)` | Inject fluid directly — safe to call many times per frame |
85
79
 
86
80
  ---
87
81
 
@@ -126,13 +120,13 @@ Official examples → [`demo/src/examples/`](./demo/src/examples/)
126
120
 
127
121
  ## Algorithms
128
122
 
129
- | Value | Character |
130
- | ------------ | ------------------------------------------------- |
131
- | `'standard'` | Colour overlay + gentle refraction (default) |
132
- | `'glass'` | Strong UV distortion only — bent-glass, no colour |
133
- | `'ink'` | Dense opaque pigment that accumulates and stains |
134
- | `'aurora'` | Velocity-field UV warp — liquid metal / lava-lamp |
135
- | `'ripple'` | Exaggerated normals + Fresnel rim — still water |
123
+ | Value | Vibe |
124
+ | ------------ | ------------------------------------------------ |
125
+ | `'standard'` | Colour overlay + gentle refraction (default) |
126
+ | `'glass'` | Bent-glass distortion, no colour |
127
+ | `'ink'` | Dense opaque pigment that accumulates and stains |
128
+ | `'aurora'` | Liquid metal / lava-lamp |
129
+ | `'ripple'` | Still water surface with Fresnel rim |
136
130
 
137
131
  ```tsx
138
132
  <FluidImage src="/photo.jpg" algorithm="aurora" />
@@ -143,72 +137,63 @@ Official examples → [`demo/src/examples/`](./demo/src/examples/)
143
137
 
144
138
  ## Quality
145
139
 
146
- `quality` controls rendering resolution on two independent axes. Both props are reactive — you can adjust them at runtime.
147
-
148
- | Field | Range | Default | Description |
149
- | ----- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
150
- | `dpr` | 0.1 – 1 | `1` | Canvas backing resolution as a fraction of `devicePixelRatio`. `0.5` on a Retina screen renders at 1× instead of 2×, saving ~75% fill rate. |
151
- | `sim` | 0.1 – 1 | `0.5` | Simulation FBO size as a fraction of canvas size. Lower = cheaper GPU, less fluid detail. |
152
-
153
- ```tsx
154
- <FluidText text="hello" quality={{ dpr: 0.75, sim: 0.25 }} />
155
- ```
140
+ Control rendering resolution on two independent axes both reactive at runtime.
156
141
 
157
- `dpr` and `sim` are independent you can run a sharp canvas at a coarser simulation:
142
+ | Field | Range | Default | What it does |
143
+ | ----- | ----- | ------- | ----------------------------------------------------------------------------------------- |
144
+ | `dpr` | 0.1–1 | `1` | Canvas resolution as fraction of screen pixel ratio. `0.5` on Retina saves ~75% GPU fill. |
145
+ | `sim` | 0.1–1 | `0.5` | Simulation resolution. Lower = cheaper, less detail. |
158
146
 
159
147
  ```tsx
160
- // Sharp display, cheap simulation
148
+ // Sharp canvas, cheap simulation
161
149
  <FluidImage src="/hero.jpg" quality={{ dpr: 1, sim: 0.2 }} />
162
150
 
163
- // Lower display res, full simulation quality
151
+ // Lower canvas res, full simulation quality
164
152
  <FluidImage src="/hero.jpg" quality={{ dpr: 0.5, sim: 1 }} />
165
153
  ```
166
154
 
167
155
  ---
168
156
 
169
- ## FluidConfig
170
-
171
- | Key | Default | Description |
172
- | --------------------- | ------------------ | ----------------------------------------- |
173
- | `densityDissipation` | `0.992` | How long ink lingers (0–1) |
174
- | `velocityDissipation` | `0.93` | How fast velocity decays (0–1) |
175
- | `pressureIterations` | `1` | Jacobi iterations — accuracy vs. cost |
176
- | `curl` | `0.0001` | Vorticity / swirl. `0.2`–`0.5` for eddies |
177
- | `splatRadius` | `0.004` | Brush radius |
178
- | `splatForce` | `0.91` | Force applied by brush |
179
- | `refraction` | `0.25` | Background warp strength |
180
- | `specularExp` | `1.01` | Specular highlight sharpness |
181
- | `shine` | `0.01` | Highlight intensity |
182
- | `waterColor` | `[0, 0, 0]` | Base fluid colour `[R, G, B]` (0–1) |
183
- | `glowColor` | `[0.7, 0.85, 1.0]` | Glow / specular colour `[R, G, B]` (0–1) |
184
- | `warpStrength` | `0.015` | UV warp intensity (`aurora` algorithm) |
157
+ ## FluidConfig reference
158
+
159
+ | Key | Default | Description |
160
+ | --------------------- | ------------------ | ----------------------------------------------- |
161
+ | `densityDissipation` | `0.992` | How long ink lingers (0–1) |
162
+ | `velocityDissipation` | `0.93` | How fast fluid slows down (0–1) |
163
+ | `pressureIterations` | `1` | Quality vs. cost trade-off |
164
+ | `curl` | `0.0001` | Swirl intensity. `0.2`–`0.5` for visible eddies |
165
+ | `splatRadius` | `0.004` | Brush radius |
166
+ | `splatForce` | `0.91` | Force applied by brush |
167
+ | `refraction` | `0.25` | Background warp strength |
168
+ | `specularExp` | `1.01` | Specular highlight sharpness |
169
+ | `shine` | `0.01` | Highlight intensity |
170
+ | `waterColor` | `[0, 0, 0]` | Base fluid colour `[R, G, B]` (0–1) |
171
+ | `glowColor` | `[0.7, 0.85, 1.0]` | Glow / specular colour `[R, G, B]` (0–1) |
172
+ | `warpStrength` | `0.015` | UV warp intensity (`aurora` algorithm) |
185
173
 
186
174
  ---
187
175
 
188
- ## FluidHandle (ref)
176
+ ## Browser support
189
177
 
190
- | Method | Description |
191
- | -------------------------------- | ----------------------------------------------------- |
192
- | `reset()` | Re-initialise simulation and reload source |
193
- | `updateConfig(patch)` | Merge a partial config update into running sim |
194
- | `move({ x, y, strength? })` | Programmatic pointer input (canvas-relative px) |
195
- | `splat(x, y, vx, vy, strength?)` | Inject a fluid splat directly — safe to call N×/frame |
178
+ Works in all modern browsers. Automatically picks the best renderer available — no configuration needed.
196
179
 
197
- ---
198
-
199
- ## Presets
180
+ | Browser | Support |
181
+ | ------------- | ------- |
182
+ | Chrome 113+ | ✅ |
183
+ | Edge 113+ | ✅ |
184
+ | Firefox | ✅ |
185
+ | Safari 17+ | ✅ |
186
+ | Safari < 17 | ✅ |
187
+ | Mobile Chrome | ✅ |
200
188
 
201
- ```tsx
202
- <FluidText text="Wicked" preset="neon" />
203
- <FluidText text="Wicked" preset="calm" />
204
- ```
189
+ ---
205
190
 
206
- Available: `calm` · `sand` · `wave` · `neon` · `smoke`
191
+ ## Contributing
207
192
 
208
- `preset` is reactive changing it re-applies the preset config. Any `config` values you pass override the preset. `algorithm` prop also overrides the preset's algorithm.
193
+ Issues and PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup and [AGENTS.md](./AGENTS.md) for code conventions.
209
194
 
210
195
  ---
211
196
 
212
197
  ## License
213
198
 
214
- [MIT](./LICENSE)
199
+ [MIT](./LICENSE) © [jayf0x](https://github.com/jayf0x)