@jayf0x/fluidity-js 0.2.3 → 0.2.5

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 CHANGED
@@ -66,7 +66,7 @@ PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun deploy # → gh-pages
66
66
 
67
67
  ```tsx
68
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 }} />
69
+ <FluidImage src="/hero.jpg" algorithm="aurora" pixelRatio={1} simResolution={0.5} />
70
70
  ```
71
71
 
72
72
  ### FluidHandle ref
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # fluidity-js — WebGPU Fluid Simulation for React
1
+ # fluidity-js — Upgrade your UX
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@jayf0x/fluidity-js)](https://www.npmjs.com/package/@jayf0x/fluidity-js)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@jayf0x/fluidity-js)](https://www.npmjs.com/package/@jayf0x/fluidity-js)
@@ -7,105 +7,75 @@
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](./tsconfig.json)
8
8
  [![CI](https://github.com/jayf0x/fluidity/actions/workflows/ci.yml/badge.svg)](https://github.com/jayf0x/fluidity/actions/workflows/ci.yml)
9
9
 
10
- **Real-time Navier-Stokes fluid dynamics for React — interactive water, ink, glass, and aurora effects on text and images. WebGPU-first with automatic WebGL2/WebGL1 fallback. Runs in a Web Worker for zero jank.**
11
-
12
10
  <a href="https://jayf0x.github.io/fluidity">
13
11
  <p align="center">
14
- <img src="assets/preview.gif" alt="WebGPU fluid simulation — fluid text and image effects in React" height="300px"/>
12
+ <img src="assets/preview.gif" alt="Fluid text and image effects in React" height="300px"/>
15
13
  </p>
16
- <p align="center"><strong>Live demo →</strong></p>
14
+ <p align="center"><strong>Demo & Examples →</strong></p>
17
15
  </a>
18
16
 
17
+ ## Quickstart
18
+
19
19
  ```bash
20
- npm install @jayf0x/fluidity-js
21
- # bun add / yarn add / pnpm add
20
+ bun add @jayf0x/fluidity-js
21
+ # npm / pnpm / aube
22
22
  ```
23
23
 
24
- > **WebGPU-first** — falls back automatically to WebGL2 → WebGL1. No configuration required.
25
-
26
- ---
27
-
28
- ## Features
29
-
30
- - **Fluid text effects** — wrap fluid around text as obstacle + background
31
- - **Fluid image effects** — apply fluid over any bitmap with `cover`/`contain` sizing
32
- - **5 visual algorithms** — standard, glass, ink, aurora, ripple
33
- - **5 presets** — calm, sand, wave, neon, smoke
34
- - **Web Worker** — simulation runs off the main thread via OffscreenCanvas
35
- - **WebGPU + WebGL2 + WebGL1** — widest browser compatibility
36
- - **Fully typed** — ambient TypeScript types, no import needed
37
- - **React 17+** — forwardRef components, StrictMode safe
38
- - **Programmatic API** — `splat()`, `move()`, `updateConfig()`, `reset()`
39
- - **Reactive props** — change algorithm, preset, quality, config at runtime
40
-
41
- ---
42
-
43
- ## Quickstart
44
-
45
24
  ```tsx
46
- import { FluidText, FluidImage } from '@jayf0x/fluidity-js';
25
+ import { FluidImage, FluidText } from '@jayf0x/fluidity-js';
47
26
 
48
27
  // Fluid text — reacts to cursor movement
49
- export function Hero() {
28
+ export const Hero = () => {
50
29
  return (
51
30
  <div style={{ width: '100%', height: 400 }}>
52
31
  <FluidText text="Hello World" fontSize={140} color="#ffffff" />
53
32
  </div>
54
33
  );
55
- }
34
+ };
56
35
 
57
36
  // Full-bleed fluid image
58
- export function Cover() {
37
+ export const Cover = () => {
59
38
  return (
60
39
  <div style={{ width: '100%', height: '100vh' }}>
61
40
  <FluidImage src="/hero.jpg" algorithm="aurora" />
62
41
  </div>
63
42
  );
64
- }
65
- ```
66
-
67
- **Presets and algorithms:**
68
-
69
- ```tsx
70
- <FluidText text="Wicked" preset="neon" algorithm="glass" />
71
- <FluidImage src="/poster.jpg" algorithm="ripple" config={{ curl: 0.4, splatRadius: 0.008 }} />
72
- <FluidText text="Chill" preset="calm" quality={{ dpr: 1, sim: 0.75 }} />
43
+ };
73
44
  ```
74
45
 
75
46
  ---
76
47
 
77
48
  ## Programmatic control
78
49
 
79
- Use `ref` to trigger effects from code — attractors, scroll-driven splats, click bursts:
50
+ Use `ref` to trigger effects from code — scroll-driven splats, click bursts, attract particles:
80
51
 
81
52
  ```tsx
82
53
  import { useRef } from 'react';
54
+
83
55
  import { FluidText } from '@jayf0x/fluidity-js';
84
56
 
85
- export function Interactive() {
57
+ export const Interactive = () => {
86
58
  const fluid = useRef<FluidHandle>(null);
87
59
 
88
60
  return (
89
61
  <>
90
62
  <div style={{ width: '100%', height: 400 }}>
91
- <FluidText ref={fluid} text="Touch me" fontSize={120} color="#fff" />
63
+ <FluidText ref={fluid} text="Splash!" fontSize={120} color="#fff" />
92
64
  </div>
93
65
  <button onClick={() => fluid.current?.splat(200, 200, 8, -4)}>Splat</button>
94
66
  <button onClick={() => fluid.current?.updateConfig({ curl: 0.5 })}>Swirl</button>
95
67
  <button onClick={() => fluid.current?.reset()}>Reset</button>
96
68
  </>
97
69
  );
98
- }
70
+ };
99
71
  ```
100
72
 
101
- ### FluidHandle methods
102
-
103
- | Method | Description |
104
- |--------|-------------|
105
- | `reset()` | Re-initialise simulation and reload source |
106
- | `updateConfig(patch)` | Merge a partial config update into the running sim |
107
- | `move({ x, y, strength? })` | Programmatic pointer input (canvas-relative px) |
108
- | `splat(x, y, vx, vy, strength?)` | Inject a fluid splat directly — safe to call N×/frame |
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 |
109
79
 
110
80
  ---
111
81
 
@@ -113,174 +83,129 @@ export function Interactive() {
113
83
 
114
84
  ### FluidText
115
85
 
116
- | Prop | Type | Default |
117
- |------|------|---------|
118
- | `text` | `string` | — |
119
- | `fontSize` | `number` | `100` |
120
- | `color` | `string` | `'#ffffff'` |
121
- | `fontFamily` | `string` | `'sans-serif'` |
122
- | `fontWeight` | `string \| number` | `900` |
86
+ | Prop | Type | Default |
87
+ | ------------ | ------------------ | -------------- |
88
+ | `text` | `string` | — |
89
+ | `fontSize` | `number` | `100` |
90
+ | `color` | `string` | `'#ffffff'` |
91
+ | `fontFamily` | `string` | `'sans-serif'` |
92
+ | `fontWeight` | `string \| number` | `900` |
123
93
 
124
94
  ### FluidImage
125
95
 
126
- | Prop | Type | Default |
127
- |------|------|---------|
128
- | `src` | `string` | — |
96
+ | Prop | Type | Default |
97
+ | ----------- | ------------------ | --------- |
98
+ | `src` | `string` | — |
129
99
  | `imageSize` | `string \| number` | `'cover'` |
130
- | `effect` | `number` | `0` |
100
+ | `effect` | `number` | `0` |
131
101
 
132
102
  ### Shared props
133
103
 
134
- | Prop | Type | Default |
135
- |------|------|---------|
136
- | `config` | `Partial<FluidConfig>` | |
137
- | `preset` | `PresetKey` | — |
138
- | `algorithm` | `FluidAlgorithm` | `'standard'` |
139
- | `quality` | `FluidQuality` | `{ dpr: 1, sim: 0.5 }` |
140
- | `backgroundColor` | `string` | `'#0a0a0a'` |
141
- | `backgroundSrc` | `string` | |
142
- | `backgroundSize` | `string \| number` | `'cover'` |
143
- | `isMouseEnabled` | `boolean` | `true` |
144
- | `isWorkerEnabled` | `boolean` | `true` |
145
- | `useWebGPU` | `boolean` | `true` |
146
- | `className` | `string` | |
147
- | `style` | `CSSProperties` | |
104
+ | Prop | Type | Default |
105
+ | --------------------- | ------------------ | ------------ |
106
+ | `algorithm` | `FluidAlgorithm` | `'aurora'` |
107
+ | `preset` | `PresetKey` | — |
108
+ | `pixelRatio` | `number` | `1` |
109
+ | `simResolution` | `number` | `0.5` |
110
+ | `densityDissipation` | `number` | `0.83` |
111
+ | `velocityDissipation` | `number` | `0.91` |
112
+ | `pressureIterations` | `number` | `1` |
113
+ | `curl` | `number` | `0` |
114
+ | `splatRadius` | `number` | `0.1` |
115
+ | `splatForce` | `number` | `0.08` |
116
+ | `refraction` | `number` | `1.0` |
117
+ | `specularExp` | `number` | `0` |
118
+ | `shine` | `number` | `0` |
119
+ | `waterColor` | `FluidColor` | `'#000000'` |
120
+ | `glowColor` | `FluidColor` | `'#b3d9ff'` |
121
+ | `warpStrength` | `number` | `0.04` |
122
+ | `backgroundColor` | `string` | `'#0a0a0a'` |
123
+ | `backgroundSrc` | `string` | — |
124
+ | `backgroundSize` | `string \| number` | `'cover'` |
125
+ | `mouseEnabled` | `boolean` | `true` |
126
+ | `workerEnabled` | `boolean` | `true` |
127
+ | `webGPUEnabled` | `boolean` | `true` |
128
+ | `alphaEnabled` | `boolean` | `true` |
129
+ | `className` | `string` | — |
130
+ | `style` | `CSSProperties` | — |
148
131
 
149
132
  ---
150
133
 
151
134
  ## Algorithms
152
135
 
153
- | Value | Character |
154
- |-------|-----------|
155
- | `'standard'` | Colour overlay + gentle refraction (default) |
156
- | `'glass'` | UV distortion only bent-glass, no colour |
157
- | `'ink'` | Dense opaque pigment that accumulates and stains |
158
- | `'aurora'` | Velocity-field UV warp liquid metal / lava-lamp |
159
- | `'ripple'` | Exaggerated normals + Fresnel rim — still water |
136
+ | Value | Vibe |
137
+ | ------------ | ------------------------------------------------ |
138
+ | `'aurora'` | Liquid metal / lava-lamp (default) |
139
+ | `'standard'` | Colour overlay + gentle refraction |
140
+ | `'glass'` | Bent-glass distortion, no colour |
141
+ | `'ink'` | Dense opaque pigment that accumulates and stains |
142
+ | `'ripple'` | Still water surface with Fresnel rim |
160
143
 
161
144
  ```tsx
162
145
  <FluidImage src="/photo.jpg" algorithm="aurora" />
163
- <FluidText text="fluid" algorithm="ripple" config={{ warpStrength: 0.03 }} />
164
- ```
165
-
166
- ---
167
-
168
- ## Presets
169
-
170
- ```tsx
171
- <FluidText text="Wicked" preset="neon" />
172
- <FluidText text="Calm vibes" preset="calm" />
146
+ <FluidText text="fluid" algorithm="ripple" warpStrength={0.03} />
173
147
  ```
174
148
 
175
- Available: `calm` · `sand` · `wave` · `neon` · `smoke`
176
-
177
- Presets are reactive — changing the prop at runtime re-applies the config. Any `config` values you pass override the preset. The `algorithm` prop also overrides the preset's algorithm.
178
-
179
149
  ---
180
150
 
181
151
  ## Quality
182
152
 
183
- `quality` controls rendering resolution on two independent axes. Both are reactive at runtime.
153
+ Control rendering resolution on two independent axes both reactive at runtime.
184
154
 
185
- | Field | Range | Default | Description |
186
- |-------|-------|---------|-------------|
187
- | `dpr` | 0.1–1 | `1` | Canvas backing resolution as fraction of `devicePixelRatio`. `0.5` on Retina saves ~75% fill rate. |
188
- | `sim` | 0.1–1 | `0.5` | Simulation FBO size as fraction of canvas size. Lower = cheaper GPU, less fluid detail. |
155
+ | Prop | Range | Default | What it does |
156
+ | --------------- | ----- | ------- | ----------------------------------------------------------------------------------------- |
157
+ | `pixelRatio` | 0.1–1 | `1` | Canvas resolution as fraction of devicePixelRatio. `0.5` on Retina saves ~75% GPU fill. |
158
+ | `simResolution` | 0.1–1 | `0.5` | Simulation FBO size. Lower = cheaper, less detail. |
189
159
 
190
160
  ```tsx
191
161
  // Sharp canvas, cheap simulation
192
- <FluidImage src="/hero.jpg" quality={{ dpr: 1, sim: 0.2 }} />
162
+ <FluidImage src="/hero.jpg" pixelRatio={1} simResolution={0.2} />
193
163
 
194
164
  // Lower canvas res, full simulation quality
195
- <FluidImage src="/hero.jpg" quality={{ dpr: 0.5, sim: 1 }} />
165
+ <FluidImage src="/hero.jpg" pixelRatio={0.5} simResolution={1} />
196
166
  ```
197
167
 
198
168
  ---
199
169
 
200
- ## FluidConfig reference
201
-
202
- | Key | Default | Description |
203
- |-----|---------|-------------|
204
- | `densityDissipation` | `0.992` | How long ink lingers (0–1) |
205
- | `velocityDissipation` | `0.93` | How fast velocity decays (0–1) |
206
- | `pressureIterations` | `1` | Jacobi iterations accuracy vs. cost |
207
- | `curl` | `0.0001` | Vorticity / swirl. `0.2`–`0.5` for visible eddies |
208
- | `splatRadius` | `0.004` | Brush radius |
209
- | `splatForce` | `0.91` | Force applied by brush |
210
- | `refraction` | `0.25` | Background warp strength |
211
- | `specularExp` | `1.01` | Specular highlight sharpness |
212
- | `shine` | `0.01` | Highlight intensity |
213
- | `waterColor` | `[0, 0, 0]` | Base fluid colour `[R, G, B]` (0–1) |
214
- | `glowColor` | `[0.7, 0.85, 1.0]` | Glow / specular colour `[R, G, B]` (0–1) |
215
- | `warpStrength` | `0.015` | UV warp intensity (`aurora` algorithm) |
216
-
217
- ---
218
-
219
- ## TypeScript
220
-
221
- All types are globally ambient — no import required:
222
-
223
- ```ts
224
- // Available globally after installing the package:
225
- // FluidConfig, FluidHandle, FluidAlgorithm, FluidQuality, PresetKey
226
- ```
227
-
228
- ---
229
-
230
- ## How it works
231
-
232
- fluidity-js runs a real-time **Navier-Stokes fluid simulation** entirely on the GPU:
233
-
234
- 1. **Advect velocity** — move velocity field along itself; obstacle mask zeroes inside text/image
235
- 2. **Advect density** — move ink/colour field with velocity
236
- 3. **Curl → vorticity confinement** — preserve swirling detail
237
- 4. **Splat** — inject velocity + density at cursor or programmatic positions
238
- 5. **Divergence → pressure solve** — N Jacobi iterations for incompressibility
239
- 6. **Gradient subtract** — enforce divergence-free velocity
240
- 7. **Display pass** — 5 texture units: density, obstacle, background, coverage mask, velocity; 5 algorithms selectable by uniform
241
-
242
- The simulation runs in a **Web Worker** by default, communicating with the React component via `postMessage`. The canvas is transferred to the worker via `OffscreenCanvas.transferControlToOffscreen()` for true off-thread rendering.
243
-
244
- **WebGPU path** uses render pipelines via `@webgpu/types`. **WebGL path** uses double-framebuffer objects (ping-pong FBOs) with GLSL shaders.
170
+ ## Simulation props reference
171
+
172
+ Simulation props that have a physics range accept a **normalized `0–1` value** — no need to know the raw shader units. Values outside `[0, 1]` are passed through as raw physics values for advanced overrides.
173
+
174
+ | Prop | Default | Range | Physics range | Description |
175
+ | --------------------- | ------- | ------- | --------------- | ----------------------------------------------- |
176
+ | `densityDissipation` | `0.83` | `0–1` | `0.94–1.0` | How long ink lingers |
177
+ | `velocityDissipation` | `0.91` | `0–1` | `0.9–0.999` | How fast fluid slows down |
178
+ | `pressureIterations` | `1` | `1–50` | | Pressure solve quality vs. cost |
179
+ | `curl` | `0` | `0–1` | | Swirl intensity |
180
+ | `splatRadius` | `0.1` | `0–1` | `0.001–0.04` | Brush radius |
181
+ | `splatForce` | `0.08` | `0–1` | `0.1–5.0` | Force applied by brush |
182
+ | `refraction` | `1.0` | `0–1` | | Background warp strength |
183
+ | `specularExp` | `0` | `0–1` | `0.1–10` | Specular highlight sharpness |
184
+ | `shine` | `0` | `01` | `0–0.15` | Highlight intensity |
185
+ | `warpStrength` | `0.04` | `0–1` | `0.001–0.1` | UV warp intensity (`aurora` algorithm) |
186
+ | `waterColor` | `#000` | — | — | Base fluid colour (hex or `[R, G, B]` 0–1) |
187
+ | `glowColor` | `#b3d9ff`| — | — | Glow / specular colour (hex or `[R, G, B]` 0–1) |
245
188
 
246
189
  ---
247
190
 
248
191
  ## Browser support
249
192
 
250
- | Browser | WebGPU | WebGL2 | WebGL1 |
251
- |---------|--------|--------|--------|
252
- | Chrome 113+ | ✅ | ✅ | ✅ |
253
- | Edge 113+ | ✅ | ✅ | ✅ |
254
- | Firefox | — | ✅ | ✅ |
255
- | Safari 17+ | ✅ (partial) | ✅ | ✅ |
256
- | Safari < 17 | — | ✅ | ✅ |
257
- | Mobile Chrome | — | ✅ | ✅ |
193
+ Works in all modern browsers. Automatically picks the best renderer available — no configuration needed.
258
194
 
259
- Fallback is automatic — no configuration needed.
260
-
261
- ---
262
-
263
- ## Examples
264
-
265
- Full working examples live in [`demo/src/examples/`](./demo/src/examples/):
266
-
267
- - `TextExample.tsx` — FluidText with Leva controls
268
- - `ImageExample.tsx` — FluidImage with backgroundSrc
269
- - `SplashExample.tsx` — full-page fluid hero
270
- - `SplitExample.tsx` — split-screen comparison
271
- - `PresetsExample.tsx` — all presets side by side
272
-
273
- [**Open live demo →**](https://jayf0x.github.io/fluidity)
195
+ | Browser | Support |
196
+ | ------------- | ------- |
197
+ | Chrome 113+ | ✅ |
198
+ | Edge 113+ | ✅ |
199
+ | Firefox | ✅ |
200
+ | Safari 17+ | ✅ |
201
+ | Safari < 17 | ✅ |
202
+ | Mobile Chrome | ✅ |
274
203
 
275
204
  ---
276
205
 
277
206
  ## Contributing
278
207
 
279
- Issues and PRs welcome. See [AGENTS.md](./AGENTS.md) for agent-specific instructions and code conventions.
280
-
281
- 1. Fork → branch → PR against `main`
282
- 2. Run `bun test:claude` before pushing — all 83 tests must pass
283
- 3. No new dependencies without discussion
208
+ Issues and PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup and [AGENTS.md](./AGENTS.md) for code conventions.
284
209
 
285
210
  ---
286
211
 
package/dist/globals.d.ts CHANGED
@@ -5,15 +5,9 @@ type FluidAlgorithm = 'standard' | 'glass' | 'ink' | 'aurora' | 'ripple';
5
5
  /** RGB tuple (values 0–1) or a CSS hex string (#RGB, #RRGGBB, #RRGGBBAA — alpha stripped). */
6
6
  type FluidColor = [number, number, number] | `#${string}`;
7
7
 
8
- /**
9
- * Granular performance/quality controls. Both axes are independent — you can
10
- * run a sharp display at a coarser simulation, or vice versa.
11
- * Reactive: changes after mount are applied on the next animation frame.
12
- */
8
+ /** Internal quality object used by FluidController and FluidSimulation. */
13
9
  interface FluidQuality {
14
- /** devicePixelRatio multiplier for canvas backing resolution. Range [0.1, 1]. Default 1 (native). On Retina, 0.5 → 1× pixels (75% less fill). */
15
10
  dpr?: number;
16
- /** Simulation FBO size as a fraction of canvas size. Range [0.1, 1]. Default 0.5 (current behavior). Higher = more fluid detail, more GPU. */
17
11
  sim?: number;
18
12
  }
19
13
 
@@ -42,24 +36,31 @@ interface FluidHandle {
42
36
  updateConfig(config: Partial<FluidConfig>): void;
43
37
  }
44
38
 
45
- interface FluidBaseProps {
46
- /** Will apply to canvas container */
39
+ /**
40
+ * All FluidConfig fields are inherited as optional props.
41
+ * Set any simulation knob directly on the component — no config object needed.
42
+ */
43
+ interface FluidBaseProps extends Partial<FluidConfig> {
44
+ /** Applied to the canvas container element. */
47
45
  className?: string;
48
- /** Will apply to canvas container */
46
+ /** Applied to the canvas container element. */
49
47
  style?: React.CSSProperties;
50
- config?: Partial<FluidConfig>;
51
- isMouseEnabled?: boolean;
52
- isWorkerEnabled?: boolean;
53
- quality?: FluidQuality;
48
+ /** Canvas backing resolution as a fraction of devicePixelRatio. Range [0.1, 1]. Default 1 (native DPR). */
49
+ pixelRatio?: number;
50
+ /** Simulation FBO size as a fraction of canvas size. Range [0.1, 1]. Default 0.5. */
51
+ simResolution?: number;
54
52
  preset?: PresetKey;
55
- algorithm?: FluidAlgorithm;
56
53
  backgroundColor?: string;
57
54
  backgroundSrc?: string;
58
55
  backgroundSize?: string | number;
59
- /** enabled greater performance, but not every browser supports it */
60
- useWebGPU?: boolean;
61
- /** Enable transparent canvas (default true). Set false for a minor perf gain when transparency is not needed. */
62
- enableAlpha?: boolean;
56
+ /** Forward pointer events to the simulation. Default true. */
57
+ mouseEnabled?: boolean;
58
+ /** Run simulation in a Web Worker via OffscreenCanvas. Default true. */
59
+ workerEnabled?: boolean;
60
+ /** Prefer WebGPU renderer; falls back to WebGL. Default true. */
61
+ webGPUEnabled?: boolean;
62
+ /** Enable transparent canvas. Default true. Set false for a minor perf gain when transparency is not needed. */
63
+ alphaEnabled?: boolean;
63
64
  }
64
65
 
65
66
  interface FluidTextProps extends FluidBaseProps {