@zakkster/lite-ambient-fx 1.0.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/llms.txt ADDED
@@ -0,0 +1,210 @@
1
+ # @zakkster/lite-ambient-fx
2
+
3
+ > Fullscreen ambient particle atmospheres in one file. Six themed presets
4
+ > across four behaviors (EMBER, MIST, FLOAT, CHAOS) plus a registry hook
5
+ > for adding your own. Zero dependencies. Sprite-cached radial gradients,
6
+ > zero-alloc render loop, monomorphic particle shape, DPR-aware,
7
+ > resize-preserving, visibility-paused, delta-time clamped.
8
+
9
+ ## Install
10
+
11
+ ```
12
+ npm install @zakkster/lite-ambient-fx
13
+ ```
14
+
15
+ ## Minimal use
16
+
17
+ ```js
18
+ import { createAmbientFX } from "@zakkster/lite-ambient-fx";
19
+ const fx = createAmbientFX(canvasElement, { theme: "Void" });
20
+ ```
21
+
22
+ ## Public API
23
+
24
+ ```ts
25
+ type BuiltInBehavior = "EMBER" | "MIST" | "FLOAT" | "CHAOS";
26
+ type BehaviorName = BuiltInBehavior | (string & {});
27
+ type ThemeName = "Fire" | "Night" | "Ice" | "Frost" | "Toxic" | "Void";
28
+
29
+ interface AmbientConfig {
30
+ behavior: BehaviorName;
31
+ colors: string[];
32
+ spark: string;
33
+ count: number;
34
+ wind: { x: number; y: number };
35
+ decay: number;
36
+ speed: number;
37
+ size: number;
38
+ alpha: number; // clamped to [0, 1]
39
+ turbulence: number;
40
+ }
41
+
42
+ interface AmbientOptions {
43
+ theme?: ThemeName; // default "Fire"
44
+ overrides?: Partial<AmbientConfig>; // shallow-merges wind
45
+ autoStart?: boolean; // default true
46
+ }
47
+
48
+ interface AmbientInstance {
49
+ setTheme(name: ThemeName): void;
50
+ updateConfig(overrides: Partial<AmbientConfig>): void;
51
+ readonly config: AmbientConfig;
52
+ readonly theme: ThemeName;
53
+ readonly count: number;
54
+ readonly running: boolean;
55
+ pause(): void;
56
+ resume(): void;
57
+ destroy(): void;
58
+ }
59
+
60
+ interface Particle {
61
+ id: number;
62
+ color: string;
63
+ spriteCanvas: HTMLCanvasElement | null;
64
+ z: number;
65
+ life: number;
66
+ x: number; y: number;
67
+ size: number;
68
+ vx: number; vy: number;
69
+ decay: number;
70
+ maxAlpha: number;
71
+ anchorX: number; anchorY: number; // MIST-only, zeroed otherwise
72
+ pulseOffset: number; // MIST-only, zeroed otherwise
73
+ }
74
+
75
+ interface FrameContext {
76
+ cfg: AmbientConfig;
77
+ W: number; H: number;
78
+ dt: number; // ms since last frame, clamped to 50
79
+ ds: number; // dt / 16
80
+ timestamp: number; // RAF-callback timestamp
81
+ isInit: boolean;
82
+ getSprite(color: string, logicalSize: number): HTMLCanvasElement;
83
+ respawn(p: Particle, isInit: boolean): void;
84
+ }
85
+
86
+ interface BehaviorDefinition {
87
+ spriteLogical: number;
88
+ spawn(p: Particle, frame: FrameContext): void;
89
+ tick(particles: Particle[], ctx: CanvasRenderingContext2D, frame: FrameContext): void;
90
+ }
91
+
92
+ // Factory
93
+ function createAmbientFX(canvas: HTMLCanvasElement, options?: AmbientOptions): AmbientInstance;
94
+
95
+ // Registry
96
+ const BEHAVIORS: Record<string, BehaviorDefinition>;
97
+ function registerBehavior(name: string, def: BehaviorDefinition): void;
98
+
99
+ // Constants
100
+ const VERSION: string;
101
+ const THEMES: Record<ThemeName, AmbientConfig>;
102
+ const THEME_META: Array<{ id: ThemeName; name: string; icon: string; behavior: BuiltInBehavior }>;
103
+
104
+ // Pure helpers
105
+ function mergeThemeConfig(base: AmbientConfig, overrides: Partial<AmbientConfig> | null): AmbientConfig;
106
+ function validateConfig(cfg: AmbientConfig): AmbientConfig;
107
+ function envelopeAlpha(mode: BuiltInBehavior, life: number, maxAlpha: number): number;
108
+ function sinLut(index: number): number;
109
+ function deltaScale(dtMs: number): number;
110
+ function clearAmbientSpriteCache(colors?: string[]): void;
111
+ ```
112
+
113
+ ## Behavior semantics (built-ins)
114
+
115
+ - **EMBER** -- rising particles with wind + turbulence; 15% "super" sparks
116
+ with 0.3x decay and 1.5x speed. `life` is 0..1 progress; fade-in 0-0.2,
117
+ fade-out 0.2-1. `spriteLogical: 64`.
118
+ - **MIST** -- large breathing fog blobs; wrap at edges; sine-LUT breath
119
+ modulation on alpha and size. `life` is an ms accumulator that wraps at
120
+ 72_000 to avoid Float32 drift. Requires `mix-blend-mode: screen` on
121
+ the canvas CSS to look right. `spriteLogical: 128`.
122
+ - **FLOAT** -- rising particles with sine-wave horizontal sway; no
123
+ turbulence. `life` is 0..1; fade-in 0-0.1, sustain, fade-out 0.9-1.
124
+ `spriteLogical: 64`.
125
+ - **CHAOS** -- random omnidirectional velocity; straight lines; ~7.8Hz
126
+ bit-flicker on alpha (via `Math.trunc(timestamp / 128)`). `life` is
127
+ 0..1 progress; dies at >=1 or on edge exit. `spriteLogical: 64`.
128
+
129
+ ## Adding a custom behavior
130
+
131
+ ```js
132
+ import { registerBehavior } from "@zakkster/lite-ambient-fx";
133
+
134
+ registerBehavior("SNOW", {
135
+ spriteLogical: 64,
136
+ spawn(p, frame) {
137
+ // MUST populate every field of `p`. MUST NOT add new fields.
138
+ // MUST set p.spriteCanvas via frame.getSprite(color, spriteLogical).
139
+ // ... (see README "Adding a custom behavior" for the full example)
140
+ },
141
+ tick(particles, ctx, frame) {
142
+ // ... own physics + alpha envelope, call frame.respawn(p, false) on death.
143
+ },
144
+ });
145
+ ```
146
+
147
+ The `Particle` shape is monomorphic -- every field (including MIST-only
148
+ `anchorX`/`anchorY`/`pulseOffset`) is present on every particle. Custom
149
+ behaviors zero out the fields they don't use.
150
+
151
+ ## Configuration semantics
152
+
153
+ - `updateConfig` changes take effect for **new spawns**; alive particles
154
+ keep the values baked at spawn. Sliding a knob feels smooth this way.
155
+ - `wind` on `overrides` shallow-merges: `{ wind: { x: 1 } }` preserves
156
+ the existing `y`.
157
+ - `overrides.colors` or `overrides.spark` triggers targeted sprite-cache
158
+ eviction of the previous palette.
159
+ - `overrides.behavior` re-primes sprites (mist vs core sprite logical
160
+ sizes differ) and re-inits the particle pool.
161
+ - `overrides.count` re-inits the pool (grow or trim tail).
162
+
163
+ ## Sizing
164
+
165
+ Canvas should be a fullscreen overlay:
166
+
167
+ ```css
168
+ canvas#fx {
169
+ position: fixed;
170
+ inset: 0;
171
+ width: 100dvw;
172
+ height: 100dvh;
173
+ z-index: 0;
174
+ mix-blend-mode: screen;
175
+ pointer-events: none;
176
+ }
177
+ ```
178
+
179
+ The module handles DPR internally; sprites are rasterized at
180
+ `logicalSize x devicePixelRatio`.
181
+
182
+ ## Performance envelope
183
+
184
+ - Target: 40-500 particles as an atmosphere layer behind normal UI.
185
+ - Not the tool for 10 000+ particles. Reach for an SoA GPU pipeline instead.
186
+ - Zero allocation on the render loop:
187
+ - Nested `Map<color, Map<physical, canvas>>` sprite cache -- no key
188
+ strings.
189
+ - Sprite reference cached on the particle at spawn time -- no
190
+ per-frame `getSprite` calls.
191
+ - Particle shape monomorphic -- all fields present at push time.
192
+ - Behavior dispatch hoisted outside the loop -- one lookup per frame.
193
+ - Pooled `FrameContext` -- allocated once, mutated per frame.
194
+ - Shared 360-entry Float32 sine LUT constructed once at module load.
195
+
196
+ ## Runtime targets
197
+
198
+ - ES2020 + Canvas 2D.
199
+ - Chrome/Edge/Firefox (last two majors), Safari 14+, Twitch Extensions.
200
+ - Node 18+ (for tests only, under a minimal DOM shim).
201
+ - Not SSR-safe to construct; module import is safe (no top-level DOM
202
+ access).
203
+
204
+ ## Zero-dependency pledge
205
+
206
+ No runtime imports from anywhere. Single-file ESM. `sideEffects: false`.
207
+
208
+ ## License
209
+
210
+ MIT (c) Zahary Shinikchiev
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@zakkster/lite-ambient-fx",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency fullscreen ambient particle atmospheres. Six themed presets across four behaviors. Sprite-cached, DPR-aware, resize-preserving, visibility-paused. One file.",
5
+ "type": "module",
6
+ "main": "./AmbientFX.js",
7
+ "module": "./AmbientFX.js",
8
+ "types": "./AmbientFX.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./AmbientFX.d.ts",
12
+ "node": "./AmbientFX.js",
13
+ "import": "./AmbientFX.js",
14
+ "default": "./AmbientFX.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "AmbientFX.js",
19
+ "AmbientFX.d.ts",
20
+ "CHANGELOG.md",
21
+ "LICENSE.txt",
22
+ "README.md",
23
+ "llms.txt"
24
+ ],
25
+ "sideEffects": false,
26
+ "scripts": {
27
+ "test": "node --test 'test/*.mjs'",
28
+ "demo": "npx serve ."
29
+ },
30
+ "keywords": [
31
+ "particles",
32
+ "ambient",
33
+ "atmosphere",
34
+ "canvas",
35
+ "fx",
36
+ "zero-dependency",
37
+ "twitch-extension",
38
+ "background",
39
+ "presets",
40
+ "single-file",
41
+ "zakkster"
42
+ ],
43
+ "author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/PeshoVurtoleta/lite-ambient-fx.git"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/PeshoVurtoleta/lite-ambient-fx/issues"
51
+ },
52
+ "homepage": "https://github.com/PeshoVurtoleta/lite-ambient-fx#readme",
53
+ "funding": {
54
+ "type": "github",
55
+ "url": "https://github.com/sponsors/PeshoVurtoleta"
56
+ },
57
+ "engines": {
58
+ "node": ">=18"
59
+ }
60
+ }