@zakkster/lite-tools 1.0.0 → 1.0.1

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,264 @@
1
+ // ═══════════════════════════════════════════════════════════
2
+ // BARREL RE-EXPORTS
3
+ // ═══════════════════════════════════════════════════════════
4
+
5
+ // Math
6
+ export {
7
+ lerp,
8
+ clamp,
9
+ inverseLerp,
10
+ mapRange,
11
+ remap,
12
+ damp,
13
+ smoothstep,
14
+ easeIn,
15
+ easeOut,
16
+ easeInOut,
17
+ lerpAngle,
18
+ lerpAngleRad
19
+ } from '@zakkster/lite-lerp';
20
+
21
+ // Color
22
+ export {
23
+ lerpOklch, toCssOklch, parseOklch, multiStopGradient, createGradient, reverseGradient, randomFromGradient
24
+ }from '@zakkster/lite-color';
25
+ export type
26
+ {
27
+ OklchColor
28
+ }
29
+ from
30
+ '@zakkster/lite-color';
31
+
32
+ // Random
33
+ export {Random} from '@zakkster/lite-random';
34
+
35
+ // Object Pool
36
+ export {ObjectPool} from 'lite-object-pool';
37
+
38
+ // Particles
39
+ export {Emitter} from '@zakkster/lite-particles';
40
+
41
+ // SoA Engine
42
+ export {SoaParticleEngine} from '@zakkster/lite-soa-particle-engine';
43
+
44
+ // FX System
45
+ export {FXSystem, Presets, EmitterShape, Wind, GravityWell, Vortex, Turbulence, DragField} from '@zakkster/lite-fx';
46
+ export type
47
+ {
48
+ FXRecipe, FXSpawnOptions
49
+ }
50
+ from
51
+ '@zakkster/lite-fx';
52
+
53
+ // Generative Art
54
+ export {SimplexNoise, FlowField, Shape, ArtCanvas, GenEngine, Pattern} from '@zakkster/lite-gen';
55
+ export type
56
+ {
57
+ DrawContext
58
+ }
59
+ from
60
+ '@zakkster/lite-gen';
61
+
62
+ // UI
63
+ export {
64
+ SmartObserver,
65
+ ScrollReveal,
66
+ Parallax,
67
+ Magnetic,
68
+ Spring,
69
+ ScrollProgress,
70
+ Tilt,
71
+ ColorShift,
72
+ ConfettiBurst,
73
+ SparkleHover,
74
+ destroyAll
75
+ } from '@zakkster/lite-ui';
76
+
77
+ // Theme
78
+ export {generateTheme, toCssVariables, createThemeCss} from '@zakkster/lite-theme-gen';
79
+ export type
80
+ {
81
+ ThemePalette, ThemeOptions
82
+ }
83
+ from
84
+ '@zakkster/lite-theme-gen';
85
+
86
+ // Viewport, Ticker, FSM, FPS, Pointer, Assets, Audio
87
+ export {Viewport} from 'lite-viewport';
88
+ export {Ticker} from 'lite-ticker';
89
+ export {FSM} from 'lite-fsm';
90
+ export {FPSMeter} from 'lite-fps-meter';
91
+ export {PointerTracker} from 'lite-pointer-tracker';
92
+ export {AssetLoader} from 'lite-asset-loader';
93
+ export {AudioManager, audioManager} from 'lite-audio-manager';
94
+
95
+
96
+ // ═══════════════════════════════════════════════════════════
97
+ // RECIPE TYPES
98
+ // ═══════════════════════════════════════════════════════════
99
+
100
+ import type {OklchColor} from '@zakkster/lite-color';
101
+ import type {FXSystem} from '@zakkster/lite-fx';
102
+ import type {GenEngine, FlowField, SimplexNoise} from '@zakkster/lite-gen';
103
+ import type {Magnetic, ColorShift, ConfettiBurst, Spring} from '@zakkster/lite-ui';
104
+ import type {ThemePalette} from '@zakkster/lite-theme-gen';
105
+ import type
106
+
107
+ Random
108
+ from
109
+ '@zakkster/lite-random';
110
+ import type {Emitter} from '@zakkster/lite-particles';
111
+ import type {Ticker} from 'lite-ticker';
112
+ import type {Viewport} from 'lite-viewport';
113
+ import type {FSM} from 'lite-fsm';
114
+ import type {FPSMeter} from 'lite-fps-meter';
115
+ import type {PointerTracker} from 'lite-pointer-tracker';
116
+
117
+ /** All recipes return at least a destroy() method. */
118
+ interface Destroyable {
119
+ destroy(): void;
120
+ }
121
+
122
+ export interface BrandedBackgroundResult extends Destroyable {
123
+ gen: GenEngine;
124
+ field: FlowField;
125
+ theme: ThemePalette;
126
+ gradient: (t: number) => OklchColor;
127
+ }
128
+
129
+ export interface PremiumButtonResult extends Destroyable {
130
+ magnetic: Magnetic;
131
+ colorShift: ColorShift;
132
+ confetti: ConfettiBurst;
133
+ }
134
+
135
+ export interface BlackHoleResult extends Destroyable {
136
+ fx: FXSystem;
137
+
138
+ explode(x: number, y: number): void;
139
+
140
+ moveTo(x: number, y: number): void;
141
+ }
142
+
143
+ export interface ScrollStoryResult extends Destroyable {
144
+ instances: Destroyable[];
145
+ }
146
+
147
+ export interface ParticleCursorResult extends Destroyable {
148
+ emitter: Emitter;
149
+ tracker: PointerTracker;
150
+ ticker: Ticker;
151
+ }
152
+
153
+ export interface StarfieldResult extends Destroyable {
154
+ viewport: Viewport;
155
+ ticker: Ticker;
156
+ stars: Array<{ x: number; y: number; size: number; speed: number; phase: number; hue: number; bright: boolean }>;
157
+ }
158
+
159
+ export interface SpringMenuResult extends Destroyable {
160
+ fsm: FSM;
161
+ spring: Spring;
162
+
163
+ open(): void;
164
+
165
+ close(): void;
166
+
167
+ toggle(): void;
168
+ }
169
+
170
+ export interface NoiseHeatmapResult extends Destroyable {
171
+ gen: GenEngine;
172
+ gradient: (t: number) => OklchColor;
173
+
174
+ reseed(seed?: number): void;
175
+ }
176
+
177
+ export interface FireworkShowResult extends Destroyable {
178
+ fx: FXSystem;
179
+ ticker: Ticker;
180
+
181
+ stop(): void;
182
+
183
+ resume(): void;
184
+
185
+ manualBurst(x: number, y: number): void;
186
+ }
187
+
188
+ export interface SnowfallResult extends Destroyable {
189
+ fx: FXSystem;
190
+ ticker: Ticker;
191
+
192
+ setWind(strength: number): void;
193
+ }
194
+
195
+ export interface TiltGalleryResult extends Destroyable {
196
+ instances: Destroyable[];
197
+ }
198
+
199
+ export interface ReplaySystemResult extends Destroyable {
200
+ fx: FXSystem;
201
+ fsm: FSM;
202
+
203
+ registerRecipe(name: string, preset: Function): void;
204
+
205
+ startRecording(): void;
206
+
207
+ recordEvent(x: number, y: number, recipeName: string): void;
208
+
209
+ stopRecording(): Array<{ time: number; x: number; y: number; recipeId: number }>;
210
+
211
+ replay(): void;
212
+
213
+ stopReplay(): void;
214
+ }
215
+
216
+ export interface ThemePlaygroundResult extends Destroyable {
217
+ readonly palette: ThemePalette;
218
+
219
+ setBrand(brand: OklchColor): void;
220
+
221
+ setMode(mode: 'light' | 'dark'): void;
222
+
223
+ toggleMode(): void;
224
+
225
+ getCss(): string;
226
+ }
227
+
228
+ export interface GameCanvasResult extends Destroyable {
229
+ viewport: Viewport;
230
+ ctx: CanvasRenderingContext2D;
231
+ ticker: Ticker;
232
+ rng: Random;
233
+ fsm: FSM;
234
+ fx: FXSystem;
235
+ meter: FPSMeter | null;
236
+ readonly width: number;
237
+ readonly height: number;
238
+ readonly state: string;
239
+
240
+ onUpdate(fn: (dt: number) => void): () => void;
241
+
242
+ setState(state: string): void;
243
+
244
+ start(): void;
245
+ }
246
+
247
+ export declare const Recipes: {
248
+ brandedBackground(canvas: HTMLCanvasElement, brandColor: OklchColor, options?: { seed?: number; animate?: boolean }): BrandedBackgroundResult;
249
+ premiumButton(buttonSelector: HTMLElement | string, overlayCanvas: HTMLCanvasElement, options?: { brandColor?: OklchColor; hoverColor?: OklchColor; confettiColors?: OklchColor[]; magneticStrength?: number }): PremiumButtonResult;
250
+ blackHole(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, options?: { maxParticles?: number; seed?: number; wellStrength?: number; vortexStrength?: number; vortexPull?: number }): BlackHoleResult;
251
+ scrollStory(options?: { heroSelector?: string; heroSpeed?: number; cardSelector?: string; imageSelector?: string; titleSelector?: string; progressBar?: HTMLElement; onProgress?: (t: number) => void }): ScrollStoryResult;
252
+ particleCursor(canvas: HTMLCanvasElement, options?: { maxParticles?: number; trailColor?: OklchColor; fadeColor?: OklchColor; spawnRate?: number }): ParticleCursorResult;
253
+ starfield(canvas: HTMLCanvasElement, options?: { seed?: number; starCount?: number; twinkleSpeed?: number }): StarfieldResult;
254
+ springMenu(menuSelector: HTMLElement | string, toggleSelector: HTMLElement | string, options?: { openColor?: OklchColor; closedColor?: OklchColor; stiffness?: number; damping?: number }): SpringMenuResult;
255
+ noiseHeatmap(canvas: HTMLCanvasElement, options?: { seed?: number; scale?: number; cellSize?: number; animate?: boolean; gradient?: OklchColor[] }): NoiseHeatmapResult;
256
+ fireworkShow(ctx: CanvasRenderingContext2D, width: number, height: number, options?: { maxParticles?: number; seed?: number; burstInterval?: number }): FireworkShowResult;
257
+ snowfall(ctx: CanvasRenderingContext2D, width: number, height: number, options?: { maxParticles?: number; seed?: number; windStrength?: number; turbulenceStrength?: number }): SnowfallResult;
258
+ tiltGallery(cardSelector: string | NodeListOf<Element>, overlayCanvas: HTMLCanvasElement, options?: { maxAngle?: number; sparkleColor?: OklchColor; sparkleRate?: number; revealStagger?: number }): TiltGalleryResult;
259
+ replaySystem(ctx: CanvasRenderingContext2D, options?: { maxParticles?: number; seed?: number }): ReplaySystemResult;
260
+ themePlayground(options?: { initialBrand?: OklchColor; mode?: 'light' | 'dark'; prefix?: string; onThemeChange?: (palette: ThemePalette, css: string) => void }): ThemePlaygroundResult;
261
+ gameCanvas(canvas: HTMLCanvasElement, options?: { fps?: boolean; fpsPosition?: string; seed?: number; maxParticles?: number; states?: Record<string, string[]> }): GameCanvasResult;
262
+ };
263
+
264
+ export default Recipes;
package/LiteEngine.js ADDED
@@ -0,0 +1,865 @@
1
+ /**
2
+ * @zakkster/lite-tools — The Standard Library for High-Performance Web Presentation
3
+ *
4
+ * A unified, tree-shakeable toolkit composing every @zakkster library into
5
+ * ready-to-use recipes. The math of Three.js, the physics of Framer Motion,
6
+ * and the color theory of Tailwind — deterministic, zero-GC, fraction of the bundle.
7
+ *
8
+ * IMPORT PATTERNS:
9
+ * import { Recipes, FXSystem, GenEngine } from '@zakkster/lite-tools'
10
+ * import { lerp, damp } from '@zakkster/lite-tools/lerp'
11
+ * import { Recipes } from '@zakkster/lite-tools'
12
+ */
13
+
14
+ // ═══════════════════════════════════════════════════════════
15
+ // BARREL RE-EXPORTS (tree-shakeable)
16
+ // ═══════════════════════════════════════════════════════════
17
+
18
+ export {
19
+ lerp,
20
+ clamp,
21
+ inverseLerp,
22
+ mapRange,
23
+ remap,
24
+ damp,
25
+ smoothstep,
26
+ easeIn,
27
+ easeOut,
28
+ easeInOut,
29
+ lerpAngle,
30
+ lerpAngleRad
31
+ } from '@zakkster/lite-lerp';
32
+ export {
33
+ lerpOklch, toCssOklch, parseOklch, multiStopGradient, createGradient, reverseGradient, randomFromGradient
34
+ }from '@zakkster/lite-color';
35
+ export {Random} from '@zakkster/lite-random';
36
+ export {ObjectPool} from 'lite-object-pool';
37
+ export {Emitter} from '@zakkster/lite-particles';
38
+ export {SoaParticleEngine} from '@zakkster/lite-soa-particle-engine';
39
+ export {FXSystem, Presets, EmitterShape, Wind, GravityWell, Vortex, Turbulence, DragField} from '@zakkster/lite-fx';
40
+ export {SimplexNoise, FlowField, Shape, ArtCanvas, GenEngine, Pattern} from '@zakkster/lite-gen';
41
+ export {
42
+ SmartObserver,
43
+ ScrollReveal,
44
+ Parallax,
45
+ Magnetic,
46
+ Spring,
47
+ ScrollProgress,
48
+ Tilt,
49
+ ColorShift,
50
+ ConfettiBurst,
51
+ SparkleHover,
52
+ destroyAll
53
+ } from '@zakkster/lite-ui';
54
+ export {generateTheme, toCssVariables, createThemeCss} from '@zakkster/lite-theme-gen';
55
+ export {Viewport} from 'lite-viewport';
56
+ export {Ticker} from 'lite-ticker';
57
+ export {FSM} from 'lite-fsm';
58
+ export {FPSMeter} from 'lite-fps-meter';
59
+ export {PointerTracker} from 'lite-pointer-tracker';
60
+ export {AssetLoader} from 'lite-asset-loader';
61
+ export {AudioManager, audioManager} from 'lite-audio-manager';
62
+
63
+ // ═══════════════════════════════════════════════════════════
64
+ // INTERNAL IMPORTS FOR RECIPES
65
+ // ═══════════════════════════════════════════════════════════
66
+
67
+ import {lerp, clamp, inverseLerp, damp, easeOut, easeInOut, smoothstep} from '@zakkster/lite-lerp';
68
+ import {lerpOklch, toCssOklch, createGradient} from '@zakkster/lite-color';
69
+ import {Random} from '@zakkster/lite-random';
70
+ import {Emitter} from '@zakkster/lite-particles';
71
+ import {FXSystem, Presets, EmitterShape, Wind, GravityWell, Vortex, Turbulence, DragField} from '@zakkster/lite-fx';
72
+ import {SimplexNoise, FlowField, Shape, ArtCanvas, GenEngine, Pattern} from '@zakkster/lite-gen';
73
+ import {
74
+ Magnetic,
75
+ Tilt,
76
+ ColorShift,
77
+ ConfettiBurst,
78
+ SparkleHover,
79
+ ScrollReveal,
80
+ Parallax,
81
+ ScrollProgress,
82
+ Spring,
83
+ destroyAll
84
+ } from '@zakkster/lite-ui';
85
+ import {generateTheme, toCssVariables} from '@zakkster/lite-theme-gen';
86
+ import {Ticker} from 'lite-ticker';
87
+ import {Viewport} from 'lite-viewport';
88
+ import {FSM} from 'lite-fsm';
89
+ import {FPSMeter} from 'lite-fps-meter';
90
+ import {PointerTracker} from 'lite-pointer-tracker';
91
+
92
+
93
+ // ═══════════════════════════════════════════════════════════
94
+ // HELPERS
95
+ // ═══════════════════════════════════════════════════════════
96
+
97
+ /** Resolve a CSS selector or element. Returns null + warns on failure. */
98
+ function _resolveEl(selectorOrEl, context) {
99
+ if (!selectorOrEl) return null;
100
+ if (typeof selectorOrEl === 'string') {
101
+ const el = document.querySelector(selectorOrEl);
102
+ if (!el) console.warn(`@zakkster/lite-tools [${context}]: Element not found for "${selectorOrEl}"`);
103
+ return el;
104
+ }
105
+ return selectorOrEl;
106
+ }
107
+
108
+ /** Resolve multiple elements from selector or NodeList. */
109
+ function _resolveEls(selectorOrList, context) {
110
+ if (!selectorOrList) return [];
111
+ if (typeof selectorOrList === 'string') {
112
+ const els = [...document.querySelectorAll(selectorOrList)];
113
+ if (els.length === 0) console.warn(`@zakkster/lite-tools [${context}]: No elements found for "${selectorOrList}"`);
114
+ return els;
115
+ }
116
+ return [...selectorOrList];
117
+ }
118
+
119
+ /** No-op return object when element resolution fails. */
120
+ const _NOOP = Object.freeze({
121
+ destroy() {
122
+ }
123
+ });
124
+
125
+
126
+ // ═══════════════════════════════════════════════════════════
127
+ // RECIPES
128
+ // ═══════════════════════════════════════════════════════════
129
+
130
+ export const Recipes = {
131
+
132
+ // ─────────────────────────────────────────────
133
+ // 🎨 1. Branded Generative Background
134
+ // lite-theme-gen + lite-gen + lite-color
135
+ // ─────────────────────────────────────────────
136
+
137
+ brandedBackground(canvas, brandColor, {seed = 42, animate = true} = {}) {
138
+ if (!canvas) {
139
+ console.warn('@zakkster/lite-tools [brandedBackground]: canvas required');
140
+ return _NOOP;
141
+ }
142
+
143
+ const theme = generateTheme(brandColor, {mode: 'dark'});
144
+ const gradient = createGradient([theme.bg, theme.bgMuted, theme.surface, theme.accent], easeOut);
145
+ const gen = new GenEngine(canvas, {seed});
146
+ const field = new FlowField({noise: gen.noise, scale: 0.004, strength: 3, zSpeed: 0.2});
147
+
148
+ if (animate) {
149
+ gen.draw(({art, rng, dt}) => {
150
+ art.ctx.fillStyle = toCssOklch({...theme.bg, a: 0.05});
151
+ art.ctx.fillRect(0, 0, art.width, art.height);
152
+ field.update(dt);
153
+ for (let i = 0; i < 3; i++) {
154
+ let px = rng.range(0, art.width), py = rng.range(0, art.height);
155
+ art.ctx.beginPath();
156
+ art.ctx.moveTo(px, py);
157
+ for (let s = 0; s < 40; s++) {
158
+ const {vx, vy} = field.sample(px, py);
159
+ px += vx * 1.5;
160
+ py += vy * 1.5;
161
+ if (px < 0 || px > art.width || py < 0 || py > art.height) break;
162
+ art.ctx.lineTo(px, py);
163
+ }
164
+ art.ctx.strokeStyle = toCssOklch({...gradient(rng.next()), a: 0.08});
165
+ art.ctx.lineWidth = 1 + rng.next() * 2;
166
+ art.ctx.stroke();
167
+ }
168
+ });
169
+ gen.start();
170
+ } else {
171
+ gen.draw(({art, rng}) => {
172
+ art.background(theme.bg);
173
+ Pattern.flowTrace(art, {
174
+ field,
175
+ rng,
176
+ particleCount: 800,
177
+ steps: 300,
178
+ stepSize: 1.5,
179
+ colorFn: (_, t) => gradient(t),
180
+ lineWidth: 0.6,
181
+ alpha: 0.12
182
+ });
183
+ });
184
+ gen.render();
185
+ }
186
+
187
+ return {
188
+ gen, field, theme, gradient, destroy() {
189
+ gen.destroy();
190
+ }
191
+ };
192
+ },
193
+
194
+
195
+ // ─────────────────────────────────────────────
196
+ // ✨ 2. Premium Agency Button
197
+ // lite-ui + lite-particles + lite-color
198
+ // ─────────────────────────────────────────────
199
+
200
+ premiumButton(buttonSelector, overlayCanvas, {
201
+ brandColor = {l: 0.6, c: 0.25, h: 280},
202
+ hoverColor = {l: 0.7, c: 0.2, h: 300},
203
+ confettiColors,
204
+ magneticStrength = 0.4,
205
+ } = {}) {
206
+ const btn = _resolveEl(buttonSelector, 'premiumButton');
207
+ if (!btn || !overlayCanvas) return _NOOP;
208
+
209
+ const colors = confettiColors || [
210
+ brandColor, hoverColor,
211
+ {l: 0.8, c: 0.15, h: (brandColor.h + 60) % 360},
212
+ {l: 0.7, c: 0.2, h: (brandColor.h + 180) % 360},
213
+ {l: 0.9, c: 0.1, h: (brandColor.h + 120) % 360},
214
+ ];
215
+
216
+ const magnetic = new Magnetic(btn, {strength: magneticStrength, smoothing: 0.12, scale: true});
217
+ const colorShift = new ColorShift(btn, {
218
+ colors: [brandColor, hoverColor],
219
+ property: 'backgroundColor',
220
+ trigger: 'hover'
221
+ });
222
+ const confetti = new ConfettiBurst(overlayCanvas, {colors, count: 40, gravity: 500, life: 1.8});
223
+ confetti.attach(btn);
224
+
225
+ return {
226
+ magnetic, colorShift, confetti, destroy() {
227
+ destroyAll([magnetic, colorShift, confetti]);
228
+ }
229
+ };
230
+ },
231
+
232
+
233
+ // ─────────────────────────────────────────────
234
+ // 🌀 3. AAA Black Hole VFX
235
+ // lite-fx + lite-soa-particle-engine + lite-random
236
+ // ─────────────────────────────────────────────
237
+
238
+ blackHole(ctx, centerX, centerY, {
239
+ maxParticles = 15000, seed = 9999,
240
+ wellStrength = 5000, vortexStrength = 3000, vortexPull = 200,
241
+ } = {}) {
242
+ if (!ctx) {
243
+ console.warn('@zakkster/lite-tools [blackHole]: ctx required');
244
+ return _NOOP;
245
+ }
246
+
247
+ const fx = new FXSystem(ctx, {maxParticles, seed});
248
+ let removeWell = fx.addForce(new GravityWell(centerX, centerY, wellStrength, 600));
249
+ let removeVortex = fx.addForce(new Vortex(centerX, centerY, vortexStrength, vortexPull, 600));
250
+ fx.addForce(new DragField(0.96));
251
+
252
+ const fire = fx.register(Presets.fire);
253
+ const sparks = fx.register(Presets.sparks);
254
+ const explosion = fx.register(Presets.explosion);
255
+ fx.start();
256
+
257
+ return {
258
+ fx,
259
+ explode(x, y) {
260
+ fx.spawn(x, y, explosion);
261
+ fx.spawn(x, y, sparks);
262
+ fx.spawn(x, y, fire);
263
+ },
264
+ moveTo(x, y) {
265
+ removeWell();
266
+ removeVortex();
267
+ removeWell = fx.addForce(new GravityWell(x, y, wellStrength, 600));
268
+ removeVortex = fx.addForce(new Vortex(x, y, vortexStrength, vortexPull, 600));
269
+ },
270
+ destroy() {
271
+ fx.destroy();
272
+ },
273
+ };
274
+ },
275
+
276
+
277
+ // ─────────────────────────────────────────────
278
+ // 🌊 4. Choreographed Scroll Story
279
+ // lite-smart-observer + lite-ui + lite-lerp
280
+ // ─────────────────────────────────────────────
281
+
282
+ scrollStory({
283
+ heroSelector, heroSpeed = 0.3,
284
+ cardSelector, imageSelector, titleSelector,
285
+ progressBar, onProgress,
286
+ } = {}) {
287
+ const instances = [];
288
+
289
+ if (heroSelector && _resolveEl(heroSelector, 'scrollStory:hero'))
290
+ instances.push(new Parallax(heroSelector, {speed: heroSpeed}));
291
+ if (titleSelector && _resolveEls(titleSelector, 'scrollStory:titles').length)
292
+ instances.push(ScrollReveal.cascade(titleSelector, {stagger: 0.15, duration: 0.8}));
293
+ if (cardSelector && _resolveEls(cardSelector, 'scrollStory:cards').length)
294
+ instances.push(ScrollReveal.fadeUp(cardSelector, {
295
+ y: 50,
296
+ stagger: 0.08,
297
+ duration: 0.6,
298
+ ease: 'power3.out'
299
+ }));
300
+ if (imageSelector && _resolveEls(imageSelector, 'scrollStory:images').length)
301
+ instances.push(ScrollReveal.fadeIn(imageSelector, 'left', {duration: 0.8, ease: 'expo.out'}));
302
+ if (progressBar || onProgress)
303
+ instances.push(new ScrollProgress({
304
+ onChange(t) {
305
+ if (progressBar) progressBar.style.width = `${t * 100}%`;
306
+ if (onProgress) onProgress(t);
307
+ }
308
+ }));
309
+
310
+ return {
311
+ instances, destroy() {
312
+ destroyAll(instances);
313
+ }
314
+ };
315
+ },
316
+
317
+
318
+ // ─────────────────────────────────────────────
319
+ // 🖱️ 5. Particle Trail Cursor
320
+ // lite-particles + lite-pointer-tracker + lite-color + lite-ticker
321
+ // ─────────────────────────────────────────────
322
+
323
+ particleCursor(canvas, {
324
+ maxParticles = 200,
325
+ trailColor = {l: 0.9, c: 0.15, h: 50},
326
+ fadeColor = {l: 0.5, c: 0.2, h: 30},
327
+ spawnRate = 3,
328
+ } = {}) {
329
+ if (!canvas) {
330
+ console.warn('@zakkster/lite-tools [particleCursor]: canvas required');
331
+ return _NOOP;
332
+ }
333
+ const ctx = canvas.getContext('2d');
334
+ const emitter = new Emitter({maxParticles});
335
+ let mx = 0, my = 0, active = false;
336
+
337
+ const tracker = new PointerTracker(canvas, {
338
+ onStart(e) {
339
+ active = true;
340
+ mx = e.offsetX;
341
+ my = e.offsetY;
342
+ },
343
+ onMove(e) {
344
+ mx = e.offsetX;
345
+ my = e.offsetY;
346
+ },
347
+ onEnd() {
348
+ active = false;
349
+ },
350
+ });
351
+ canvas.addEventListener('mousemove', (e) => {
352
+ mx = e.offsetX;
353
+ my = e.offsetY;
354
+ active = true;
355
+ });
356
+ canvas.addEventListener('mouseleave', () => {
357
+ active = false;
358
+ });
359
+
360
+ const ticker = new Ticker();
361
+ ticker.add((dt) => {
362
+ const dtSec = dt / 1000;
363
+ if (active) {
364
+ for (let i = 0; i < spawnRate; i++) {
365
+ emitter.emit({
366
+ x: mx + (Math.random() - 0.5) * 10,
367
+ y: my + (Math.random() - 0.5) * 10,
368
+ vx: (Math.random() - 0.5) * 40,
369
+ vy: -Math.random() * 30 - 10,
370
+ gravity: -15,
371
+ drag: 0.96,
372
+ life: 0.5 + Math.random() * 0.3,
373
+ maxLife: 0.8,
374
+ size: 2 + Math.random() * 3
375
+ });
376
+ }
377
+ }
378
+ emitter.update(dtSec);
379
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
380
+ emitter.draw(ctx, (c, p, life) => {
381
+ const color = lerpOklch(fadeColor, trailColor, life);
382
+ c.fillStyle = toCssOklch({...color, a: life * life});
383
+ c.beginPath();
384
+ c.arc(p.x, p.y, p.size * life, 0, Math.PI * 2);
385
+ c.fill();
386
+ });
387
+ });
388
+ ticker.start();
389
+
390
+ return {
391
+ emitter, tracker, ticker, destroy() {
392
+ ticker.destroy();
393
+ tracker.destroy();
394
+ emitter.destroy();
395
+ }
396
+ };
397
+ },
398
+
399
+
400
+ // ─────────────────────────────────────────────
401
+ // 🌌 6. Procedural Starfield
402
+ // lite-gen + lite-random + lite-color + lite-viewport
403
+ // ─────────────────────────────────────────────
404
+
405
+ starfield(canvas, {seed = 42, starCount = 500, twinkleSpeed = 2} = {}) {
406
+ if (!canvas) {
407
+ console.warn('@zakkster/lite-tools [starfield]: canvas required');
408
+ return _NOOP;
409
+ }
410
+ const viewport = new Viewport({canvas, autoResize: true});
411
+ const rng = new Random(seed);
412
+ const stars = [];
413
+ for (let i = 0; i < starCount; i++) {
414
+ stars.push({
415
+ x: rng.next(),
416
+ y: rng.next(),
417
+ size: 0.5 + rng.next() * 2,
418
+ speed: 0.5 + rng.next() * 3,
419
+ phase: rng.next() * Math.PI * 2,
420
+ hue: rng.chance(0.1) ? rng.range(200, 280) : rng.range(40, 70),
421
+ bright: rng.chance(0.05)
422
+ });
423
+ }
424
+ const ticker = new Ticker();
425
+ ticker.add(() => {
426
+ const {ctx, width, height} = viewport;
427
+ const time = ticker.time / 1000;
428
+ ctx.fillStyle = '#04040a';
429
+ ctx.fillRect(0, 0, width, height);
430
+ for (const s of stars) {
431
+ const tw = (Math.sin(time * s.speed * twinkleSpeed + s.phase) + 1) / 2;
432
+ const l = s.bright ? 0.85 + tw * 0.15 : 0.5 + tw * 0.3;
433
+ ctx.fillStyle = toCssOklch({l, c: s.bright ? 0.05 : 0.02, h: s.hue, a: 0.3 + tw * 0.7});
434
+ ctx.beginPath();
435
+ ctx.arc(s.x * width, s.y * height, s.size * (0.8 + tw * 0.4), 0, Math.PI * 2);
436
+ ctx.fill();
437
+ }
438
+ });
439
+ ticker.start();
440
+ return {
441
+ viewport, ticker, stars, destroy() {
442
+ ticker.destroy();
443
+ viewport.destroy();
444
+ }
445
+ };
446
+ },
447
+
448
+
449
+ // ─────────────────────────────────────────────
450
+ // 🍔 7. Spring-Driven Navigation Menu
451
+ // lite-ui (Spring + FSM) + lite-color
452
+ // ─────────────────────────────────────────────
453
+
454
+ springMenu(menuSelector, toggleSelector, {
455
+ openColor = {l: 0.15, c: 0.03, h: 260},
456
+ closedColor = {l: 0.95, c: 0.01, h: 0},
457
+ stiffness = 200, damping = 22,
458
+ } = {}) {
459
+ const menuEl = _resolveEl(menuSelector, 'springMenu:menu');
460
+ const toggleEl = _resolveEl(toggleSelector, 'springMenu:toggle');
461
+ if (!menuEl || !toggleEl) return _NOOP;
462
+
463
+ const spring = new Spring(0, {stiffness, damping});
464
+ const fsm = new FSM('closed', {closed: ['open'], open: ['closed']});
465
+ let rafId;
466
+
467
+ fsm.onEnter('open', () => spring.set(1));
468
+ fsm.onEnter('closed', () => spring.set(0));
469
+ toggleEl.addEventListener('click', () => fsm.set(fsm.is('closed') ? 'open' : 'closed'));
470
+
471
+ const animate = () => {
472
+ const val = spring.update(1 / 60);
473
+ menuEl.style.transform = `translate3d(${lerp(-100, 0, val)}%, 0, 0)`;
474
+ menuEl.style.opacity = val;
475
+ menuEl.style.backgroundColor = toCssOklch(lerpOklch(closedColor, openColor, val));
476
+ toggleEl.style.transform = `rotate(${val * 90}deg)`;
477
+ if (!spring.settled) rafId = requestAnimationFrame(animate);
478
+ };
479
+
480
+ const origSet = spring.set.bind(spring);
481
+ spring.set = (t) => {
482
+ origSet(t);
483
+ if (rafId) cancelAnimationFrame(rafId);
484
+ rafId = requestAnimationFrame(animate);
485
+ };
486
+
487
+ return {
488
+ fsm, spring,
489
+ open() {
490
+ fsm.set('open');
491
+ }, close() {
492
+ fsm.set('closed');
493
+ },
494
+ toggle() {
495
+ fsm.set(fsm.is('closed') ? 'open' : 'closed');
496
+ },
497
+ destroy() {
498
+ if (rafId) cancelAnimationFrame(rafId);
499
+ fsm.destroy();
500
+ },
501
+ };
502
+ },
503
+
504
+
505
+ // ─────────────────────────────────────────────
506
+ // 🗺️ 8. Interactive Noise Heatmap
507
+ // lite-gen + lite-color + lite-random
508
+ // ─────────────────────────────────────────────
509
+
510
+ noiseHeatmap(canvas, {
511
+ seed = 42, scale = 0.008, cellSize = 6, animate = true,
512
+ gradient: gradColors = [
513
+ {l: 0.15, c: 0.08, h: 260}, {l: 0.4, c: 0.15, h: 200},
514
+ {l: 0.55, c: 0.2, h: 130}, {l: 0.7, c: 0.18, h: 90},
515
+ {l: 0.85, c: 0.1, h: 40}, {l: 0.95, c: 0.02, h: 0},
516
+ ],
517
+ } = {}) {
518
+ if (!canvas) {
519
+ console.warn('@zakkster/lite-tools [noiseHeatmap]: canvas required');
520
+ return _NOOP;
521
+ }
522
+ const gen = new GenEngine(canvas, {seed});
523
+ const gradient = createGradient(gradColors, easeInOut);
524
+
525
+ gen.draw(({art, noise, time}) => {
526
+ const cols = Math.ceil(art.width / cellSize), rows = Math.ceil(art.height / cellSize);
527
+ const z = animate ? time * 0.3 : 0;
528
+ for (let r = 0; r < rows; r++) {
529
+ for (let c = 0; c < cols; c++) {
530
+ const x = c * cellSize, y = r * cellSize;
531
+ const n = noise.fbm(x * scale, y * scale, 4, 2, 0.5);
532
+ const t = clamp((n + 1) / 2 + Math.sin(z) * 0.05, 0, 1);
533
+ art.ctx.fillStyle = toCssOklch(gradient(t));
534
+ art.ctx.fillRect(x, y, cellSize, cellSize);
535
+ }
536
+ }
537
+ });
538
+
539
+ if (animate) gen.start(); else gen.render();
540
+ return {
541
+ gen, gradient, reseed(s) {
542
+ gen.seed(s ?? Date.now());
543
+ if (!animate) gen.render();
544
+ }, destroy() {
545
+ gen.destroy();
546
+ }
547
+ };
548
+ },
549
+
550
+
551
+ // ─────────────────────────────────────────────
552
+ // 🎆 9. Choreographed Firework Show
553
+ // lite-fx + lite-ticker + lite-random + lite-color
554
+ // ─────────────────────────────────────────────
555
+
556
+ fireworkShow(ctx, width, height, {maxParticles = 8000, seed = 42, burstInterval = 800} = {}) {
557
+ if (!ctx) {
558
+ console.warn('@zakkster/lite-tools [fireworkShow]: ctx required');
559
+ return _NOOP;
560
+ }
561
+ const fx = new FXSystem(ctx, {maxParticles, seed});
562
+ const rng = new Random(seed);
563
+ const hues = [0, 30, 60, 130, 200, 280, 330];
564
+ const recipes = hues.map(h => fx.register({
565
+ count: [30, 60], life: [0.6, 1.2], speed: [80, 250], angle: [0, Math.PI * 2],
566
+ gravity: 120, friction: 0.96, size: [3, 0],
567
+ colorFn: createGradient([{l: 1, c: 0, h: 60}, {l: 0.8, c: 0.25, h}, {
568
+ l: 0.3,
569
+ c: 0.15,
570
+ h: (h + 30) % 360
571
+ }], easeOut),
572
+ blendMode: 'screen', shape: 'circle',
573
+ }));
574
+ fx.addForce(new DragField(0.97));
575
+ fx.start();
576
+ const ticker = new Ticker();
577
+ let running = true;
578
+ ticker.setInterval(() => {
579
+ if (!running) return;
580
+ fx.spawn(rng.range(width * 0.15, width * 0.85), rng.range(height * 0.15, height * 0.5), rng.pick(recipes), {shape: (r) => EmitterShape.circle(15, r)});
581
+ }, burstInterval);
582
+ ticker.start();
583
+
584
+ return {
585
+ fx, ticker,
586
+ stop() {
587
+ running = false;
588
+ }, resume() {
589
+ running = true;
590
+ },
591
+ manualBurst(x, y) {
592
+ fx.spawn(x, y, rng.pick(recipes));
593
+ },
594
+ destroy() {
595
+ ticker.destroy();
596
+ fx.destroy();
597
+ },
598
+ };
599
+ },
600
+
601
+
602
+ // ─────────────────────────────────────────────
603
+ // ❄️ 10. Ambient Snowfall
604
+ // lite-fx + lite-gen (turbulence) + lite-color
605
+ // ─────────────────────────────────────────────
606
+
607
+ snowfall(ctx, width, height, {maxParticles = 3000, seed = 42, windStrength = 30, turbulenceStrength = 60} = {}) {
608
+ if (!ctx) {
609
+ console.warn('@zakkster/lite-tools [snowfall]: ctx required');
610
+ return _NOOP;
611
+ }
612
+ const fx = new FXSystem(ctx, {maxParticles, seed});
613
+ const snow = fx.register({
614
+ count: [1, 3], life: [4, 8], speed: [10, 30], angle: [Math.PI / 2 - 0.3, Math.PI / 2 + 0.3],
615
+ gravity: 15, friction: 0.99, size: [2, 4],
616
+ colorFn: (t) => ({l: 0.9 + t * 0.1, c: 0.01, h: 220}), blendMode: 'source-over', shape: 'circle',
617
+ });
618
+ let windForce = new Wind(windStrength, 0);
619
+ fx.addForce(windForce);
620
+ fx.addForce(new Turbulence(turbulenceStrength, 0.008, 0.5));
621
+ fx.start();
622
+ const ticker = new Ticker();
623
+ ticker.setInterval(() => {
624
+ fx.spawn(fx.rng.range(-50, width + 50), -20, snow, {shape: (r) => EmitterShape.line(width * 0.3, r)});
625
+ }, 50);
626
+ ticker.start();
627
+
628
+ return {
629
+ fx, ticker,
630
+ setWind(s) {
631
+ fx.forces = fx.forces.filter(f => f !== windForce);
632
+ windForce = new Wind(s, 0);
633
+ fx.addForce(windForce);
634
+ },
635
+ destroy() {
636
+ ticker.destroy();
637
+ fx.destroy();
638
+ },
639
+ };
640
+ },
641
+
642
+
643
+ // ─────────────────────────────────────────────
644
+ // 🃏 11. Interactive Tilt Gallery
645
+ // lite-ui (Tilt + SparkleHover + ScrollReveal)
646
+ // ─────────────────────────────────────────────
647
+
648
+ tiltGallery(cardSelector, overlayCanvas, {
649
+ maxAngle = 12, sparkleColor = {
650
+ l: 0.95,
651
+ c: 0.1,
652
+ h: 50
653
+ }, sparkleRate = 3, revealStagger = 0.06
654
+ } = {}) {
655
+ const cards = _resolveEls(cardSelector, 'tiltGallery');
656
+ if (cards.length === 0) return _NOOP;
657
+ const instances = [];
658
+ instances.push(ScrollReveal.scaleIn(cardSelector, {stagger: revealStagger, duration: 0.5}));
659
+ cards.forEach(card => {
660
+ instances.push(new Tilt(card, {maxAngle, glare: true, scale: 1.03}));
661
+ if (overlayCanvas) instances.push(new SparkleHover(overlayCanvas, card, {
662
+ rate: sparkleRate,
663
+ color: sparkleColor,
664
+ life: 0.5
665
+ }));
666
+ });
667
+ return {
668
+ instances, destroy() {
669
+ destroyAll(instances);
670
+ }
671
+ };
672
+ },
673
+
674
+
675
+ // ─────────────────────────────────────────────
676
+ // 🎬 12. Deterministic Replay System
677
+ // lite-random + lite-fx + lite-fsm
678
+ // ─────────────────────────────────────────────
679
+
680
+ replaySystem(ctx, {maxParticles = 5000, seed = Date.now()} = {}) {
681
+ if (!ctx) {
682
+ console.warn('@zakkster/lite-tools [replaySystem]: ctx required');
683
+ return _NOOP;
684
+ }
685
+ const fx = new FXSystem(ctx, {maxParticles, seed});
686
+ const events = [];
687
+ let startTime = 0, replayRaf = null;
688
+ const fsm = new FSM('idle', {idle: ['recording', 'replaying'], recording: ['idle'], replaying: ['idle']});
689
+ const registeredRecipes = {};
690
+
691
+ return {
692
+ fx, fsm,
693
+ registerRecipe(name, preset) {
694
+ registeredRecipes[name] = fx.register(preset);
695
+ },
696
+ startRecording() {
697
+ events.length = 0;
698
+ startTime = performance.now();
699
+ fx.resetSeed(seed);
700
+ fx.clear();
701
+ fsm.set('recording');
702
+ fx.start();
703
+ },
704
+ recordEvent(x, y, name) {
705
+ if (!fsm.is('recording')) return;
706
+ const r = registeredRecipes[name];
707
+ if (!r) return;
708
+ events.push({time: performance.now() - startTime, x, y, recipeId: r.id});
709
+ fx.spawn(x, y, r);
710
+ },
711
+ stopRecording() {
712
+ fsm.set('idle');
713
+ return [...events];
714
+ },
715
+ replay() {
716
+ fsm.set('replaying');
717
+ fx.resetSeed(seed);
718
+ fx.clear();
719
+ fx.start();
720
+ let idx = 0;
721
+ const start = performance.now();
722
+ const step = () => {
723
+ if (!fsm.is('replaying')) return;
724
+ const t = performance.now() - start;
725
+ while (idx < events.length && events[idx].time <= t) {
726
+ const e = events[idx];
727
+ const r = fx._recipes?.[e.recipeId];
728
+ if (r) fx.spawn(e.x, e.y, r);
729
+ idx++;
730
+ }
731
+ if (idx < events.length) replayRaf = requestAnimationFrame(step); else fsm.set('idle');
732
+ };
733
+ replayRaf = requestAnimationFrame(step);
734
+ },
735
+ stopReplay() {
736
+ if (replayRaf) cancelAnimationFrame(replayRaf);
737
+ fsm.set('idle');
738
+ },
739
+ destroy() {
740
+ if (replayRaf) cancelAnimationFrame(replayRaf);
741
+ fx.destroy();
742
+ fsm.destroy();
743
+ },
744
+ };
745
+ },
746
+
747
+
748
+ // ─────────────────────────────────────────────
749
+ // 🎨 13. Live Theme Playground
750
+ // lite-theme-gen + lite-color
751
+ // ─────────────────────────────────────────────
752
+
753
+ themePlayground({initialBrand = {l: 0.6, c: 0.2, h: 260}, mode = 'light', prefix = 'app', onThemeChange} = {}) {
754
+ let palette = null, styleEl = null, currentMode = mode, currentBrand = initialBrand;
755
+
756
+ function apply(brand, m) {
757
+ currentBrand = brand;
758
+ currentMode = m;
759
+ palette = generateTheme(brand, {mode: m});
760
+ const css = toCssVariables(palette, {prefix, selector: ':root'});
761
+ if (!styleEl) {
762
+ styleEl = document.createElement('style');
763
+ document.head.appendChild(styleEl);
764
+ }
765
+ styleEl.textContent = css;
766
+ if (onThemeChange) onThemeChange(palette, css);
767
+ }
768
+
769
+ apply(initialBrand, mode);
770
+
771
+ return {
772
+ get palette() {
773
+ return palette;
774
+ },
775
+ setBrand(b) {
776
+ apply(b, currentMode);
777
+ },
778
+ setMode(m) {
779
+ apply(currentBrand, m);
780
+ },
781
+ toggleMode() {
782
+ apply(currentBrand, currentMode === 'light' ? 'dark' : 'light');
783
+ },
784
+ getCss() {
785
+ return styleEl?.textContent || '';
786
+ },
787
+ destroy() {
788
+ if (styleEl) {
789
+ styleEl.remove();
790
+ styleEl = null;
791
+ }
792
+ },
793
+ };
794
+ },
795
+
796
+
797
+ // ─────────────────────────────────────────────
798
+ // 🏗️ 14. Complete Game Canvas Setup
799
+ // ALL THE THINGS
800
+ // ─────────────────────────────────────────────
801
+
802
+ gameCanvas(canvas, {
803
+ fps = true, fpsPosition = 'top-right', seed = Date.now(), maxParticles = 5000,
804
+ states = {
805
+ loading: ['ready'],
806
+ ready: ['playing'],
807
+ playing: ['paused', 'gameover'],
808
+ paused: ['playing', 'gameover'],
809
+ gameover: ['ready']
810
+ },
811
+ } = {}) {
812
+ if (!canvas) {
813
+ console.warn('@zakkster/lite-tools [gameCanvas]: canvas required');
814
+ return _NOOP;
815
+ }
816
+
817
+ const viewport = new Viewport({canvas, autoResize: true, contextOptions: {alpha: false}});
818
+ const ticker = new Ticker();
819
+ const rng = new Random(seed);
820
+ const fsm = new FSM('loading', states);
821
+ const fx = new FXSystem(viewport.ctx, {maxParticles, seed});
822
+ let meter = fps ? new FPSMeter({position: fpsPosition}) : null;
823
+
824
+ fsm.onEnter('paused', () => {
825
+ ticker.pause();
826
+ fx.stop();
827
+ });
828
+ fsm.onEnter('playing', () => {
829
+ ticker.start();
830
+ fx.start();
831
+ });
832
+
833
+ return {
834
+ viewport, ctx: viewport.ctx, ticker, rng, fsm, fx, meter,
835
+ get width() {
836
+ return viewport.width;
837
+ },
838
+ get height() {
839
+ return viewport.height;
840
+ },
841
+ onUpdate(fn) {
842
+ return ticker.add(fn);
843
+ },
844
+ setState(s) {
845
+ return fsm.set(s);
846
+ },
847
+ get state() {
848
+ return fsm.current;
849
+ },
850
+ start() {
851
+ fsm.set('ready');
852
+ fsm.set('playing');
853
+ },
854
+ destroy() {
855
+ ticker.destroy();
856
+ fx.destroy();
857
+ viewport.destroy();
858
+ fsm.destroy();
859
+ if (meter) meter.destroy();
860
+ },
861
+ };
862
+ },
863
+ };
864
+
865
+ export default Recipes;
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@zakkster/lite-tools",
3
3
  "author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "description": "The standard library for high-performance web presentation — GSAP-level scroll reveals, Framer-level physics, Three.js-level particles, Tailwind-level color generation. Zero-GC, deterministic, tree-shakeable.",
6
6
  "type": "module",
7
7
  "main": "lite-tools.js",
8
8
  "types": "lite-tools.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./GenEngine.d.ts",
12
- "import": "./GenEngine.js",
13
- "default": "./GenEngine.js"
11
+ "types": "./LiteEngine.d.ts",
12
+ "import": "./LiteEngine.js",
13
+ "default": "./LiteEngine.js"
14
14
  }
15
15
  },
16
16
  "files": [
17
- "GenEngine.js",
18
- "GenEngine.d.ts",
17
+ "LiteEngine.js",
18
+ "LiteEngine.d.ts",
19
19
  "README.md"
20
20
  ],
21
21
  "license": "MIT",
@@ -39,6 +39,15 @@
39
39
  "lite-asset-loader": "^1.0.0",
40
40
  "lite-audio-manager": "^1.0.0"
41
41
  },
42
+ "homepage": "https://github.com/PeshoVurtoleta/-zakkster-lite-tools#readme",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/PeshoVurtoleta/-zakkster-lite-tools.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/PeshoVurtoleta/-zakkster-lite-tools/issues",
49
+ "email": "shinikchiev@yahoo.com"
50
+ },
42
51
  "keywords": [
43
52
  "toolkit",
44
53
  "micro-library",