@dopaminefx/core 0.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.
Files changed (136) hide show
  1. package/dist/engine/color.d.ts +71 -0
  2. package/dist/engine/color.d.ts.map +1 -0
  3. package/dist/engine/color.js +107 -0
  4. package/dist/engine/color.js.map +1 -0
  5. package/dist/engine/context.d.ts +54 -0
  6. package/dist/engine/context.d.ts.map +1 -0
  7. package/dist/engine/context.js +0 -0
  8. package/dist/engine/context.js.map +1 -0
  9. package/dist/engine/gl.d.ts +9 -0
  10. package/dist/engine/gl.d.ts.map +1 -0
  11. package/dist/engine/gl.js +39 -0
  12. package/dist/engine/gl.js.map +1 -0
  13. package/dist/engine/look/glsl.d.ts +95 -0
  14. package/dist/engine/look/glsl.d.ts.map +1 -0
  15. package/dist/engine/look/glsl.js +171 -0
  16. package/dist/engine/look/glsl.js.map +1 -0
  17. package/dist/engine/look/particles.glsl.d.ts +21 -0
  18. package/dist/engine/look/particles.glsl.d.ts.map +1 -0
  19. package/dist/engine/look/particles.glsl.js +44 -0
  20. package/dist/engine/look/particles.glsl.js.map +1 -0
  21. package/dist/engine/sdf.d.ts +77 -0
  22. package/dist/engine/sdf.d.ts.map +1 -0
  23. package/dist/engine/sdf.js +255 -0
  24. package/dist/engine/sdf.js.map +1 -0
  25. package/dist/engine/seed.d.ts +10 -0
  26. package/dist/engine/seed.d.ts.map +1 -0
  27. package/dist/engine/seed.js +20 -0
  28. package/dist/engine/seed.js.map +1 -0
  29. package/dist/engine/shadow.d.ts +41 -0
  30. package/dist/engine/shadow.d.ts.map +1 -0
  31. package/dist/engine/shadow.js +39 -0
  32. package/dist/engine/shadow.js.map +1 -0
  33. package/dist/engine/tempo.d.ts +33 -0
  34. package/dist/engine/tempo.d.ts.map +1 -0
  35. package/dist/engine/tempo.js +51 -0
  36. package/dist/engine/tempo.js.map +1 -0
  37. package/dist/framework/conductor.d.ts +100 -0
  38. package/dist/framework/conductor.d.ts.map +1 -0
  39. package/dist/framework/conductor.js +493 -0
  40. package/dist/framework/conductor.js.map +1 -0
  41. package/dist/framework/content.d.ts +67 -0
  42. package/dist/framework/content.d.ts.map +1 -0
  43. package/dist/framework/content.js +72 -0
  44. package/dist/framework/content.js.map +1 -0
  45. package/dist/framework/dope-pass.d.ts +131 -0
  46. package/dist/framework/dope-pass.d.ts.map +1 -0
  47. package/dist/framework/dope-pass.js +346 -0
  48. package/dist/framework/dope-pass.js.map +1 -0
  49. package/dist/framework/dope-zip.d.ts +22 -0
  50. package/dist/framework/dope-zip.d.ts.map +1 -0
  51. package/dist/framework/dope-zip.js +116 -0
  52. package/dist/framework/dope-zip.js.map +1 -0
  53. package/dist/framework/effect.d.ts +128 -0
  54. package/dist/framework/effect.d.ts.map +1 -0
  55. package/dist/framework/effect.js +19 -0
  56. package/dist/framework/effect.js.map +1 -0
  57. package/dist/framework/frame-expr.d.ts +124 -0
  58. package/dist/framework/frame-expr.d.ts.map +1 -0
  59. package/dist/framework/frame-expr.js +135 -0
  60. package/dist/framework/frame-expr.js.map +1 -0
  61. package/dist/framework/load-effect.d.ts +77 -0
  62. package/dist/framework/load-effect.d.ts.map +1 -0
  63. package/dist/framework/load-effect.js +135 -0
  64. package/dist/framework/load-effect.js.map +1 -0
  65. package/dist/framework/loader.d.ts +309 -0
  66. package/dist/framework/loader.d.ts.map +1 -0
  67. package/dist/framework/loader.js +266 -0
  68. package/dist/framework/loader.js.map +1 -0
  69. package/dist/framework/mood-registry.d.ts +58 -0
  70. package/dist/framework/mood-registry.d.ts.map +1 -0
  71. package/dist/framework/mood-registry.js +58 -0
  72. package/dist/framework/mood-registry.js.map +1 -0
  73. package/dist/framework/panel-runner.d.ts +96 -0
  74. package/dist/framework/panel-runner.d.ts.map +1 -0
  75. package/dist/framework/panel-runner.js +137 -0
  76. package/dist/framework/panel-runner.js.map +1 -0
  77. package/dist/framework/pass-common.d.ts +97 -0
  78. package/dist/framework/pass-common.d.ts.map +1 -0
  79. package/dist/framework/pass-common.js +178 -0
  80. package/dist/framework/pass-common.js.map +1 -0
  81. package/dist/framework/pass-runner.d.ts +183 -0
  82. package/dist/framework/pass-runner.d.ts.map +1 -0
  83. package/dist/framework/pass-runner.js +212 -0
  84. package/dist/framework/pass-runner.js.map +1 -0
  85. package/dist/framework/programs.d.ts +54 -0
  86. package/dist/framework/programs.d.ts.map +1 -0
  87. package/dist/framework/programs.js +33 -0
  88. package/dist/framework/programs.js.map +1 -0
  89. package/dist/framework/registry.d.ts +29 -0
  90. package/dist/framework/registry.d.ts.map +1 -0
  91. package/dist/framework/registry.js +38 -0
  92. package/dist/framework/registry.js.map +1 -0
  93. package/dist/framework/runtime.d.ts +19 -0
  94. package/dist/framework/runtime.d.ts.map +1 -0
  95. package/dist/framework/runtime.js +37 -0
  96. package/dist/framework/runtime.js.map +1 -0
  97. package/dist/index.d.ts +63 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +126 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/overlay.d.ts +46 -0
  102. package/dist/overlay.d.ts.map +1 -0
  103. package/dist/overlay.js +79 -0
  104. package/dist/overlay.js.map +1 -0
  105. package/dist/types.d.ts +68 -0
  106. package/dist/types.d.ts.map +1 -0
  107. package/dist/types.js +10 -0
  108. package/dist/types.js.map +1 -0
  109. package/package.json +37 -0
  110. package/src/engine/color.ts +154 -0
  111. package/src/engine/context.ts +0 -0
  112. package/src/engine/gl.ts +46 -0
  113. package/src/engine/look/glsl.ts +183 -0
  114. package/src/engine/look/particles.glsl.ts +44 -0
  115. package/src/engine/sdf.ts +298 -0
  116. package/src/engine/seed.ts +23 -0
  117. package/src/engine/shadow.ts +66 -0
  118. package/src/engine/tempo.ts +54 -0
  119. package/src/framework/conductor.ts +604 -0
  120. package/src/framework/content.ts +113 -0
  121. package/src/framework/dope-pass.ts +432 -0
  122. package/src/framework/dope-zip.ts +125 -0
  123. package/src/framework/effect.ts +127 -0
  124. package/src/framework/frame-expr.ts +217 -0
  125. package/src/framework/load-effect.ts +204 -0
  126. package/src/framework/loader.ts +502 -0
  127. package/src/framework/mood-registry.ts +87 -0
  128. package/src/framework/panel-runner.ts +233 -0
  129. package/src/framework/pass-common.ts +222 -0
  130. package/src/framework/pass-runner.ts +391 -0
  131. package/src/framework/programs.ts +62 -0
  132. package/src/framework/registry.ts +44 -0
  133. package/src/framework/runtime.ts +38 -0
  134. package/src/index.ts +227 -0
  135. package/src/overlay.ts +109 -0
  136. package/src/types.ts +63 -0
package/src/index.ts ADDED
@@ -0,0 +1,227 @@
1
+ /**
2
+ * @dopaminefx/core — the EFFECT-FREE runtime + public API.
3
+ *
4
+ * This is the slim runtime: the conductor (overlay + shared program-cached GL
5
+ * contexts + RAF loop), the registry, the mood registry, the `.dope` loader +
6
+ * `loadEffect`, the generic runners (pass + panel), the shared engine bits
7
+ * (color, sdf, shadow, seed, context, gl, the `look/` GLSL chunks, tempo
8
+ * PRIMITIVES) and the generic `play(name, …)` / `prepare(name, …)` API.
9
+ *
10
+ * Core imports + registers NO effect. Each effect ships as its own
11
+ * `@dopaminefx/effect-<name>` package that depends on this and self-registers on
12
+ * import; the `@dopaminefx/effects` umbrella bundles all nine + the `celebrate*`
13
+ * conveniences + the `<dopamine-success>` element.
14
+ */
15
+
16
+ import {
17
+ play as conductorPlay,
18
+ prepare as conductorPrepare,
19
+ type CompositeMode,
20
+ type PlayHandle,
21
+ } from "./framework/conductor.js";
22
+ import { getEffect } from "./framework/registry.js";
23
+ import type { Anchor, FeelingInput } from "./framework/effect.js";
24
+ import { parseBackdrop } from "./engine/color.js";
25
+ import { randomSeed } from "./engine/seed.js";
26
+ import { isBrowser } from "./framework/runtime.js";
27
+ import type { DopamineSuccessOptions } from "./types.js";
28
+
29
+ export type { DopamineMood, DopamineSuccessOptions } from "./types.js";
30
+ export type { RGB, OKLCH } from "./engine/color.js";
31
+ export { buildPalette, oklchToLinearSrgb, wrapHue, GOLDEN_ANGLE_DEG, type PaletteParams } from "./engine/color.js";
32
+ export { mulberry32, randomSeed, type Rng } from "./engine/seed.js";
33
+
34
+ // Framework surface — for adding new effects / moods and lower-level control.
35
+ export type {
36
+ Effect,
37
+ EffectFactory,
38
+ EffectInstance,
39
+ EffectContext,
40
+ FeelingInput,
41
+ Anchor,
42
+ } from "./framework/effect.js";
43
+ export { registerEffect, getEffect, hasEffect, effectNames } from "./framework/registry.js";
44
+ export {
45
+ registerMood,
46
+ resolveMood,
47
+ hasMood,
48
+ moodNames,
49
+ type MoodSpec,
50
+ type ResolvedMood,
51
+ } from "./framework/mood-registry.js";
52
+ export { teardown, type PreparedHandle, type PlayHandle } from "./framework/conductor.js";
53
+ export {
54
+ loadEffect,
55
+ loadEffectSync,
56
+ type LoadEffectOptions,
57
+ type LoadOverrides,
58
+ type LoadedEffect,
59
+ } from "./framework/load-effect.js";
60
+ export { registerProgram, getProgram, programNames, type RenderProgram } from "./framework/programs.js";
61
+ export {
62
+ parseDope,
63
+ resolveDopeParams,
64
+ getOutline,
65
+ type DopeDoc,
66
+ type DopeOutline,
67
+ type DopeBinding,
68
+ type DopeSampler,
69
+ type DopeFrameSpec,
70
+ type DopeLoopSpec,
71
+ } from "./framework/loader.js";
72
+ export {
73
+ dopePassConfig,
74
+ dopePanelConfig,
75
+ registerDopeEffect,
76
+ registerDopePanelEffect,
77
+ type DopePassHooks,
78
+ type DopeShader,
79
+ type RegisterDopeEffectOptions,
80
+ } from "./framework/dope-pass.js";
81
+ export {
82
+ evalFrameExpr,
83
+ evalParamExpr,
84
+ evalPassExpr,
85
+ type FrameExprNode,
86
+ type FrameExprCtx,
87
+ type PassExprInputs,
88
+ } from "./framework/frame-expr.js";
89
+ export {
90
+ pickFromList,
91
+ pickBand,
92
+ resolveTypography,
93
+ type DopeTypography,
94
+ } from "./framework/content.js";
95
+ export { bakeSdf, decodeSdf, parseSvgPath, type BakedSdf, type DecodedSdf } from "./engine/sdf.js";
96
+ // The generic runners — for authoring new pure-shader / Canvas2D-panel effects.
97
+ export {
98
+ createPassInstance,
99
+ type PassConfig,
100
+ type PassParams,
101
+ type AuxTextureSpec,
102
+ type FrameInfo,
103
+ } from "./framework/pass-runner.js";
104
+ export {
105
+ createPanelInstance,
106
+ type PanelConfig,
107
+ type PanelDraw,
108
+ type PanelFrameInfo,
109
+ } from "./framework/panel-runner.js";
110
+
111
+ // Tempo PRIMITIVES — the generic easing/envelope building blocks effects build
112
+ // their bespoke timing on top of (each effect's bespoke envelope lives in its
113
+ // own package's `<name>-tempo.ts`).
114
+ export {
115
+ clamp01,
116
+ easeOutCubic,
117
+ easeOutBack,
118
+ envelope,
119
+ NPR_TIME_STEP_MS,
120
+ } from "./engine/tempo.js";
121
+
122
+ // The shared GLSL "look" chunks — reusable shader fragments (hash, fbm, palette
123
+ // mix, tonemap, dither, halftone, …) an effect's shader composes into its source.
124
+ export * from "./engine/look/glsl.js";
125
+ export { GLSL_PARTICLES } from "./engine/look/particles.glsl.js";
126
+
127
+ const DEFAULTS = { mood: "celebratory", intensity: 0.7, whimsy: 0.5 } as const;
128
+
129
+ /**
130
+ * Resolve the shared options into a target, a feeling, and an overlay-local
131
+ * anchor. Effects that aren't anchored (Verdict, Comic) simply ignore the anchor.
132
+ */
133
+ function resolveRequest(
134
+ effect: string,
135
+ options: DopamineSuccessOptions,
136
+ ): {
137
+ factory: ReturnType<typeof getEffect>;
138
+ target: HTMLElement;
139
+ anchor: Anchor;
140
+ targetSize: { width: number; height: number };
141
+ feeling: FeelingInput;
142
+ composite: CompositeMode | null;
143
+ } | null {
144
+ const factory = getEffect(effect);
145
+ if (!factory) throw new Error(`dopamine: unknown effect "${effect}"`);
146
+ const target = options.target ?? document.body;
147
+ const seed = options.seed ?? randomSeed();
148
+ const feeling: FeelingInput = {
149
+ mood: options.mood ?? DEFAULTS.mood,
150
+ intensity: options.intensity ?? DEFAULTS.intensity,
151
+ whimsy: options.whimsy ?? DEFAULTS.whimsy,
152
+ seed,
153
+ };
154
+ const rect = target.getBoundingClientRect();
155
+ const origin = options.origin ?? {
156
+ x: rect.left + rect.width / 2,
157
+ y: rect.top + rect.height / 2,
158
+ };
159
+ const anchor: Anchor =
160
+ target === document.body || target === document.documentElement
161
+ ? origin
162
+ : { x: origin.x - rect.left, y: origin.y - rect.top };
163
+ // The element box the centrepiece is sized to (CSS px). Defaults to the target's
164
+ // own rect, so the centrepiece matches whatever element was fired on; an explicit
165
+ // `targetSize` lets a caller match a child element under a full-page overlay.
166
+ const targetSize = options.targetSize ?? { width: rect.width, height: rect.height };
167
+ // A `backdrop` colour opts into surface-aware compositing (visible on light /
168
+ // arbitrary surfaces); we keep only its luminance — the colour itself isn't a
169
+ // shader input (the light layer composites source-over against the live page).
170
+ const bd = options.backdrop ? parseBackdrop(options.backdrop) : null;
171
+ const composite: CompositeMode | null = bd ? { luminance: bd.luminance } : null;
172
+ return { factory, target, anchor, targetSize, feeling, composite };
173
+ }
174
+
175
+ /**
176
+ * Generic real-time fire: play a registered effect by name. Resolves when the
177
+ * animation has fully played out. A CONTINUOUS effect (one whose `.dope`
178
+ * declares `tempo.loop`, e.g. halo) loops seamlessly until the host calls the
179
+ * returned handle's `stop()`. The handle's `pause()`/`resume()` freeze and
180
+ * resume the timeline drift-free (parking a perpetual loop so it costs no
181
+ * battery; the conductor also auto-pauses on a hidden tab). SSR-safe (resolves
182
+ * immediately off-DOM). The effect must already be registered (import
183
+ * `@dopaminefx/effect-<name>` or the `@dopaminefx/effects` umbrella, or load one
184
+ * via `loadEffect`).
185
+ */
186
+ export function play(effect: string, options: DopamineSuccessOptions = {}): PlayHandle {
187
+ const noop: PlayHandle = Object.assign(Promise.resolve(), { stop() {}, pause() {}, resume() {} });
188
+ if (!isBrowser()) return noop;
189
+ const req = resolveRequest(effect, options);
190
+ if (!req || !req.factory) return noop;
191
+ return conductorPlay({
192
+ factory: req.factory,
193
+ target: req.target,
194
+ anchor: req.anchor,
195
+ targetSize: req.targetSize,
196
+ feeling: req.feeling,
197
+ composite: req.composite,
198
+ });
199
+ }
200
+
201
+ /** A prepared, manually-driven effect handle (offline capture / external clock). */
202
+ export interface PreparedEffect {
203
+ readonly durationMs: number;
204
+ /** Draw the frame at `elapsedMs` since the start. */
205
+ renderAt(elapsedMs: number): void;
206
+ /** Dispose the renderer *and* release the overlay. */
207
+ dispose(): void;
208
+ }
209
+
210
+ /**
211
+ * Generic prepared effect: mount the overlay and return a renderer you drive
212
+ * yourself via `renderAt(elapsedMs)`. Call `dispose()` when finished. Returns
213
+ * `null` in non-DOM environments.
214
+ */
215
+ export function prepare(effect: string, options: DopamineSuccessOptions = {}): PreparedEffect | null {
216
+ if (!isBrowser()) return null;
217
+ const req = resolveRequest(effect, options);
218
+ if (!req || !req.factory) return null;
219
+ return conductorPrepare({
220
+ factory: req.factory,
221
+ target: req.target,
222
+ anchor: req.anchor,
223
+ targetSize: req.targetSize,
224
+ feeling: req.feeling,
225
+ composite: req.composite,
226
+ });
227
+ }
package/src/overlay.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Full-bleed overlay host. Creates fixed, click-through canvases layered over
3
+ * the target.
4
+ *
5
+ * Two stacked compositing layers give the effect real physical presence:
6
+ *
7
+ * - LIGHT layer (`mix-blend-mode: screen`): black pixels leave content
8
+ * untouched, bright pixels lighten it — this is what makes the effect cast
9
+ * coloured light onto the UI beneath.
10
+ * - SHADOW layer (`mix-blend-mode: multiply`): white pixels leave content
11
+ * untouched, dark pixels darken it — a soft, offset occlusion silhouette of
12
+ * the effect's bright forms, so the effect reads as floating ABOVE the page
13
+ * and throwing shadow into it, not just glowing on top of it.
14
+ *
15
+ * The shadow layer sits BENEATH the light layer in z-order, so the bright core
16
+ * always wins where the two overlap (the shadow is pushed out to the edges /
17
+ * away from the light, which is physically what an offset penumbra does).
18
+ *
19
+ * Back-compat: `createOverlay(target)` still returns an object whose `.canvas`
20
+ * is the single light canvas and `.destroy()` tears everything down — existing
21
+ * single-canvas callers are unaffected. Pass `{ shadow: true }` to additionally
22
+ * get a `shadow` canvas (`overlay.shadow`).
23
+ */
24
+
25
+ export interface Overlay {
26
+ /** The light-casting canvas (`mix-blend-mode: screen`). */
27
+ canvas: HTMLCanvasElement;
28
+ /**
29
+ * The shadow-casting canvas (`mix-blend-mode: multiply`), present only when
30
+ * the overlay was created with `{ shadow: true }`.
31
+ */
32
+ shadow?: HTMLCanvasElement;
33
+ /**
34
+ * Lazily create (or return the existing) shadow canvas, inserting it beneath
35
+ * the light layer. Lets a persistent overlay gain a shadow layer when a later
36
+ * effect needs one without recreating the whole overlay.
37
+ */
38
+ ensureShadow: () => HTMLCanvasElement;
39
+ /** Remove the overlay (all layers) from the DOM. */
40
+ destroy: () => void;
41
+ }
42
+
43
+ export interface OverlayOptions {
44
+ /** Also create a multiply "shadow" layer beneath the light layer. */
45
+ shadow?: boolean;
46
+ }
47
+
48
+ const LIGHT_Z = "2147483646";
49
+ // One below the light layer so the bright core composites over the shadow.
50
+ const SHADOW_Z = "2147483645";
51
+
52
+ function styleCanvas(
53
+ canvas: HTMLCanvasElement,
54
+ blend: "screen" | "multiply",
55
+ zIndex: string,
56
+ scoped: boolean,
57
+ ): void {
58
+ const s = canvas.style;
59
+ s.position = scoped ? "absolute" : "fixed";
60
+ s.inset = "0";
61
+ s.width = "100%";
62
+ s.height = "100%";
63
+ s.pointerEvents = "none";
64
+ s.zIndex = zIndex;
65
+ s.mixBlendMode = blend;
66
+ s.display = "block";
67
+ canvas.setAttribute("aria-hidden", "true");
68
+ }
69
+
70
+ export function createOverlay(target: HTMLElement, options: OverlayOptions = {}): Overlay {
71
+ const scoped = target !== document.body && target !== document.documentElement;
72
+ if (scoped) {
73
+ const cs = getComputedStyle(target);
74
+ if (cs.position === "static") target.style.position = "relative";
75
+ }
76
+
77
+ // Shadow layer is created (and appended) first so it sits beneath the light
78
+ // layer both in z-index and DOM order.
79
+ let shadow: HTMLCanvasElement | undefined;
80
+ const makeShadow = (): HTMLCanvasElement => {
81
+ const s = document.createElement("canvas");
82
+ styleCanvas(s, "multiply", SHADOW_Z, scoped);
83
+ s.dataset.dopamine = "shadow";
84
+ // Insert at the front so it sits beneath the (later-appended) light canvas.
85
+ target.insertBefore(s, target.firstChild);
86
+ return s;
87
+ };
88
+ if (options.shadow) shadow = makeShadow();
89
+
90
+ const canvas = document.createElement("canvas");
91
+ styleCanvas(canvas, "screen", LIGHT_Z, scoped);
92
+ canvas.dataset.dopamine = "solarbloom";
93
+ target.appendChild(canvas);
94
+
95
+ return {
96
+ canvas,
97
+ get shadow() {
98
+ return shadow;
99
+ },
100
+ ensureShadow(): HTMLCanvasElement {
101
+ if (!shadow) shadow = makeShadow();
102
+ return shadow;
103
+ },
104
+ destroy: () => {
105
+ canvas.remove();
106
+ shadow?.remove();
107
+ },
108
+ };
109
+ }
package/src/types.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Public types for Dopamine's success effect.
3
+ *
4
+ * The whole point of the API is that callers choose a *feeling* — a mood, how
5
+ * intense it should be, and how much whimsy — rather than tuning low-level
6
+ * particle counts and easing curves. Those get derived internally (see
7
+ * `engine/mood.ts`).
8
+ */
9
+
10
+ /** Emotional register of the celebration. */
11
+ export type DopamineMood = "serene" | "celebratory" | "electric";
12
+
13
+ export interface DopamineSuccessOptions {
14
+ /**
15
+ * Emotional register. Default `"celebratory"`. A built-in success mood, or any
16
+ * mood registered via `registerMood` (e.g. the fail effect's `try-again` /
17
+ * `error` / `denied`).
18
+ */
19
+ mood?: DopamineMood | (string & {});
20
+ /**
21
+ * How strong the reward feels, 0..1. Drives saturation, brightness, bloom
22
+ * size, mote count and overshoot — grounded in the finding that saturated,
23
+ * bright color raises both arousal and positive valence. Default `0.7`.
24
+ */
25
+ intensity?: number;
26
+ /**
27
+ * How playful/organic the motion is, 0..1. Widens the hue spread and the
28
+ * turbulence of the drifting motes. Default `0.5`.
29
+ */
30
+ whimsy?: number;
31
+ /**
32
+ * Seed for the algorithmic color + motion. Omit to get a fresh, unique
33
+ * palette every fire (the variable-reward / novelty lever). Provide a fixed
34
+ * value for reproducible output (e.g. tests, snapshots).
35
+ */
36
+ seed?: number;
37
+ /** Origin of the bloom in viewport pixels. Default: center of `target`. */
38
+ origin?: { x: number; y: number };
39
+ /**
40
+ * Size (CSS px) of the element the effect's centrepiece (checkmark, ✗, comic
41
+ * word, hero heart, ink gesture) is sized to. Default: the `target`'s own box.
42
+ * Set this to match a CHILD element while the overlay still covers the page.
43
+ */
44
+ targetSize?: { width: number; height: number };
45
+ /**
46
+ * Element the full-bleed overlay is mounted over. Default `document.body`,
47
+ * i.e. the whole page. Light is cast (via `mix-blend-mode`) onto whatever
48
+ * sits beneath the overlay.
49
+ */
50
+ target?: HTMLElement;
51
+ /**
52
+ * The page colour the effect composites against, as any CSS colour string
53
+ * (e.g. `"#ffffff"`, `"rgb(20 24 37)"`, a named colour). Omit (the default)
54
+ * for the classic dark compositing: the light layer uses
55
+ * `mix-blend-mode: screen`, which is rich on a dark UI but mathematically
56
+ * invisible on white. Pass the actual surface colour and the runtime switches
57
+ * the light layer to PREMULTIPLIED source-over light — visible on ANY surface,
58
+ * white included — and strengthens the multiply shadow as the surface
59
+ * lightens. Use this whenever the effect plays over a light or unknown-colour
60
+ * background.
61
+ */
62
+ backdrop?: string;
63
+ }