godlights 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/dist/index.d.ts +610 -17
  2. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,29 +1,119 @@
1
1
  import { default as default_2 } from 'react';
2
2
  import { JSX as JSX_2 } from 'react/jsx-runtime';
3
3
 
4
- /** Parameters that control the animation loop — not persisted in scene. */
4
+ /**
5
+ * Parameters that drive the animation loop. These are **not** stored inside
6
+ * `SceneConfig` — they live separately so that the same scene can be rendered
7
+ * statically or animated without touching the scene object.
8
+ *
9
+ * **Common mistake:** there is NO `opacityAmp` field. The only amplitude
10
+ * controls are `angleAmp`, `lengthAmp`, `widthAmp`, and `haloAmp`.
11
+ *
12
+ * @example
13
+ * // Gentle sway, no width variation
14
+ * const anim: AnimParams = {
15
+ * speed: 0.8,
16
+ * angleAmp: 60,
17
+ * lengthAmp: 40,
18
+ * widthAmp: 0, // ← set to 0 to disable an axis entirely
19
+ * haloAmp: 50,
20
+ * };
21
+ */
5
22
  export declare interface AnimParams {
6
- /** Time multiplier — higher = faster (default 1). */
23
+ /**
24
+ * Global time multiplier. Scales how fast the elapsed-time clock ticks.
25
+ * 1.0 = real-time, 2.0 = twice as fast, 0.5 = slow motion.
26
+ * Range: 0.01–10. Default: 1.
27
+ */
7
28
  speed: number;
8
- /** Angle swing intensity 0–100 (default 50). */
29
+ /**
30
+ * Controls how much each ray's angle oscillates per frame.
31
+ * 0 = rays never swing, 100 = maximum swing.
32
+ * Range: 0–100. Default: 50.
33
+ */
9
34
  angleAmp: number;
10
- /** Ray length oscillation intensity 0–100 (default 50). */
35
+ /**
36
+ * Controls how much each ray's length pulsates.
37
+ * 0 = static length, 100 = maximum pulsation.
38
+ * Range: 0–100. Default: 50.
39
+ */
11
40
  lengthAmp: number;
12
- /** Ray width oscillation intensity 0–100 (default 50). */
41
+ /**
42
+ * Controls how much each ray's width breathes in and out.
43
+ * 0 = static width, 100 = maximum variation.
44
+ * Range: 0–100. Default: 50.
45
+ */
13
46
  widthAmp: number;
14
- /** Halo pulse intensity 0–100 (default 50). */
47
+ /**
48
+ * Controls how much the halo radius pulses.
49
+ * 0 = static halo, 100 = most pronounced pulse.
50
+ * Range: 0–100. Default: 50.
51
+ */
15
52
  haloAmp: number;
16
53
  }
17
54
 
55
+ /**
56
+ * The mandatory first layer in every scene. It clears the canvas and paints
57
+ * the backdrop before any rays or halos are composited on top.
58
+ *
59
+ * **This must always be `layers[0]`.** If it is missing or placed at any
60
+ * other index the canvas will not be cleared between animation frames, causing
61
+ * ray trails and other visual artifacts.
62
+ *
63
+ * @example
64
+ * const bg: BackgroundLayer = {
65
+ * id: "background", // must be the literal string "background"
66
+ * type: "background",
67
+ * bgType: "gradient",
68
+ * bgColor: "#0b1024", // gradient start (top by default)
69
+ * bgColor2: "#1a1340", // gradient end (bottom by default)
70
+ * bgGradientAngle: 180, // 180° = top-to-bottom
71
+ * };
72
+ */
18
73
  export declare interface BackgroundLayer {
74
+ /**
75
+ * Fixed identifier — must always be the string literal `"background"`.
76
+ * Only one BackgroundLayer per scene is supported.
77
+ */
19
78
  id: "background";
79
+ /** Discriminator — always `"background"` for this layer type. */
20
80
  type: "background";
81
+ /**
82
+ * Controls how the background is drawn.
83
+ * - `"transparent"` — nothing is painted; the canvas keeps whatever was
84
+ * there before (or the browser default, usually white/transparent).
85
+ * - `"solid"` — fills the canvas with `bgColor`.
86
+ * - `"gradient"` — linear gradient from `bgColor` to `bgColor2`.
87
+ */
21
88
  bgType: BackgroundType;
89
+ /**
90
+ * Primary background color (solid fill, or gradient start).
91
+ * CSS hex string, e.g. `"#0b1024"`.
92
+ */
22
93
  bgColor: string;
94
+ /**
95
+ * Secondary background color used as the gradient end when
96
+ * `bgType === "gradient"`. Ignored for `"solid"` and `"transparent"`.
97
+ */
23
98
  bgColor2: string;
99
+ /**
100
+ * Angle of the linear gradient in degrees. 0 = left-to-right,
101
+ * 90 = bottom-to-top, 180 = top-to-bottom (default), 270 = top-to-bottom.
102
+ * Range: 0–360.
103
+ */
24
104
  bgGradientAngle: number;
25
105
  }
26
106
 
107
+ /**
108
+ * How the background layer fills the canvas.
109
+ *
110
+ * - `"transparent"` — does not paint anything; the canvas keeps its current
111
+ * state (or whatever the browser renders behind it). Useful when the
112
+ * component is overlaid on top of existing content.
113
+ * - `"solid"` — fills the entire canvas with `bgColor`.
114
+ * - `"gradient"` — fills with a linear gradient from `bgColor` to `bgColor2`
115
+ * at the angle specified by `bgGradientAngle`.
116
+ */
27
117
  export declare type BackgroundType = "transparent" | "solid" | "gradient";
28
118
 
29
119
  export declare const BLEND_MODES: {
@@ -32,8 +122,43 @@ export declare const BLEND_MODES: {
32
122
  }[];
33
123
 
34
124
  /**
35
- * God Rays / Light Rays rendering engine
36
- * Supports multiple independent ray and halo layers per scene.
125
+ * God Rays / Light Rays rendering engine.
126
+ *
127
+ * Renders a layered scene of light rays, halos, and backgrounds onto a
128
+ * `HTMLCanvasElement`. Supports static one-shot rendering and a
129
+ * `requestAnimationFrame` loop driven by an elapsed-time clock.
130
+ *
131
+ * **Typical layer stack (back → front)**
132
+ * 1. `BackgroundLayer` (index 0, **required** — clears the canvas each frame)
133
+ * 2. One or more `HaloLayer`s
134
+ * 3. One or more `RayLayer`s
135
+ *
136
+ * @example
137
+ * // Minimal static render
138
+ * import { drawScene, DEFAULT_SCENE } from "./godrays";
139
+ * const canvas = document.createElement("canvas");
140
+ * canvas.width = 1920; canvas.height = 1080;
141
+ * drawScene(canvas, DEFAULT_SCENE);
142
+ * document.body.appendChild(canvas);
143
+ */
144
+ /**
145
+ * Canvas 2-D composite operation to use when blending a layer onto the scene.
146
+ *
147
+ * **Choosing the right mode:**
148
+ * - `"screen"` / `"lighter"` — additive-style brightening. Only looks correct
149
+ * on **dark backgrounds**; on light backgrounds they blow out to white.
150
+ * - `"multiply"` — darkening blend. Use on **light/white backgrounds** to keep
151
+ * the rays visible without washing them out.
152
+ * - `"source-over"` — plain alpha compositing, background-agnostic.
153
+ * - `"overlay"` / `"soft-light"` / `"hard-light"` — contrast-boosting blends;
154
+ * effect depends heavily on the underlying colors.
155
+ *
156
+ * @example
157
+ * // Good for a dark background
158
+ * const darkBgRay: Partial<RayLayer> = { blendMode: "lighter" };
159
+ *
160
+ * // Good for a light/white background
161
+ * const lightBgRay: Partial<RayLayer> = { blendMode: "multiply" };
37
162
  */
38
163
  export declare type BlendMode = "source-over" | "lighter" | "screen" | "overlay" | "soft-light" | "hard-light";
39
164
 
@@ -43,45 +168,307 @@ export declare function buildSceneCssSnippet(scene: SceneConfig): Promise<string
43
168
 
44
169
  export declare const DEFAULT_ANIM_PARAMS: AnimParams;
45
170
 
171
+ /**
172
+ * Ready-to-use solid black `BackgroundLayer`. Use as `layers[0]` in every
173
+ * scene. The `id` is already set to the required literal `"background"`.
174
+ *
175
+ * @example
176
+ * const scene: SceneConfig = {
177
+ * width: 1920, height: 1080, noise: 8, grainSize: 1,
178
+ * layers: [
179
+ * { ...DEFAULT_BACKGROUND_LAYER, bgColor: "#0b1024" },
180
+ * { ...DEFAULT_HALO_LAYER, id: "halo-1", name: "Halo" },
181
+ * ],
182
+ * };
183
+ */
46
184
  export declare const DEFAULT_BACKGROUND_LAYER: BackgroundLayer;
47
185
 
48
186
  export declare const DEFAULT_CONFIG: GodRaysConfig;
49
187
 
188
+ /**
189
+ * Ready-to-use base values for a `HaloLayer`. Spread and add `id` / `name`.
190
+ *
191
+ * Produces a white glow at the top-center of the canvas, with radius = 25% of
192
+ * the canvas diagonal, peak intensity 0.5, and additive `"lighter"` blending.
193
+ *
194
+ * @example
195
+ * const myHalo: HaloLayer = { ...DEFAULT_HALO_LAYER, id: "halo-2", name: "Secondary glow" };
196
+ */
50
197
  export declare const DEFAULT_HALO_LAYER: Omit<HaloLayer, "id" | "name">;
51
198
 
199
+ /**
200
+ * Ready-to-use base values for a `RayLayer`. Spread this object and supply
201
+ * the required `id` and `name` fields to create a new layer quickly.
202
+ *
203
+ * Produces a white, top-centered fan pointing downward with 24 rays,
204
+ * additive `"lighter"` blending (dark backgrounds only), and a soft 8 px blur.
205
+ *
206
+ * @example
207
+ * const myRays: RayLayer = { ...DEFAULT_RAY_LAYER, id: "rays-2", name: "Accent rays" };
208
+ */
52
209
  export declare const DEFAULT_RAY_LAYER: Omit<RayLayer, "id" | "name">;
53
210
 
211
+ /**
212
+ * A complete, ready-to-render scene: 1920×1080, solid black background, one
213
+ * white halo, and one 24-ray fan. Safe to pass directly to `drawScene` or
214
+ * the `<GodLights>` component.
215
+ *
216
+ * Deep-clone this object before mutating it so that multiple scenes do not
217
+ * share the same layer array reference.
218
+ *
219
+ * @example
220
+ * import { drawScene, DEFAULT_SCENE } from "./godrays";
221
+ *
222
+ * const canvas = document.createElement("canvas");
223
+ * canvas.width = DEFAULT_SCENE.width;
224
+ * canvas.height = DEFAULT_SCENE.height;
225
+ * drawScene(canvas, DEFAULT_SCENE);
226
+ * document.body.appendChild(canvas);
227
+ */
54
228
  export declare const DEFAULT_SCENE: SceneConfig;
55
229
 
56
230
  export declare function drawGodRays(canvas: HTMLCanvasElement, config: GodRaysConfig): void;
57
231
 
232
+ /**
233
+ * Renders a complete `SceneConfig` onto a `HTMLCanvasElement`.
234
+ *
235
+ * Call this once for a static image, or on every `requestAnimationFrame` tick
236
+ * for animation — increment `time` by `deltaSeconds * speed` each frame.
237
+ *
238
+ * The canvas `width` and `height` attributes are **not** set by this function;
239
+ * make sure they match `scene.width` / `scene.height` before calling.
240
+ *
241
+ * @param canvas - Target canvas element. Must already be appended to the DOM
242
+ * (or at least have a valid 2-D context) before this is called.
243
+ * @param scene - Scene configuration. `scene.layers[0]` **must** be a
244
+ * `BackgroundLayer`; otherwise the canvas is not cleared between frames and
245
+ * you will see ghosting / smearing artifacts.
246
+ * @param time - Elapsed animation time in seconds (default `0`). When `0`,
247
+ * every ray is rendered at its rest position with no oscillation. Pass a
248
+ * monotonically increasing value to animate.
249
+ * @param anim - Optional amplitude modifiers. Only used when `time !== 0`.
250
+ * **Note:** there is no `opacityAmp` — the valid keys are `speed`,
251
+ * `angleAmp`, `lengthAmp`, `widthAmp`, and `haloAmp`.
252
+ * @param skipGrain - When `true`, the film-grain pass is skipped. Set this to
253
+ * `true` in animated mode when grain is handled by a separate static overlay
254
+ * canvas (as `GodLights` does) to avoid re-generating grain every frame.
255
+ *
256
+ * @example
257
+ * // Static render
258
+ * const canvas = document.getElementById("canvas") as HTMLCanvasElement;
259
+ * canvas.width = scene.width;
260
+ * canvas.height = scene.height;
261
+ * drawScene(canvas, scene);
262
+ *
263
+ * @example
264
+ * // Animated render loop
265
+ * let time = 0;
266
+ * let last: number | null = null;
267
+ * const anim: AnimParams = { speed: 1, angleAmp: 50, lengthAmp: 50, widthAmp: 50, haloAmp: 50 };
268
+ *
269
+ * function frame(ts: number) {
270
+ * if (last !== null) time += ((ts - last) / 1000) * anim.speed;
271
+ * last = ts;
272
+ * drawScene(canvas, scene, time, anim);
273
+ * requestAnimationFrame(frame);
274
+ * }
275
+ * requestAnimationFrame(frame);
276
+ */
58
277
  export declare function drawScene(canvas: HTMLCanvasElement, scene: SceneConfig, time?: number, anim?: AnimParams, skipGrain?: boolean): void;
59
278
 
279
+ /**
280
+ * Renders the legacy flat `GodRaysConfig` and returns the result as a PNG or
281
+ * JPEG data URL string (a `"data:image/..."` Base64-encoded string).
282
+ *
283
+ * Use this when you need an inline image string — e.g. to set as a CSS
284
+ * `background-image`, embed in an `<img src>`, or store in `localStorage`.
285
+ * For downloads or network uploads, prefer `exportScene` which returns a
286
+ * `Blob` instead (more memory-efficient for large images).
287
+ *
288
+ * @param config - Legacy flat config. For the layer-based API use
289
+ * `exportScene` with a `SceneConfig` instead.
290
+ * @param type - MIME type: `"image/png"` or `"image/jpeg"`.
291
+ * @param quality - JPEG quality 0–1 (default `0.95`). Ignored for PNG.
292
+ * @returns A `Promise` resolving to a Base64 data URL string.
293
+ *
294
+ * @example
295
+ * const dataUrl = await exportDataURL(config, "image/png");
296
+ * document.body.style.backgroundImage = `url("${dataUrl}")`;
297
+ */
60
298
  export declare function exportDataURL(config: GodRaysConfig, type: "image/png" | "image/jpeg", quality?: number): Promise<string>;
61
299
 
62
300
  export declare function exportImage(config: GodRaysConfig, type: "image/png" | "image/jpeg", quality?: number): Promise<Blob>;
63
301
 
302
+ /**
303
+ * Renders `scene` at its native resolution and returns the result as a PNG or
304
+ * JPEG `Blob` suitable for `URL.createObjectURL`, `<a download>`, or
305
+ * `FormData` uploads.
306
+ *
307
+ * Internally creates a temporary off-screen `<canvas>` (never attached to the
308
+ * DOM), draws the full scene statically (`time = 0`, grain enabled), and calls
309
+ * `canvas.toBlob`.
310
+ *
311
+ * @param scene - Scene to render. Uses `scene.width` × `scene.height` as the
312
+ * canvas dimensions.
313
+ * @param type - MIME type. Use `"image/png"` for lossless export or
314
+ * `"image/jpeg"` for smaller files with lossy compression.
315
+ * @param quality - Compression quality for JPEG, 0–1 (default `0.95`).
316
+ * Ignored for PNG.
317
+ * @returns A `Promise` that resolves to a `Blob` containing the encoded image.
318
+ *
319
+ * @example
320
+ * const blob = await exportScene(scene, "image/png");
321
+ * const url = URL.createObjectURL(blob);
322
+ * const a = document.createElement("a");
323
+ * a.href = url;
324
+ * a.download = "godlights.png";
325
+ * a.click();
326
+ * URL.revokeObjectURL(url);
327
+ */
64
328
  export declare function exportScene(scene: SceneConfig, type: "image/png" | "image/jpeg", quality?: number): Promise<Blob>;
65
329
 
66
330
  /**
67
- * Standalone Godlights canvas component.
331
+ * Standalone canvas component that renders a Godlights scene.
332
+ *
333
+ * The outer wrapper is a `position: relative` `<div>` with `overflow: hidden`.
334
+ * Inside it, one `<canvas>` is used for the main render. In animated mode a
335
+ * second `<canvas>` is added as a fixed grain overlay — grain is drawn once
336
+ * and composited with `mixBlendMode: "overlay"`, avoiding the cost of
337
+ * regenerating noise on every frame.
338
+ *
339
+ * **Positioning:** the component does not set its own size. You must give it
340
+ * explicit dimensions via `className`, `style`, or a parent constraint.
341
+ * For a full-bleed background behind other content:
342
+ *
343
+ * ```tsx
344
+ * // Parent must have position: relative (or absolute/fixed)
345
+ * <div style={{ position: "relative" }}>
346
+ * <GodLights
347
+ * scene={myScene}
348
+ * style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }}
349
+ * />
350
+ * <p style={{ position: "relative" }}>Content on top</p>
351
+ * </div>
352
+ * ```
353
+ *
354
+ * **Blend modes:** `scene` layers using `blendMode: "screen"` or
355
+ * `blendMode: "lighter"` look correct only on dark backgrounds. Switch to
356
+ * `blendMode: "multiply"` when the background layer is light or white.
357
+ *
358
+ * **`opacityAmp` does not exist.** The animation amplitude fields are:
359
+ * `angleAmp`, `lengthAmp`, `widthAmp`, and `haloAmp` (all 0–100).
360
+ *
361
+ * @example
362
+ * // Static render — draws once, no RAF loop
363
+ * import { GodLights, DEFAULT_SCENE } from "@your-org/godlights";
364
+ *
365
+ * export function HeroBackground() {
366
+ * return (
367
+ * <GodLights
368
+ * scene={DEFAULT_SCENE}
369
+ * className="w-full h-64"
370
+ * />
371
+ * );
372
+ * }
68
373
  *
69
374
  * @example
70
- * <GodLights scene={myScene} className="w-full h-full" />
71
- * <GodLights scene={myScene} animate animParams={{ speed: 1, angleAmp: 50, lengthAmp: 50, widthAmp: 50, haloAmp: 50 }} />
375
+ * // Animated render continuous RAF loop with custom amplitudes
376
+ * import { GodLights, DEFAULT_SCENE, AnimParams } from "@your-org/godlights";
377
+ * import { useMemo } from "react";
378
+ *
379
+ * export function AnimatedBackground() {
380
+ * const animParams: AnimParams = useMemo(() => ({
381
+ * speed: 1,
382
+ * angleAmp: 60, // moderate swing
383
+ * lengthAmp: 40, // subtle length pulsation
384
+ * widthAmp: 20, // gentle width breathing
385
+ * haloAmp: 50, // standard halo pulse
386
+ * // ⚠️ opacityAmp does NOT exist — omit it
387
+ * }), []);
388
+ *
389
+ * return (
390
+ * <div style={{ position: "relative", height: 480 }}>
391
+ * <GodLights
392
+ * scene={DEFAULT_SCENE}
393
+ * animate
394
+ * animParams={animParams}
395
+ * showFps // optional FPS badge for dev
396
+ * style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }}
397
+ * />
398
+ * <h1 style={{ position: "relative", color: "#fff" }}>Hello</h1>
399
+ * </div>
400
+ * );
401
+ * }
72
402
  */
73
403
  export declare function GodLights({ scene, animate, animParams, showFps, className, style, }: GodLightsProps): JSX_2.Element;
74
404
 
405
+ /**
406
+ * Props for the `<GodLights>` React component.
407
+ *
408
+ * **Positioning note:** the component renders a `position: relative` `<div>`
409
+ * that contains one or two absolutely-positioned `<canvas>` elements. To make
410
+ * it fill a parent container as a full-bleed background, give the parent
411
+ * `position: relative` (or `absolute`) and pass:
412
+ * ```tsx
413
+ * style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }}
414
+ * ```
415
+ */
75
416
  export declare interface GodLightsProps {
76
- /** Full scene configuration — use the Godlights editor to build this. */
417
+ /**
418
+ * Full scene configuration describing the background, halos, and ray layers.
419
+ * Build this with the Godlights visual editor, or construct it manually
420
+ * using `DEFAULT_SCENE` / `DEFAULT_RAY_LAYER` as starting points.
421
+ *
422
+ * **`scene.layers[0]` must be a `BackgroundLayer`** — it is the only thing
423
+ * that clears the canvas between animation frames. Omitting it causes ray
424
+ * trails in animated mode.
425
+ *
426
+ * The component re-renders whenever this prop reference changes, so avoid
427
+ * constructing the object inline in JSX; memoize it with `useMemo` or
428
+ * define it outside the component.
429
+ */
77
430
  scene: SceneConfig;
78
- /** Enable animation loop. */
431
+ /**
432
+ * When `true`, starts a `requestAnimationFrame` loop that continuously
433
+ * redraws the scene with an incrementing time clock, producing smooth
434
+ * animation. When `false` (default), the scene is drawn once statically.
435
+ */
79
436
  animate?: boolean;
80
- /** Animation parameters (speed, amplitudes). Only used when animate=true. */
437
+ /**
438
+ * Amplitude and speed settings for the animation loop. Only consulted when
439
+ * `animate={true}`.
440
+ *
441
+ * **Common mistake:** there is NO `opacityAmp` field. The valid keys are:
442
+ * - `speed` — global time multiplier (default `1`)
443
+ * - `angleAmp` — ray swing amount 0–100 (default `50`)
444
+ * - `lengthAmp` — ray length pulsation 0–100 (default `50`)
445
+ * - `widthAmp` — ray width breathing 0–100 (default `50`)
446
+ * - `haloAmp` — halo size pulse 0–100 (default `50`)
447
+ *
448
+ * Omit this prop to use the defaults from `DEFAULT_ANIM_PARAMS`.
449
+ */
81
450
  animParams?: AnimParams;
82
- /** Show FPS counter overlay. Only visible when animate=true. */
451
+ /**
452
+ * When `true`, a small FPS counter badge is rendered in the top-right corner
453
+ * of the canvas. Only visible when `animate={true}`. Useful during
454
+ * development to check rendering performance. Default: `false`.
455
+ */
83
456
  showFps?: boolean;
457
+ /**
458
+ * Optional CSS class name applied to the outer wrapper `<div>`.
459
+ * Useful for Tailwind sizing utilities, e.g. `className="w-full h-full"`.
460
+ */
84
461
  className?: string;
462
+ /**
463
+ * Inline styles merged onto the outer wrapper `<div>`, which already has
464
+ * `position: "relative"` and `overflow: "hidden"` set.
465
+ *
466
+ * **Full-bleed background pattern** — add this to a relatively-positioned
467
+ * parent and pass:
468
+ * ```tsx
469
+ * style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }}
470
+ * ```
471
+ */
85
472
  style?: default_2.CSSProperties;
86
473
  }
87
474
 
@@ -121,15 +508,60 @@ export declare interface GodRaysConfig {
121
508
  seed: number;
122
509
  }
123
510
 
511
+ /**
512
+ * A layer that renders a soft radial glow centered on a point. Useful for
513
+ * simulating a light source (sun, lamp, portal, etc.) at the ray origin.
514
+ *
515
+ * @example
516
+ * const halo: HaloLayer = {
517
+ * id: "halo-1",
518
+ * type: "halo",
519
+ * name: "Sun glow",
520
+ * originX: 50, // center of canvas
521
+ * originY: 0, // top edge
522
+ * intensity: 0.5, // 0–1 peak opacity at center
523
+ * size: 0.25, // radius = 25% of canvas diagonal
524
+ * color: "#ffd28a", // warm amber
525
+ * blendMode: "lighter",
526
+ * };
527
+ */
124
528
  export declare interface HaloLayer {
529
+ /** Unique identifier. Must be distinct from all other layer ids. */
125
530
  id: string;
531
+ /** Discriminator — always `"halo"` for this layer type. */
126
532
  type: "halo";
533
+ /** Human-readable label shown in the editor UI. */
127
534
  name: string;
535
+ /**
536
+ * Horizontal center of the halo as a percentage of canvas width.
537
+ * Range: 0–100. 50 = horizontally centered.
538
+ */
128
539
  originX: number;
540
+ /**
541
+ * Vertical center of the halo as a percentage of canvas height.
542
+ * Range: 0–100. 0 = top edge, 100 = bottom edge.
543
+ */
129
544
  originY: number;
545
+ /**
546
+ * Peak opacity at the center of the radial gradient.
547
+ * Range: 0–1. 0 = invisible, 1 = fully opaque at center.
548
+ */
130
549
  intensity: number;
550
+ /**
551
+ * Radius of the halo as a multiplier of the canvas diagonal.
552
+ * 0.25 means the halo radius is 25% of `Math.hypot(width, height)`.
553
+ * Range: 0.01–2. Typical: 0.1–0.5.
554
+ */
131
555
  size: number;
556
+ /**
557
+ * CSS hex color of the glow.
558
+ * @example "#ffffff"
559
+ */
132
560
  color: string;
561
+ /**
562
+ * Canvas composite operation. Use `"lighter"` or `"screen"` for dark
563
+ * backgrounds; use `"multiply"` for light/white backgrounds.
564
+ */
133
565
  blendMode: BlendMode;
134
566
  }
135
567
 
@@ -143,38 +575,199 @@ export declare type Layer = RayLayer | HaloLayer | BackgroundLayer;
143
575
 
144
576
  export declare function mulberry32(seed: number): () => number;
145
577
 
578
+ /**
579
+ * A layer that renders a fan of light-beam polygons radiating from an origin
580
+ * point. All numeric positions are expressed as percentages of the canvas
581
+ * dimensions (0–100) unless otherwise noted.
582
+ *
583
+ * @example
584
+ * const rays: RayLayer = {
585
+ * id: "rays-1",
586
+ * type: "rays",
587
+ * name: "Sun rays",
588
+ * direction: 200, // pointing downward-left (compass degrees)
589
+ * spread: 60, // 60° fan
590
+ * originX: 50, // centered horizontally
591
+ * originY: 0, // at the top edge
592
+ * rayCount: 24,
593
+ * rayWidth: 60, // px at the origin end
594
+ * divergence: 1.6, // tip is 1.6× wider than the base
595
+ * rayLength: 1.4, // 1.4× the canvas diagonal
596
+ * opacity: 0.6, // 0–1
597
+ * blendMode: "lighter", // additive — good on dark backgrounds
598
+ * colorStart: "#ffffff",
599
+ * colorEnd: "#ffffff",
600
+ * fadeToTransparent: true,
601
+ * blur: 8,
602
+ * randomnessWidth: 30,
603
+ * randomnessLength: 18,
604
+ * randomnessAngle: 30,
605
+ * seed: 1337,
606
+ * };
607
+ */
146
608
  export declare interface RayLayer {
609
+ /** Unique identifier. Must be distinct from all other layer ids. */
147
610
  id: string;
611
+ /** Discriminator — always `"rays"` for this layer type. */
148
612
  type: "rays";
613
+ /** Human-readable label shown in the editor UI. */
149
614
  name: string;
615
+ /**
616
+ * Compass bearing the rays point toward, in degrees. 0 = North (up),
617
+ * 90 = East (right), 180 = South (down), 270 = West (left).
618
+ * Range: 0–360.
619
+ */
150
620
  direction: number;
621
+ /**
622
+ * Total angular width of the ray fan, in degrees.
623
+ * Range: 1–360. Typical: 30–120.
624
+ */
151
625
  spread: number;
626
+ /**
627
+ * Horizontal origin as a percentage of the canvas width.
628
+ * Range: 0–100. 50 = horizontally centered.
629
+ */
152
630
  originX: number;
631
+ /**
632
+ * Vertical origin as a percentage of the canvas height.
633
+ * Range: 0–100. 0 = top edge, 100 = bottom edge.
634
+ */
153
635
  originY: number;
636
+ /**
637
+ * Number of individual ray polygons to draw.
638
+ * Range: 1–200. Typical: 10–40.
639
+ */
154
640
  rayCount: number;
641
+ /**
642
+ * Base width of each ray polygon at the origin end, in pixels.
643
+ * Range: 1–500. Typical: 10–120.
644
+ */
155
645
  rayWidth: number;
646
+ /**
647
+ * Tip-to-base width ratio — how much wider the far end is relative to the
648
+ * near end. 1.0 = uniform beam, 2.0 = tip is twice as wide.
649
+ * Range: 0.1–10. Typical: 1.0–3.0.
650
+ */
156
651
  divergence: number;
652
+ /**
653
+ * Length of each ray as a multiplier of the canvas diagonal.
654
+ * 1.0 = exactly fits corner-to-corner. 1.4 overshoots a bit.
655
+ * Range: 0.1–5. Typical: 0.8–2.0.
656
+ */
157
657
  rayLength: number;
658
+ /**
659
+ * Master opacity of the entire layer.
660
+ * Range: 0–1. 0 = invisible, 1 = fully opaque.
661
+ */
158
662
  opacity: number;
663
+ /**
664
+ * Canvas composite operation. Use `"lighter"` or `"screen"` for dark
665
+ * backgrounds; use `"multiply"` for light/white backgrounds.
666
+ */
159
667
  blendMode: BlendMode;
668
+ /**
669
+ * Gradient color at the origin (near) end of each ray. CSS hex string.
670
+ * @example "#ffd28a"
671
+ */
160
672
  colorStart: string;
673
+ /**
674
+ * Gradient color at the tip (far) end of each ray. CSS hex string.
675
+ * Ignored when `fadeToTransparent` is `true` (the tip becomes fully
676
+ * transparent instead).
677
+ */
161
678
  colorEnd: string;
679
+ /**
680
+ * When `true`, rays fade from `colorStart` at the origin to fully
681
+ * transparent at the tip, producing a natural light-beam look.
682
+ * When `false`, the gradient goes from `colorStart` to `colorEnd`.
683
+ */
162
684
  fadeToTransparent: boolean;
685
+ /**
686
+ * Gaussian blur applied to the ray shapes before compositing, in pixels.
687
+ * Values > 0 soften hard edges and produce a glow effect.
688
+ * Range: 0–80. Typical: 4–20.
689
+ */
163
690
  blur: number;
164
- /** @deprecated use randomnessWidth/Length/Angle */
691
+ /**
692
+ * @deprecated Replaced by the separate `randomnessWidth`, `randomnessLength`,
693
+ * and `randomnessAngle` fields. Still accepted for backward compatibility.
694
+ */
165
695
  randomness?: number;
696
+ /**
697
+ * Per-ray width variation, as a percentage of `rayWidth`.
698
+ * Range: 0–100. 0 = all rays identical width, 100 = maximum variation.
699
+ */
166
700
  randomnessWidth: number;
701
+ /**
702
+ * Per-ray length variation, as a percentage of `rayLength`.
703
+ * Range: 0–100. 0 = uniform length.
704
+ */
167
705
  randomnessLength: number;
706
+ /**
707
+ * Per-ray angle jitter, as a percentage of the per-ray angular slot.
708
+ * Range: 0–100. Higher values break up the regular fan pattern.
709
+ */
168
710
  randomnessAngle: number;
711
+ /**
712
+ * Seed for the deterministic pseudo-random number generator (mulberry32).
713
+ * Two layers with the same parameters but different seeds will produce
714
+ * visually different ray distributions. Change this integer to get a new
715
+ * random layout without touching any other parameter.
716
+ */
169
717
  seed: number;
170
718
  }
171
719
 
720
+ /**
721
+ * Top-level description of everything that gets rendered onto a canvas.
722
+ *
723
+ * Layers are composited in array order, back-to-front. **`layers[0]` MUST be
724
+ * a `BackgroundLayer`** — it performs the `clearRect` equivalent each frame.
725
+ * Omitting it or placing it at any other index causes visual artifacts in
726
+ * animated mode (rays will smear across frames).
727
+ *
728
+ * @example
729
+ * const scene: SceneConfig = {
730
+ * width: 1920,
731
+ * height: 1080,
732
+ * noise: 8, // film-grain intensity 0–100
733
+ * grainSize: 1, // pixel size of grain
734
+ * layers: [
735
+ * // index 0: BackgroundLayer — REQUIRED, must come first
736
+ * { id: "background", type: "background", bgType: "solid", bgColor: "#000011", bgColor2: "#000011", bgGradientAngle: 180 },
737
+ * // index 1+: halos and rays in any order
738
+ * { id: "halo-1", name: "Glow", type: "halo", originX: 50, originY: 0, intensity: 0.5, size: 0.25, color: "#ffffff", blendMode: "lighter" },
739
+ * { ...DEFAULT_RAY_LAYER, id: "rays-1", name: "Rays" },
740
+ * ],
741
+ * };
742
+ */
172
743
  export declare interface SceneConfig {
744
+ /**
745
+ * Canvas width in pixels. Should match the `HTMLCanvasElement.width` you
746
+ * pass to `drawScene`. Typical: 1920.
747
+ */
173
748
  width: number;
749
+ /**
750
+ * Canvas height in pixels. Should match `HTMLCanvasElement.height`.
751
+ * Typical: 1080.
752
+ */
174
753
  height: number;
754
+ /**
755
+ * Film-grain intensity. 0 disables grain entirely; higher values add more
756
+ * visible noise. Range: 0–100. Typical: 4–15.
757
+ */
175
758
  noise: number;
759
+ /**
760
+ * Size of each grain "pixel" in screen pixels. 1 = per-pixel noise,
761
+ * 2+ = chunky grain. Range: 1–20. Typical: 1–3.
762
+ */
176
763
  grainSize: number;
177
- /** Ordered back-to-front. BackgroundLayer is always at index 0. */
764
+ /**
765
+ * Ordered back-to-front list of layers.
766
+ *
767
+ * **`layers[0]` MUST be a `BackgroundLayer`** — it is the only thing that
768
+ * clears the canvas before each frame. All subsequent entries can be
769
+ * `HaloLayer` or `RayLayer` in any order.
770
+ */
178
771
  layers: Layer[];
179
772
  }
180
773
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "godlights",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Animated god ray / light beam effects for React — zero dependencies beyond React itself.",
5
5
  "type": "module",
6
6
  "main": "./dist/godlights.js",