react-smokey-fluid-cursor 1.0.4 → 2.1.0

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.
@@ -0,0 +1,409 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * This file defines the foundational TypeScript interfaces
5
+ * and type aliases used throughout the Smokey Fluid simulation engine.
6
+ *
7
+ * It abstracts WebGL object types (FBOs, textures, uniforms, etc.)
8
+ * to provide clear, type-safe contracts for interacting with GPU resources.
9
+ *
10
+ * Developers integrating or extending this package should primarily
11
+ * adjust configuration options defined in `ISmokeyFluidConfig`.
12
+ */
13
+ /**
14
+ * Generic alias for either WebGL1 or WebGL2 rendering contexts.
15
+ *
16
+ * - WebGLRenderingContext → legacy WebGL1 API
17
+ * - WebGL2RenderingContext → modern API with extended texture formats and MRT support
18
+ *
19
+ * This alias allows code to remain compatible across both WebGL versions.
20
+ */
21
+ type GL = WebGLRenderingContext | WebGL2RenderingContext;
22
+ /**
23
+ * Describes a format pair used for texture or renderbuffer configuration.
24
+ *
25
+ * - `internalFormat`: how GPU stores the texture internally (e.g., RGBA16F)
26
+ * - `format`: how the data is laid out when uploading pixels (e.g., RGBA)
27
+ *
28
+ * Used for compatibility mapping between WebGL1 and WebGL2.
29
+ */
30
+ interface GLFormat {
31
+ internalFormat: number;
32
+ format: number;
33
+ }
34
+ /**
35
+ * Represents detected WebGL extension and format capabilities
36
+ * for the current context. This allows the engine to dynamically
37
+ * choose the optimal texture formats and filtering modes.
38
+ *
39
+ * Usually populated at initialization after checking for extensions like:
40
+ * - OES_texture_half_float
41
+ * - OES_texture_float_linear
42
+ * - EXT_color_buffer_float
43
+ */
44
+ interface GLExtInfo {
45
+ /** Supported RGBA floating-point texture format */
46
+ formatRGBA: GLFormat | null;
47
+ /** Supported RG (two-channel) texture format (WebGL2 only) */
48
+ formatRG: GLFormat | null;
49
+ /** Supported R (single-channel) texture format (WebGL2 only) */
50
+ formatR: GLFormat | null;
51
+ /** GL constant for HALF_FLOAT type (e.g., gl.HALF_FLOAT_OES) */
52
+ halfFloatTexType: number;
53
+ /** Whether linear filtering (smooth interpolation) for float textures is available */
54
+ supportLinearFiltering: boolean;
55
+ /** True if running on WebGL2 (vs WebGL1 + extensions) */
56
+ isWebGL2: boolean;
57
+ }
58
+ /**
59
+ * Framebuffer Object (FBO) descriptor — wraps a texture and its framebuffer target.
60
+ *
61
+ * Each FBO represents a GPU render target that can store color or data fields
62
+ * (such as velocity, pressure, or density in fluid simulation).
63
+ */
64
+ interface FBO {
65
+ /** GPU texture attached to the framebuffer */
66
+ texture: WebGLTexture | null;
67
+ /** The framebuffer object itself */
68
+ fbo: WebGLFramebuffer | null;
69
+ /** Width of the texture in pixels */
70
+ width: number;
71
+ /** Height of the texture in pixels */
72
+ height: number;
73
+ /** Normalized texel size (1 / width) */
74
+ texelSizeX: number;
75
+ /** Normalized texel size (1 / height) */
76
+ texelSizeY: number;
77
+ /**
78
+ * Binds this FBO's texture to a given texture unit (slot).
79
+ * Returns the bound texture unit index for convenience.
80
+ *
81
+ * Example:
82
+ * ```ts
83
+ * const unit = velocityFBO.attach(0); // binds to TEXTURE0
84
+ * gl.uniform1i(uniforms.uVelocity, unit);
85
+ * ```
86
+ */
87
+ attach(id: number): number;
88
+ }
89
+ /**
90
+ * Represents a double-buffered framebuffer system.
91
+ *
92
+ * Used heavily in fluid simulations to "ping-pong" between two framebuffers:
93
+ * - One for reading previous state
94
+ * - One for writing the next frame
95
+ *
96
+ * After each simulation step, `swap()` is called to exchange them.
97
+ */
98
+ interface DoubleFBO {
99
+ /** Buffer width in pixels */
100
+ width: number;
101
+ /** Buffer height in pixels */
102
+ height: number;
103
+ /** Normalized texel size (1 / width) */
104
+ texelSizeX: number;
105
+ /** Normalized texel size (1 / height) */
106
+ texelSizeY: number;
107
+ /** Framebuffer currently used for reading */
108
+ read: FBO;
109
+ /** Framebuffer currently used for writing */
110
+ write: FBO;
111
+ /** Swaps read/write buffers for next iteration */
112
+ swap(): void;
113
+ }
114
+ /**
115
+ * ---------------------------------------------------------
116
+ * 🔧 Smokey Fluid Configuration
117
+ * ---------------------------------------------------------
118
+ *
119
+ * This interface defines **all configurable simulation parameters**
120
+ * that can be customized externally (e.g., by UI, app settings, or user code).
121
+ *
122
+ * Each property controls a different aspect of the fluid’s physics,
123
+ * rendering behavior, or visual appearance.
124
+ */
125
+ interface ISmokeyFluidConfig {
126
+ /** Simulation grid resolution for velocity/pressure fields (lower = faster but coarser) */
127
+ simResolution: number;
128
+ /** Visual color/dye buffer resolution (affects rendering detail) */
129
+ dyeResolution: number;
130
+ /** Resolution used for high-quality screenshots or recording */
131
+ captureResolution: number;
132
+ /** Rate at which color fades from the fluid (higher = faster fade) */
133
+ densityDissipation: number;
134
+ /** Rate at which velocity energy dissipates (higher = faster slowdown) */
135
+ velocityDissipation: number;
136
+ /** Initial pressure clear multiplier (affects stability of solver) */
137
+ pressure: number;
138
+ /** Number of Jacobi iterations when solving pressure (higher = more accurate but slower) */
139
+ pressureIteration: number;
140
+ /** Strength of vorticity confinement (adds swirling, curl-like motion) */
141
+ curl: number;
142
+ /** Base normalized radius of user splats (0–1 range relative to screen size) */
143
+ splatRadius: number;
144
+ /** Force multiplier applied when user interacts (e.g., mouse/touch input) */
145
+ splatForce: number;
146
+ /** Enable or disable lighting/shading effects for visual depth */
147
+ shading: boolean;
148
+ /** Speed at which the dynamic color palette rotates during simulation */
149
+ colorUpdateSpeed: number;
150
+ /** If true, pauses the main simulation loop (used for debugging or static rendering) */
151
+ paused: boolean;
152
+ /**
153
+ * Canvas background color (can be in 0–1 normalized range or 0–255)
154
+ * Example: `{ r: 0, g: 0, b: 0 }` for black.
155
+ */
156
+ backColor: {
157
+ r: number;
158
+ g: number;
159
+ b: number;
160
+ };
161
+ /**
162
+ * Determines whether the canvas should preserve alpha transparency.
163
+ * If `true`, blending with HTML backgrounds is allowed.
164
+ */
165
+ transparent: boolean;
166
+ /**
167
+ * ID assigned to the canvas element.
168
+ * Default is "smokey-fluid-canvas".
169
+ */
170
+ id: string;
171
+ /**
172
+ * An existing canvas to render into — an element, or a CSS selector.
173
+ *
174
+ * Takes precedence over `id` and `container`. Use this when the canvas is
175
+ * already part of your markup or managed by a framework.
176
+ */
177
+ canvas?: HTMLCanvasElement | string | null;
178
+ /**
179
+ * Element (or CSS selector) to create the canvas inside, when no canvas
180
+ * matches `canvas`/`id` yet.
181
+ *
182
+ * Defaults to `document.body`, which with `position: "fixed"` gives the
183
+ * classic full-viewport effect. Point it at a section to scope the effect
184
+ * to just that area.
185
+ */
186
+ container?: HTMLElement | string | null;
187
+ /**
188
+ * CSS position for the canvas. Use `"absolute"` to confine the effect to a
189
+ * positioned container; `"fixed"` covers the viewport. Default `"fixed"`.
190
+ */
191
+ position: "fixed" | "absolute" | "static" | "relative";
192
+ /** Stacking order of the canvas. Default `-9999` (behind page content). */
193
+ zIndex: number;
194
+ /**
195
+ * Whether the canvas receives pointer events. Default `false`, so clicks
196
+ * pass straight through to your UI.
197
+ */
198
+ pointerEvents: boolean;
199
+ /** Extra class name applied to the canvas element. */
200
+ className?: string;
201
+ /**
202
+ * Upper bound on the device pixel ratio used for the render buffers.
203
+ *
204
+ * Default `2`. Uncapped, a 3x phone renders nine times the pixels of a 1x
205
+ * display for a purely decorative effect, which drains battery and drops
206
+ * frame rate.
207
+ */
208
+ maxDpr: number;
209
+ /**
210
+ * Pause the simulation while the page is hidden (background tab). Default
211
+ * `true` — an invisible animation should not burn CPU or battery.
212
+ */
213
+ pauseOnHidden: boolean;
214
+ /**
215
+ * Honour the `prefers-reduced-motion` media query. When the visitor has
216
+ * asked for reduced motion the simulation starts paused. Default `true`.
217
+ */
218
+ respectReducedMotion: boolean;
219
+ /**
220
+ * Colour palette for the fluid, as CSS hex strings (e.g. `["#ff4ecd"]`).
221
+ *
222
+ * When omitted, colours are generated randomly across the full hue range,
223
+ * which is the original behaviour.
224
+ */
225
+ palette?: string[] | null;
226
+ /**
227
+ * Brightness multiplier applied to generated colours. Default `0.15`;
228
+ * raise it for a more saturated, higher-contrast trail.
229
+ */
230
+ colorIntensity: number;
231
+ }
232
+ /**
233
+ * Controls returned by {@link initFluid}.
234
+ */
235
+ interface FluidHandle {
236
+ /** Stop the render loop, detach listeners and release the GL context. */
237
+ dispose(): void;
238
+ /** Pause the simulation, leaving the canvas in place. */
239
+ pause(): void;
240
+ /** Resume after {@link FluidHandle.pause}. */
241
+ resume(): void;
242
+ /** Whether the simulation is currently paused. */
243
+ isPaused(): boolean;
244
+ /**
245
+ * Update tunable options in place, without tearing the simulation down.
246
+ *
247
+ * Resolution options (`simResolution`, `dyeResolution`) reallocate the
248
+ * framebuffers; everything else applies on the next frame.
249
+ */
250
+ setConfig(partial: Partial<ISmokeyFluidConfig>): void;
251
+ /**
252
+ * Inject a splash at a point, in CSS pixels relative to the canvas.
253
+ * Useful for driving the effect from something other than the pointer.
254
+ */
255
+ splat(x: number, y: number, color?: {
256
+ r: number;
257
+ g: number;
258
+ b: number;
259
+ }): void;
260
+ /** The canvas being rendered into. */
261
+ readonly canvas: HTMLCanvasElement | null;
262
+ }
263
+
264
+ /**
265
+ * Initializes and starts the fluid simulation
266
+ * @param incomingConfig - Partial configuration object to override default settings
267
+ */
268
+ declare const initFluid: (incomingConfig?: Partial<ISmokeyFluidConfig>) => FluidHandle;
269
+
270
+ /**
271
+ * A named, ready-made configuration.
272
+ *
273
+ * Presets only set appearance and physics — never mounting or placement — so
274
+ * they compose with whatever `canvas`, `container` or `zIndex` you pass.
275
+ */
276
+ type Preset = Pick<Partial<ISmokeyFluidConfig>, "palette" | "colorIntensity" | "colorUpdateSpeed" | "curl" | "splatForce" | "splatRadius" | "densityDissipation" | "velocityDissipation" | "pressure" | "pressureIteration" | "shading">;
277
+ /**
278
+ * The colour side of a preset.
279
+ *
280
+ * `null` means "no palette": hues are generated across the full spectrum,
281
+ * which is the library's default behaviour.
282
+ */
283
+ declare const palettes: {
284
+ readonly Spectrum: null;
285
+ readonly Sunset: readonly ["#ff4ecd", "#ff8a4e", "#ffd24e"];
286
+ readonly Ocean: readonly ["#4ea8ff", "#4effd2", "#7c4dff"];
287
+ readonly Mono: readonly ["#ffffff"];
288
+ readonly Aurora: readonly ["#3affa3", "#38d9ff", "#8f7bff"];
289
+ readonly Ember: readonly ["#ff5722", "#ff9100", "#ffc400"];
290
+ readonly Lagoon: readonly ["#00c2a8", "#00a3ff", "#0057d9"];
291
+ readonly Candy: readonly ["#ff8fd0", "#ffa9f0", "#c79bff"];
292
+ readonly Toxic: readonly ["#b6ff00", "#4dff88", "#00ffc8"];
293
+ readonly Royal: readonly ["#5b2bff", "#8f4dff", "#c44dff"];
294
+ readonly Sakura: readonly ["#ffc2dd", "#ff8fb1", "#ff6f91"];
295
+ readonly Mint: readonly ["#9cffd6", "#5ef2c0", "#2fd6a5"];
296
+ readonly Copper: readonly ["#ff9a5a", "#e2703a", "#b34700"];
297
+ readonly Ultraviolet: readonly ["#7b2cff", "#b429ff", "#ff29f0"];
298
+ readonly Ice: readonly ["#c9f0ff", "#8ad4ff", "#4fb3ff"];
299
+ readonly Magma: readonly ["#ff2d2d", "#ff6a00", "#ffb300"];
300
+ readonly Forest: readonly ["#2f9e44", "#69db7c", "#a9e34b"];
301
+ readonly Dusk: readonly ["#3b3b98", "#7158e2", "#cd84f1"];
302
+ readonly Cyber: readonly ["#00fff0", "#ff00e0", "#fffb00"];
303
+ readonly Pastel: readonly ["#ffd6e0", "#c7ceea", "#b5ead7"];
304
+ };
305
+ /**
306
+ * The motion side of a preset — how the fluid moves, independent of colour.
307
+ */
308
+ declare const characters: {
309
+ readonly Calm: {
310
+ readonly curl: 3;
311
+ readonly splatForce: 4200;
312
+ readonly splatRadius: 0.45;
313
+ readonly densityDissipation: 4.6;
314
+ readonly velocityDissipation: 2.6;
315
+ readonly pressureIteration: 16;
316
+ readonly colorUpdateSpeed: 6;
317
+ };
318
+ readonly Flow: {
319
+ readonly curl: 10;
320
+ readonly splatForce: 6000;
321
+ readonly splatRadius: 0.5;
322
+ readonly densityDissipation: 3.5;
323
+ readonly velocityDissipation: 2;
324
+ readonly pressureIteration: 20;
325
+ readonly colorUpdateSpeed: 10;
326
+ };
327
+ readonly Swirl: {
328
+ readonly curl: 24;
329
+ readonly splatForce: 7200;
330
+ readonly splatRadius: 0.55;
331
+ readonly densityDissipation: 3;
332
+ readonly velocityDissipation: 1.6;
333
+ readonly pressureIteration: 24;
334
+ readonly colorUpdateSpeed: 12;
335
+ };
336
+ readonly Storm: {
337
+ readonly curl: 40;
338
+ readonly splatForce: 9500;
339
+ readonly splatRadius: 0.65;
340
+ readonly densityDissipation: 2.2;
341
+ readonly velocityDissipation: 1.2;
342
+ readonly pressureIteration: 28;
343
+ readonly colorUpdateSpeed: 16;
344
+ };
345
+ readonly Wisp: {
346
+ readonly curl: 6;
347
+ readonly splatForce: 3200;
348
+ readonly splatRadius: 0.32;
349
+ readonly densityDissipation: 6.5;
350
+ readonly velocityDissipation: 3.4;
351
+ readonly pressureIteration: 12;
352
+ readonly colorUpdateSpeed: 8;
353
+ };
354
+ };
355
+ type PaletteName = keyof typeof palettes;
356
+ type CharacterName = keyof typeof characters;
357
+ type PresetName = `${PaletteName} ${CharacterName}`;
358
+ /**
359
+ * 100 ready-made looks: every colour palette crossed with every motion
360
+ * character, named `"<Palette> <Character>"` — e.g. `"Ocean Swirl"`.
361
+ *
362
+ * ```ts
363
+ * import { initFluid, presets } from "smokey-fluid-cursor";
364
+ *
365
+ * initFluid(presets["Ocean Swirl"]);
366
+ * ```
367
+ */
368
+ declare const presets: Record<PresetName, Preset>;
369
+ /** Every preset name, in definition order. */
370
+ declare const presetNames: PresetName[];
371
+ /** The palette names presets are built from. */
372
+ declare const paletteNames: PaletteName[];
373
+ /** The motion characters presets are built from. */
374
+ declare const characterNames: CharacterName[];
375
+ /** Looks up a preset by name, returning `undefined` when it does not exist. */
376
+ declare const getPreset: (name: string) => Preset | undefined;
377
+
378
+ interface SmokeyFluidCursorProps {
379
+ /** Simulation options. See ISmokeyFluidConfig. */
380
+ config?: Partial<ISmokeyFluidConfig>;
381
+ /**
382
+ * Render the effect inside this component's own wrapper element rather than
383
+ * over the whole viewport. Combine with `config.position: "absolute"` to
384
+ * scope the fluid to a section of the page.
385
+ */
386
+ scoped?: boolean;
387
+ /** Class name for the wrapper element (only rendered when `scoped`). */
388
+ className?: string;
389
+ /** Inline styles for the wrapper element (only rendered when `scoped`). */
390
+ style?: React.CSSProperties;
391
+ /** Content to render inside the scoped wrapper. */
392
+ children?: React.ReactNode;
393
+ }
394
+ /**
395
+ * Runs the fluid simulation for the lifetime of the calling component.
396
+ *
397
+ * Returns a ref to the live {@link FluidHandle}, so callers can pause, resume
398
+ * or retune without remounting.
399
+ */
400
+ declare function useSmokeyFluidCursor(config?: Partial<ISmokeyFluidConfig>, containerRef?: React.RefObject<HTMLElement | null>): React.RefObject<FluidHandle | null>;
401
+ /**
402
+ * Drop-in fluid cursor effect.
403
+ *
404
+ * By default it covers the viewport and sits behind your content. Pass
405
+ * `scoped` with `config.position: "absolute"` to confine it to one section.
406
+ */
407
+ declare const SmokeyFluidCursor: React.ForwardRefExoticComponent<SmokeyFluidCursorProps & React.RefAttributes<FluidHandle>>;
408
+
409
+ export { type CharacterName, type DoubleFBO, type FBO, type FluidHandle, type GL, type GLExtInfo, type ISmokeyFluidConfig, type PaletteName, type Preset, type PresetName, SmokeyFluidCursor, type SmokeyFluidCursorProps, characterNames, getPreset, initFluid, paletteNames, presetNames, presets, useSmokeyFluidCursor };