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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Farasat Ali
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,183 +1,373 @@
1
- <h1 align="center">πŸ’¨ React Smokey Fluid Cursor</h1>
2
-
3
1
  <p align="center">
4
- A beautiful, interactive fluid simulation that creates stunning visual effects following your cursor movements for your React and Next.js application on web. Built with WebGL for high-performance real-time fluid dynamics.
2
+ <img src="https://raw.githubusercontent.com/faraasat/react-smokey-fluid-cursor/main/.github/assets/banner.svg" alt="react-smokey-fluid-cursor" width="100%" />
5
3
  </p>
6
4
 
7
- ![npm version](https://img.shields.io/npm/v/react-smokey-fluid-cursor.svg)
8
- ![package size minified](https://img.shields.io/bundlephobia/min/react-smokey-fluid-cursor?style=plastic)
9
- [![Badge](https://data.jsdelivr.com/v1/package/npm/react-smokey-fluid-cursor/badge)](https://www.jsdelivr.com/package/npm/react-smokey-fluid-cursor)
10
- [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
5
+ <p align="center">
6
+ A GPU-accelerated fluid-simulation cursor trail for React and Next.js β€” one component, no configuration required.
7
+ </p>
11
8
 
12
- ![total downloads](https://img.shields.io/npm/dt/react-smokey-fluid-cursor.svg)
13
- ![total downloads per year](https://img.shields.io/npm/dy/react-smokey-fluid-cursor.svg)
14
- ![total downloads per week](https://img.shields.io/npm/dw/react-smokey-fluid-cursor.svg)
15
- ![total downloads per month](https://img.shields.io/npm/dm/react-smokey-fluid-cursor.svg)
16
- ![download-image](https://img.shields.io/npm/dm/react-smokey-fluid-cursor.svg)
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/react-smokey-fluid-cursor"><img alt="npm version" src="https://img.shields.io/npm/v/react-smokey-fluid-cursor?color=cb3837&label=npm&logo=npm"></a>
11
+ <a href="https://www.npmjs.com/package/react-smokey-fluid-cursor"><img alt="downloads" src="https://img.shields.io/npm/dm/react-smokey-fluid-cursor?color=cb3837&label=downloads"></a>
12
+ <a href="https://bundlephobia.com/package/react-smokey-fluid-cursor"><img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/react-smokey-fluid-cursor?label=minzipped"></a>
13
+ <a href="https://github.com/faraasat/react-smokey-fluid-cursor/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/faraasat/react-smokey-fluid-cursor/actions/workflows/ci.yml/badge.svg"></a>
14
+ <img alt="types" src="https://img.shields.io/badge/types-included-3178c6?logo=typescript&logoColor=white">
15
+ <a href="https://github.com/faraasat/react-smokey-fluid-cursor/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/npm/l/react-smokey-fluid-cursor?color=blue"></a>
16
+ </p>
17
17
 
18
- [![react-smokey-fluid-cursor](https://nodei.co/npm/react-smokey-fluid-cursor.png)](https://npmjs.org/package/react-smokey-fluid-cursor)
18
+ <p align="center">
19
+ <a href="https://faraasat.github.io/react-smokey-fluid-cursor/"><b>Live demo</b></a> Β·
20
+ <a href="https://www.npmjs.com/package/react-smokey-fluid-cursor">npm</a> Β·
21
+ <a href="https://github.com/faraasat/react-smokey-fluid-cursor/blob/main/CHANGELOG.md">Changelog</a> Β·
22
+ <a href="https://github.com/faraasat/react-smokey-fluid-cursor/issues">Issues</a>
23
+ </p>
19
24
 
20
25
  ---
21
26
 
22
- ## πŸ“¦ Installation
27
+ ## Upgrading from 1.x
23
28
 
24
- ```bash
25
- npm i react-smokey-fluid-cursor
29
+ `2.0.0` adds scoped rendering, an imperative handle and live config updates.
30
+ `<SmokeyFluidCursor />` keeps its props, but four behaviours differ.
26
31
 
27
- yarn add react-smokey-fluid-cursor
32
+ | Change | Impact | What to do |
33
+ | --- | --- | --- |
34
+ | **The component renders `null`** when not `scoped` | The canvas is created and appended by the engine rather than returned from render, so it no longer appears where you placed the component | Nothing, unless you relied on its position in the DOM. Use `scoped` to keep it inside your own wrapper |
35
+ | **Placement is applied inline**, not via an injected `<style>` | CSS you wrote against `#smokey-fluid-canvas` no longer wins | Use the `position`, `zIndex`, `pointerEvents` and `className` options, or add `!important` |
36
+ | **Device pixel ratio is capped at 2** (`maxDpr`) | Slightly softer on 3x displays, markedly better frame rate and battery | `maxDpr: Infinity` restores the old behaviour |
37
+ | **`prefers-reduced-motion` is honoured** | Visitors who asked for reduced motion get a still canvas | `respectReducedMotion: false` opts out |
28
38
 
29
- pnpm i react-smokey-fluid-cursor
39
+ ### The effect is invisible after upgrading?
30
40
 
31
- bun add react-smokey-fluid-cursor
41
+ The canvas sits behind your content at `z-index: -9999`. If your page sets an
42
+ opaque background on `<body>`, it paints over the effect β€” see
43
+ [the section above](#the-effect-is-invisible-check-your-page-background). This
44
+ applied to 1.x too, but the package now warns about it in development.
45
+
46
+ ### New, optional
47
+
48
+ ```tsx
49
+ const fluid = useRef<FluidHandle>(null);
50
+
51
+ <SmokeyFluidCursor ref={fluid} scoped config={{ palette: ["#ff4ecd"] }} />;
52
+ fluid.current?.pause();
53
+ fluid.current?.setConfig({ curl: 30 });
32
54
  ```
33
55
 
34
- ---
56
+ ## Why
35
57
 
36
- ## πŸ“Έ Demo
58
+ A real-time Navier–Stokes fluid solver running in WebGL, wired to your pointer
59
+ and wrapped as a single React component. It cleans up after itself on unmount,
60
+ survives React StrictMode's double-invoke, and degrades quietly on devices
61
+ without WebGL instead of crashing your page.
37
62
 
38
- Checkout demo here: [Demo](https://react-smokey-fluid-cursor.vercel.app/)
63
+ > Not using React? See
64
+ > [`smokey-fluid-cursor`](https://github.com/faraasat/smokey-fluid-cursor).
39
65
 
40
- Also see more details in [Example](https://github.com/faraasat/react-smokey-fluid-cursor/tree/main/example):
66
+ ## Installation
41
67
 
42
- ![Demo](https://github.com/faraasat/react-smokey-fluid-cursor/blob/main/images/demo.gif)
68
+ ```bash
69
+ npm install react-smokey-fluid-cursor
70
+ ```
43
71
 
44
- ---
72
+ <details>
73
+ <summary>yarn / pnpm / bun</summary>
45
74
 
46
- ## πŸš€ Quick Start
75
+ ```bash
76
+ yarn add react-smokey-fluid-cursor
77
+ pnpm add react-smokey-fluid-cursor
78
+ bun add react-smokey-fluid-cursor
79
+ ```
80
+ </details>
47
81
 
48
- ### **React (CRA)**
82
+ **Peer dependencies:** `react >= 17`, `react-dom >= 17`.
49
83
 
50
- ```tsx
51
- // src/App.tsx|jsx
52
- import React from "react";
84
+ ## Quick start
53
85
 
86
+ ```tsx
54
87
  import { SmokeyFluidCursor } from "react-smokey-fluid-cursor";
55
88
 
56
- function App() {
89
+ export default function Layout({ children }) {
57
90
  return (
58
- <div className="App">
59
- {/* Place observer once globally */}
91
+ <>
60
92
  <SmokeyFluidCursor />
61
- </div>
93
+ {children}
94
+ </>
62
95
  );
63
96
  }
64
97
  ```
65
98
 
66
- ### **Vite + React**
99
+ That is the whole integration. The component renders nothing itself β€” it
100
+ creates a `fixed`, full-viewport canvas behind your content with
101
+ `pointer-events: none`, and tears it down on unmount.
102
+
103
+ > **Next.js App Router:** the package ships the `"use client"` directive, so it
104
+ > can be imported straight into a server component.
105
+
106
+ ## Scope it to one section
107
+
108
+ Pass `scoped` and an `absolute` position, and the effect stays inside the
109
+ component's own wrapper instead of covering the page:
67
110
 
68
111
  ```tsx
69
- // src/main.tsx|jsx
70
- import React from "react";
112
+ <SmokeyFluidCursor
113
+ scoped
114
+ style={{ height: 320, borderRadius: 16 }}
115
+ config={{ position: "absolute", zIndex: 0, palette: ["#4ea8ff", "#7c4dff"] }}
116
+ >
117
+ <h2>Hover me</h2>
118
+ </SmokeyFluidCursor>
119
+ ```
120
+
121
+ When `scoped`, the wrapper gets `position: relative` and `overflow: hidden`
122
+ automatically, so the fluid is clipped to it.
71
123
 
124
+ ## Controlling it
125
+
126
+ Grab a ref for a live handle β€” pause, resume or retune without remounting:
127
+
128
+ ```tsx
129
+ import { useRef } from "react";
72
130
  import { SmokeyFluidCursor } from "react-smokey-fluid-cursor";
131
+ import type { FluidHandle } from "react-smokey-fluid-cursor";
132
+
133
+ function Page() {
134
+ const fluid = useRef<FluidHandle>(null);
73
135
 
74
- function Main() {
75
136
  return (
76
137
  <>
77
- <SmokeyFluidCursor />
138
+ <SmokeyFluidCursor ref={fluid} />
139
+ <button onClick={() => fluid.current?.pause()}>Pause</button>
140
+ <button onClick={() => fluid.current?.setConfig({ curl: 30 })}>Swirl</button>
78
141
  </>
79
142
  );
80
143
  }
144
+ ```
145
+
146
+ | Method | Description |
147
+ | --- | --- |
148
+ | `pause()` / `resume()` | Freeze or restart the simulation. |
149
+ | `isPaused()` | Current state. |
150
+ | `setConfig(partial)` | Retune in place, no remount. |
151
+ | `splat(x, y, color?)` | Inject a splash, in CSS pixels relative to the canvas. |
152
+ | `dispose()` | Tear down early (the component already does this on unmount). |
153
+ | `canvas` | The canvas being rendered into. |
154
+
155
+ ### The hook
156
+
157
+ For full control over where the effect lives:
158
+
159
+ ```tsx
160
+ import { useSmokeyFluidCursor } from "react-smokey-fluid-cursor";
161
+
162
+ function Hero() {
163
+ const ref = useRef<HTMLDivElement>(null);
164
+ useSmokeyFluidCursor({ position: "absolute" }, ref);
165
+ return <div ref={ref} style={{ position: "relative", height: 300 }} />;
166
+ }
167
+ ```
168
+
169
+ ## Updating config
170
+
171
+ Cheap options (`curl`, `splatForce`, `palette`, `colorIntensity`, dissipation,
172
+ `paused`, `zIndex`, …) are pushed straight into the running simulation.
81
173
 
82
- export default Main;
174
+ Structural options (`id`, `position`, `simResolution`, `dyeResolution`) rebuild
175
+ it, because they reallocate buffers or move DOM. Changing those every render
176
+ would be expensive, so keep them stable.
177
+
178
+ ## The effect is invisible? Check your page background
179
+
180
+ This is the single most common integration problem, and it looks like the
181
+ package is broken when it is not.
182
+
183
+ The canvas defaults to `z-index: -9999` so it sits behind your content. Per the
184
+ CSS painting order, a negatively-stacked element paints **above the root
185
+ background but below the background of block-level descendants** β€” so this
186
+ extremely common setup hides the effect completely:
187
+
188
+ ```css
189
+ /* βœ— body's background paints straight over the canvas */
190
+ body { background: #0b0f17; }
83
191
  ```
84
192
 
85
- ### **Next.js Pages Router**
193
+ Put the page background on `<html>` instead:
194
+
195
+ ```css
196
+ /* βœ“ the canvas paints above the root background, below your content */
197
+ html { background: #0b0f17; }
198
+ body { background: transparent; }
199
+ ```
200
+
201
+ Alternatively, lift the canvas above your background and push your content
202
+ above the canvas:
86
203
 
87
204
  ```tsx
88
- // pages/_app.tsx|jsx
89
- import type { AppProps } from "next/app";
205
+ initFluid({ zIndex: 0 });
206
+ ```
207
+ ```css
208
+ main { position: relative; z-index: 1; }
209
+ ```
90
210
 
91
- import { SmokeyFluidCursor } from "react-smokey-fluid-cursor";
211
+ In development the package detects this and warns in the console rather than
212
+ leaving you with a blank screen.
92
213
 
93
- function MyApp({ Component, pageProps }: AppProps) {
94
- return (
95
- <>
96
- {/* Global ad observer */}
97
- <SmokeyFluidCursor />
98
- <Component {...pageProps} />
99
- </>
100
- );
101
- }
214
+ ## Presets
215
+
216
+ 100 ready-made looks ship with the package β€” every colour palette crossed with
217
+ every motion character, named `"<Palette> <Character>"`:
218
+
219
+ ```tsx
220
+ import { presets } from "react-smokey-fluid-cursor";
102
221
 
103
- export default MyApp;
222
+ <SmokeyFluidCursor config={presets["Ocean Swirl"]} />
104
223
  ```
105
224
 
106
- ### **Next.js (App Router)**
225
+ | | |
226
+ | --- | --- |
227
+ | **Palettes** (20) | Spectrum, Sunset, Ocean, Mono, Aurora, Ember, Lagoon, Candy, Toxic, Royal, Sakura, Mint, Copper, Ultraviolet, Ice, Magma, Forest, Dusk, Cyber, Pastel |
228
+ | **Characters** (5) | Calm, Flow, Swirl, Storm, Wisp |
229
+
230
+ So `"Magma Storm"` is the magma palette at storm intensity, `"Ice Wisp"` is
231
+ pale and sparse, and so on.
232
+
233
+ Presets only set **appearance and physics** β€” never mounting or placement β€” so
234
+ they compose with your own `container`, `zIndex` and the rest:
107
235
 
108
236
  ```tsx
109
- // app/layout.tsx|jsx
110
- import "./globals.css";
237
+ initFluid({ ...presets["Cyber Swirl"], container: "#hero", position: "absolute" });
238
+ ```
111
239
 
112
- import { SmokeyFluidCursor } from "react-smokey-fluid-cursor";
240
+ Browse all 100 on the [live demo](https://faraasat.github.io/react-smokey-fluid-cursor/).
113
241
 
114
- export default function RootLayout({
115
- children,
116
- }: {
117
- children: React.ReactNode;
118
- }) {
119
- return (
120
- <html lang="en">
121
- <body>
122
- <SmokeyFluidCursor />
123
- {children}
124
- </body>
125
- </html>
126
- );
127
- }
242
+ ### Helpers
243
+
244
+ | Export | Type | Description |
245
+ | --- | --- | --- |
246
+ | `presets` | `Record<PresetName, Preset>` | Every preset, keyed by name. |
247
+ | `presetNames` | `PresetName[]` | All 100 names, in order. |
248
+ | `paletteNames` | `PaletteName[]` | The 20 palettes. |
249
+ | `characterNames` | `CharacterName[]` | The 5 motion characters. |
250
+ | `getPreset(name)` | `Preset \| undefined` | Safe lookup for a user-supplied name. |
251
+
252
+ ## Configuration
253
+
254
+ Every option is optional.
255
+
256
+ ### Mounting & placement
257
+
258
+ | Option | Type | Default | Description |
259
+ | --- | --- | --- | --- |
260
+ | `container` | `HTMLElement \| string` | `document.body` | Where to create the canvas. Set automatically when `scoped`. |
261
+ | `canvas` | `HTMLCanvasElement \| string` | β€” | Render into an existing canvas instead. |
262
+ | `id` | `string` | `"smokey-fluid-canvas"` | Id assigned to the canvas. |
263
+ | `position` | `"fixed" \| "absolute" \| "relative" \| "static"` | `"fixed"` | `absolute` confines the effect to its container. |
264
+ | `zIndex` | `number` | `-9999` | Stacking order. |
265
+ | `pointerEvents` | `boolean` | `false` | Whether the canvas swallows clicks. |
266
+ | `className` | `string` | β€” | Extra class on the canvas. |
267
+
268
+ ### Performance & accessibility
269
+
270
+ | Option | Type | Default | Description |
271
+ | --- | --- | --- | --- |
272
+ | `maxDpr` | `number` | `2` | Caps the device pixel ratio. Uncapped, a 3x phone renders **nine times** the pixels of a 1x display. |
273
+ | `pauseOnHidden` | `boolean` | `true` | Stop the loop while the tab is backgrounded. |
274
+ | `respectReducedMotion` | `boolean` | `true` | Start paused for `prefers-reduced-motion: reduce`. |
275
+ | `paused` | `boolean` | `false` | Start frozen. |
276
+
277
+ ### Appearance
278
+
279
+ | Option | Type | Default | Description |
280
+ | --- | --- | --- | --- |
281
+ | `palette` | `string[]` | `null` | Hex colours to draw from, e.g. `["#ff4ecd"]`. Omit for random hues. |
282
+ | `colorIntensity` | `number` | `0.15` | Brightness multiplier. |
283
+ | `backColor` | `{ r, g, b }` | `{ r: 0, g: 0, b: 0 }` | Canvas background. |
284
+ | `transparent` | `boolean` | `true` | Blend with the page background. |
285
+ | `shading` | `boolean` | `true` | Lighting, for depth. |
286
+ | `colorUpdateSpeed` | `number` | `10` | How fast the palette rotates. |
287
+
288
+ ### Simulation
289
+
290
+ | Option | Type | Default | Description |
291
+ | --- | --- | --- | --- |
292
+ | `simResolution` | `number` | `128` | Velocity/pressure grid. |
293
+ | `dyeResolution` | `number` | `1440` | Colour buffer resolution β€” the main quality/cost dial. |
294
+ | `densityDissipation` | `number` | `3.5` | How fast colour fades. |
295
+ | `velocityDissipation` | `number` | `2` | How fast motion slows. |
296
+ | `pressure` | `number` | `0.1` | Initial pressure multiplier. |
297
+ | `pressureIteration` | `number` | `20` | Jacobi iterations. |
298
+ | `curl` | `number` | `10` | Vorticity confinement β€” the swirliness. |
299
+ | `splatRadius` | `number` | `0.5` | Size of each splat. |
300
+ | `splatForce` | `number` | `6000` | Force per splat. |
301
+
302
+ ## Lifecycle
303
+
304
+ On unmount the component stops the render loop, detaches its window listeners,
305
+ releases the WebGL context and removes the canvas it created. Mounting and
306
+ unmounting repeatedly β€” including StrictMode's development double-invoke β€”
307
+ does not stack simulations.
308
+
309
+ ## Performance
310
+
311
+ The big levers, in order of impact:
312
+
313
+ 1. **`maxDpr`** β€” already capped at `2`. Drop to `1` for the weakest devices.
314
+ 2. **`dyeResolution`** β€” `512` is much cheaper and still looks good.
315
+ 3. **`pressureIteration`** β€” `10` roughly halves the solver cost.
316
+
317
+ ```tsx
318
+ <SmokeyFluidCursor config={{ maxDpr: 1, dyeResolution: 512, pressureIteration: 10 }} />
128
319
  ```
129
320
 
130
- ---
321
+ ## Accessibility
131
322
 
132
- ## βš™οΈ Configuration Options
133
-
134
- Customize the fluid simulation with these configuration options:
135
-
136
- | Property | Default Value | Description |
137
- | --------------------- | ----------------------------- | ---------------------------------------------- |
138
- | `id` | `"react-smokey-fluid-cursor"` | Canvas element ID |
139
- | `simResolution` | `128` | Simulation resolution (higher = more detailed) |
140
- | `dyeResolution` | `512` | Dye/color resolution |
141
- | `densityDissipation` | `0.98` | How quickly colors fade (0–1) |
142
- | `velocityDissipation` | `0.98` | How quickly movement slows down |
143
- | `pressureIteration` | `10` | Pressure solver iterations |
144
- | `curl` | `30` | Vorticity/swirl intensity |
145
- | `splatRadius` | `0.25` | Size of cursor splats |
146
- | `splatForce` | `6000` | Force of cursor movements |
147
- | `shading` | `true` | Enable 3D lighting effects |
148
- | `colorUpdateSpeed` | `0.5` | Speed of color transitions |
149
- | `transparent` | `false` | Transparent background |
323
+ A full-screen animation is a real problem for people with vestibular
324
+ disorders. By default this honours `prefers-reduced-motion: reduce` by starting
325
+ paused, and reacts if the preference changes while the page is open.
150
326
 
151
- ---
327
+ ## Browser support
152
328
 
153
- ## 🌟 Features
329
+ Requires WebGL (WebGL 2 when available, WebGL 1 fallback). Without it the
330
+ component logs a warning and renders nothing β€” it never throws, so a decorative
331
+ effect cannot take down your app.
154
332
 
155
- - **Real-time Fluid Dynamics**: Physics-based simulation using Navier-Stokes equations
156
- - **WebGL Accelerated**: High-performance rendering for smooth 60fps
157
- - **Interactive**: Responds to mouse and touch movements
158
- - **Customizable**: Extensive configuration options
159
- - **Mobile Support**: Touch-optimized interactions
160
- - **Auto-scaling**: Adapts to screen size and pixel ratio
161
- - **Color Cycling**: Dynamic, evolving color palettes
162
- - **3D Lighting**: Optional shading for depth perception
333
+ ## Contributing
163
334
 
164
- ---
335
+ Issues and pull requests are welcome.
165
336
 
166
- ## 🎯 Use Cases
337
+ ```bash
338
+ git clone https://github.com/faraasat/react-smokey-fluid-cursor.git
339
+ cd react-smokey-fluid-cursor
340
+ npm install
341
+ npm test # vitest unit tests
342
+ npm run typecheck # tsc --noEmit
343
+ npm run build # tsup
344
+ ```
167
345
 
168
- - **Website Backgrounds**: Immersive animated backgrounds
169
- - **Cursor Effects**: Enhanced user interaction feedback
170
- - **Data Visualization**: Fluid-based data representations
171
- - **Art Installations**: Digital art and creative coding
172
- - **Game Effects**: Atmospheric and UI effects
173
- - **Product Demos**: Eye-catching technology showcases
346
+ End-to-end tests run against the built demo in a real browser (desktop and
347
+ mobile viewports), and cover the things unit tests cannot: layout, CSS and
348
+ keyboard behaviour.
174
349
 
175
- ---
350
+ ```bash
351
+ npm run build && npm --prefix example install && npm --prefix example run build
352
+ npm run test:e2e # playwright
353
+ npm run test:e2e:ui # interactive
354
+ ```
355
+
356
+ To run the demo site against your local build:
357
+
358
+ ```bash
359
+ npm run example:dev
360
+ ```
361
+
362
+ Releases are manual β€” nothing publishes on a push to `main`. Maintainers run
363
+ the **Release** workflow from the Actions tab.
364
+
365
+ ## Privacy
176
366
 
177
- ## πŸ§‘β€πŸ’» Author
367
+ The published package contains **no telemetry**. The demo site at
368
+ [faraasat.github.io/react-smokey-fluid-cursor](https://faraasat.github.io/react-smokey-fluid-cursor/) uses
369
+ Google Analytics and Aptabase; the library itself never phones home.
178
370
 
179
- Built and maintained by [**Farasat Ali**](https://www.farasat.me)
371
+ ## License
180
372
 
181
- - Website: [www.farasat.me](https://www.farasat.me)
182
- - LinkedIn: [linkedin.com/in/faraasat](https://linkedin.com/in/faraasat)
183
- - GitHub: [github.com/faraasat](https://github.com/faraasat)
373
+ [MIT](./LICENSE) Β© [Farasat Ali](https://github.com/faraasat)