@ansh-ui/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/behaviours.cjs +64 -0
- package/dist/behaviours.cjs.map +1 -0
- package/dist/behaviours.d.cts +3 -0
- package/dist/behaviours.d.ts +3 -0
- package/dist/behaviours.js +7 -0
- package/dist/behaviours.js.map +1 -0
- package/dist/chunk-23GSSIMK.cjs +439 -0
- package/dist/chunk-23GSSIMK.cjs.map +1 -0
- package/dist/chunk-56NNBIAO.js +2446 -0
- package/dist/chunk-56NNBIAO.js.map +1 -0
- package/dist/chunk-6PFYGJWD.cjs +136 -0
- package/dist/chunk-6PFYGJWD.cjs.map +1 -0
- package/dist/chunk-7MEIXWPH.js +313 -0
- package/dist/chunk-7MEIXWPH.js.map +1 -0
- package/dist/chunk-7MYTSAPR.js +83 -0
- package/dist/chunk-7MYTSAPR.js.map +1 -0
- package/dist/chunk-BOQQHICI.js +1036 -0
- package/dist/chunk-BOQQHICI.js.map +1 -0
- package/dist/chunk-C6XPQO2O.js +1170 -0
- package/dist/chunk-C6XPQO2O.js.map +1 -0
- package/dist/chunk-DXJ63QAR.cjs +723 -0
- package/dist/chunk-DXJ63QAR.cjs.map +1 -0
- package/dist/chunk-FAVIEION.js +705 -0
- package/dist/chunk-FAVIEION.js.map +1 -0
- package/dist/chunk-FNR35HTL.cjs +107 -0
- package/dist/chunk-FNR35HTL.cjs.map +1 -0
- package/dist/chunk-GHBRGMSO.cjs +91 -0
- package/dist/chunk-GHBRGMSO.cjs.map +1 -0
- package/dist/chunk-GJRIHN76.cjs +359 -0
- package/dist/chunk-GJRIHN76.cjs.map +1 -0
- package/dist/chunk-ILKUL7GD.cjs +115 -0
- package/dist/chunk-ILKUL7GD.cjs.map +1 -0
- package/dist/chunk-KTUBCQC3.js +409 -0
- package/dist/chunk-KTUBCQC3.js.map +1 -0
- package/dist/chunk-KU2C6GEA.js +90 -0
- package/dist/chunk-KU2C6GEA.js.map +1 -0
- package/dist/chunk-LGA3N3YJ.cjs +413 -0
- package/dist/chunk-LGA3N3YJ.cjs.map +1 -0
- package/dist/chunk-MMO7KO53.cjs +1060 -0
- package/dist/chunk-MMO7KO53.cjs.map +1 -0
- package/dist/chunk-NSXKCJJ7.js +109 -0
- package/dist/chunk-NSXKCJJ7.js.map +1 -0
- package/dist/chunk-QALZKHWH.js +387 -0
- package/dist/chunk-QALZKHWH.js.map +1 -0
- package/dist/chunk-R7UJ7MWP.cjs +2479 -0
- package/dist/chunk-R7UJ7MWP.cjs.map +1 -0
- package/dist/chunk-UJBXY6QC.cjs +1186 -0
- package/dist/chunk-UJBXY6QC.cjs.map +1 -0
- package/dist/chunk-YFQXRC5R.js +99 -0
- package/dist/chunk-YFQXRC5R.js.map +1 -0
- package/dist/color.cjs +257 -0
- package/dist/color.cjs.map +1 -0
- package/dist/color.d.cts +303 -0
- package/dist/color.d.ts +303 -0
- package/dist/color.js +8 -0
- package/dist/color.js.map +1 -0
- package/dist/easing.cjs +189 -0
- package/dist/easing.cjs.map +1 -0
- package/dist/easing.d.cts +134 -0
- package/dist/easing.d.ts +134 -0
- package/dist/easing.js +4 -0
- package/dist/easing.js.map +1 -0
- package/dist/effects.cjs +99 -0
- package/dist/effects.cjs.map +1 -0
- package/dist/effects.d.cts +5 -0
- package/dist/effects.d.ts +5 -0
- package/dist/effects.js +10 -0
- package/dist/effects.js.map +1 -0
- package/dist/index-CCdpefMp.d.ts +967 -0
- package/dist/index-CQEEPu-D.d.ts +435 -0
- package/dist/index-DHOfsEIn.d.cts +967 -0
- package/dist/index-Dl8GCsya.d.cts +435 -0
- package/dist/index.cjs +2206 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1122 -0
- package/dist/index.d.ts +1122 -0
- package/dist/index.js +1182 -0
- package/dist/index.js.map +1 -0
- package/dist/math.cjs +238 -0
- package/dist/math.cjs.map +1 -0
- package/dist/math.d.cts +120 -0
- package/dist/math.d.ts +120 -0
- package/dist/math.js +5 -0
- package/dist/math.js.map +1 -0
- package/dist/noise.cjs +78 -0
- package/dist/noise.cjs.map +1 -0
- package/dist/noise.d.cts +190 -0
- package/dist/noise.d.ts +190 -0
- package/dist/noise.js +5 -0
- package/dist/noise.js.map +1 -0
- package/dist/palette-6HkVltYU.d.cts +219 -0
- package/dist/palette-6HkVltYU.d.ts +219 -0
- package/dist/random--4mSc5Lp.d.cts +75 -0
- package/dist/random--4mSc5Lp.d.ts +75 -0
- package/dist/reduced-motion-D0LUgZle.d.cts +120 -0
- package/dist/reduced-motion-D0LUgZle.d.ts +120 -0
- package/dist/types-B1FLLf3-.d.cts +21 -0
- package/dist/types-B1FLLf3-.d.ts +21 -0
- package/package.json +78 -0
|
@@ -0,0 +1,2479 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk23GSSIMK_cjs = require('./chunk-23GSSIMK.cjs');
|
|
4
|
+
var chunkDXJ63QAR_cjs = require('./chunk-DXJ63QAR.cjs');
|
|
5
|
+
var chunkFNR35HTL_cjs = require('./chunk-FNR35HTL.cjs');
|
|
6
|
+
var chunkMMO7KO53_cjs = require('./chunk-MMO7KO53.cjs');
|
|
7
|
+
var chunkLGA3N3YJ_cjs = require('./chunk-LGA3N3YJ.cjs');
|
|
8
|
+
var chunkILKUL7GD_cjs = require('./chunk-ILKUL7GD.cjs');
|
|
9
|
+
|
|
10
|
+
// src/render/surface.ts
|
|
11
|
+
function createSurface(canvas, options = {}) {
|
|
12
|
+
const maxDpr = Math.max(1, options.maxDevicePixelRatio ?? 2);
|
|
13
|
+
let resolution = chunkILKUL7GD_cjs.clamp(options.resolution ?? 1, 0.1, 1);
|
|
14
|
+
const size = {
|
|
15
|
+
width: 1,
|
|
16
|
+
height: 1,
|
|
17
|
+
pixelWidth: 1,
|
|
18
|
+
pixelHeight: 1,
|
|
19
|
+
scale: 1
|
|
20
|
+
};
|
|
21
|
+
let ctx = null;
|
|
22
|
+
let isLost = false;
|
|
23
|
+
let pendingResize = true;
|
|
24
|
+
let rafHandle = null;
|
|
25
|
+
let destroyed = false;
|
|
26
|
+
const acquireContext = () => {
|
|
27
|
+
ctx = canvas.getContext("2d", {
|
|
28
|
+
alpha: options.alpha ?? true,
|
|
29
|
+
willReadFrequently: options.willReadFrequently ?? false
|
|
30
|
+
});
|
|
31
|
+
if (!ctx) {
|
|
32
|
+
console.error(
|
|
33
|
+
"[ansh] Could not acquire a 2D canvas context. The effect will not render. This usually means too many live canvases on the page."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
acquireContext();
|
|
38
|
+
const measure = () => {
|
|
39
|
+
const host = canvas.parentElement;
|
|
40
|
+
const rect = host ? host.getBoundingClientRect() : canvas.getBoundingClientRect();
|
|
41
|
+
const cssWidth = Math.max(1, Math.round(rect.width));
|
|
42
|
+
const cssHeight = Math.max(1, Math.round(rect.height));
|
|
43
|
+
const dpr = Math.min(
|
|
44
|
+
maxDpr,
|
|
45
|
+
typeof devicePixelRatio === "number" && devicePixelRatio > 0 ? devicePixelRatio : 1
|
|
46
|
+
);
|
|
47
|
+
const scale = dpr * resolution;
|
|
48
|
+
const pixelWidth = Math.max(1, Math.round(cssWidth * scale));
|
|
49
|
+
const pixelHeight = Math.max(1, Math.round(cssHeight * scale));
|
|
50
|
+
const changed = size.width !== cssWidth || size.height !== cssHeight || size.pixelWidth !== pixelWidth || size.pixelHeight !== pixelHeight;
|
|
51
|
+
size.width = cssWidth;
|
|
52
|
+
size.height = cssHeight;
|
|
53
|
+
size.pixelWidth = pixelWidth;
|
|
54
|
+
size.pixelHeight = pixelHeight;
|
|
55
|
+
size.scale = scale;
|
|
56
|
+
if (!changed) return;
|
|
57
|
+
canvas.width = pixelWidth;
|
|
58
|
+
canvas.height = pixelHeight;
|
|
59
|
+
canvas.style.width = `${cssWidth}px`;
|
|
60
|
+
canvas.style.height = `${cssHeight}px`;
|
|
61
|
+
if (ctx) ctx.setTransform(scale, 0, 0, scale, 0, 0);
|
|
62
|
+
options.onResize?.({ ...size });
|
|
63
|
+
};
|
|
64
|
+
const scheduleResize = () => {
|
|
65
|
+
pendingResize = true;
|
|
66
|
+
if (rafHandle !== null || destroyed) return;
|
|
67
|
+
rafHandle = requestAnimationFrame(() => {
|
|
68
|
+
rafHandle = null;
|
|
69
|
+
if (destroyed) return;
|
|
70
|
+
if (pendingResize) {
|
|
71
|
+
pendingResize = false;
|
|
72
|
+
measure();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const stopResizeObserver = canvas.parentElement ? chunkMMO7KO53_cjs.observeResize(canvas.parentElement, scheduleResize) : () => {
|
|
77
|
+
};
|
|
78
|
+
const onContextLost = (event) => {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
isLost = true;
|
|
81
|
+
};
|
|
82
|
+
const onContextRestored = () => {
|
|
83
|
+
isLost = false;
|
|
84
|
+
acquireContext();
|
|
85
|
+
measure();
|
|
86
|
+
if (ctx) ctx.setTransform(size.scale, 0, 0, size.scale, 0, 0);
|
|
87
|
+
options.onRestore?.();
|
|
88
|
+
};
|
|
89
|
+
canvas.addEventListener("contextlost", onContextLost);
|
|
90
|
+
canvas.addEventListener("contextrestored", onContextRestored);
|
|
91
|
+
measure();
|
|
92
|
+
return {
|
|
93
|
+
canvas,
|
|
94
|
+
get ctx() {
|
|
95
|
+
return ctx;
|
|
96
|
+
},
|
|
97
|
+
get size() {
|
|
98
|
+
return size;
|
|
99
|
+
},
|
|
100
|
+
get isLost() {
|
|
101
|
+
return isLost;
|
|
102
|
+
},
|
|
103
|
+
clear() {
|
|
104
|
+
if (!ctx || isLost) return;
|
|
105
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
106
|
+
ctx.clearRect(0, 0, size.pixelWidth, size.pixelHeight);
|
|
107
|
+
ctx.setTransform(size.scale, 0, 0, size.scale, 0, 0);
|
|
108
|
+
},
|
|
109
|
+
setResolution(next) {
|
|
110
|
+
const clamped = chunkILKUL7GD_cjs.clamp(next, 0.1, 1);
|
|
111
|
+
if (Math.abs(clamped - resolution) < 0.02) return;
|
|
112
|
+
resolution = clamped;
|
|
113
|
+
scheduleResize();
|
|
114
|
+
},
|
|
115
|
+
resize: scheduleResize,
|
|
116
|
+
destroy() {
|
|
117
|
+
destroyed = true;
|
|
118
|
+
if (rafHandle !== null) cancelAnimationFrame(rafHandle);
|
|
119
|
+
stopResizeObserver();
|
|
120
|
+
canvas.removeEventListener("contextlost", onContextLost);
|
|
121
|
+
canvas.removeEventListener("contextrestored", onContextRestored);
|
|
122
|
+
canvas.width = 1;
|
|
123
|
+
canvas.height = 1;
|
|
124
|
+
ctx = null;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function supportsOffscreenCanvas() {
|
|
129
|
+
if (typeof OffscreenCanvas === "undefined") return false;
|
|
130
|
+
try {
|
|
131
|
+
const probe = new OffscreenCanvas(1, 1);
|
|
132
|
+
return probe.getContext("2d") !== null;
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function supportsWorkerRendering(canvas) {
|
|
138
|
+
return supportsOffscreenCanvas() && typeof canvas.transferControlToOffscreen === "function";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/render/draw.ts
|
|
142
|
+
var GradientCache = class {
|
|
143
|
+
constructor(limit = 128) {
|
|
144
|
+
this.limit = limit;
|
|
145
|
+
}
|
|
146
|
+
limit;
|
|
147
|
+
cache = /* @__PURE__ */ new Map();
|
|
148
|
+
radial(ctx, radius, stops, key) {
|
|
149
|
+
const cached = this.cache.get(key);
|
|
150
|
+
if (cached) return cached;
|
|
151
|
+
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, Math.max(0.01, radius));
|
|
152
|
+
for (const [offset, color] of stops) gradient.addColorStop(offset, color);
|
|
153
|
+
if (this.cache.size >= this.limit) this.cache.clear();
|
|
154
|
+
this.cache.set(key, gradient);
|
|
155
|
+
return gradient;
|
|
156
|
+
}
|
|
157
|
+
clear() {
|
|
158
|
+
this.cache.clear();
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
function drawGlow(ctx, x, y, radius, color, intensity = 1, cache) {
|
|
162
|
+
if (radius <= 0 || intensity <= 0) return;
|
|
163
|
+
const rgb = `${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)}`;
|
|
164
|
+
const alpha = color.a * intensity;
|
|
165
|
+
const stops = [
|
|
166
|
+
[0, `rgba(${rgb},${alpha})`],
|
|
167
|
+
[0.35, `rgba(${rgb},${alpha * 0.55})`],
|
|
168
|
+
[0.65, `rgba(${rgb},${alpha * 0.18})`],
|
|
169
|
+
[1, `rgba(${rgb},0)`]
|
|
170
|
+
];
|
|
171
|
+
ctx.save();
|
|
172
|
+
ctx.translate(x, y);
|
|
173
|
+
const gradient = cache ? cache.radial(ctx, radius, stops, `${rgb}|${alpha.toFixed(3)}|${radius.toFixed(1)}`) : buildRadial(ctx, radius, stops);
|
|
174
|
+
ctx.fillStyle = gradient;
|
|
175
|
+
ctx.beginPath();
|
|
176
|
+
ctx.arc(0, 0, radius, 0, chunkILKUL7GD_cjs.TAU);
|
|
177
|
+
ctx.fill();
|
|
178
|
+
ctx.restore();
|
|
179
|
+
}
|
|
180
|
+
function buildRadial(ctx, radius, stops) {
|
|
181
|
+
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, Math.max(0.01, radius));
|
|
182
|
+
for (const [offset, color] of stops) gradient.addColorStop(offset, color);
|
|
183
|
+
return gradient;
|
|
184
|
+
}
|
|
185
|
+
function drawDot(ctx, x, y, radius, color) {
|
|
186
|
+
if (radius <= 0) return;
|
|
187
|
+
ctx.fillStyle = chunk23GSSIMK_cjs.toRgbString(color);
|
|
188
|
+
ctx.beginPath();
|
|
189
|
+
ctx.arc(x, y, radius, 0, chunkILKUL7GD_cjs.TAU);
|
|
190
|
+
ctx.fill();
|
|
191
|
+
}
|
|
192
|
+
function traceClosedSpline(ctx, points, tension = 0.5) {
|
|
193
|
+
const n = points.length;
|
|
194
|
+
if (n < 3) {
|
|
195
|
+
if (n === 2) {
|
|
196
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
197
|
+
ctx.lineTo(points[1].x, points[1].y);
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const at = (i) => points[(i % n + n) % n];
|
|
202
|
+
const k = tension / 3;
|
|
203
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
204
|
+
for (let i = 0; i < n; i++) {
|
|
205
|
+
const p0 = at(i - 1);
|
|
206
|
+
const p1 = at(i);
|
|
207
|
+
const p2 = at(i + 1);
|
|
208
|
+
const p3 = at(i + 2);
|
|
209
|
+
ctx.bezierCurveTo(
|
|
210
|
+
p1.x + (p2.x - p0.x) * k,
|
|
211
|
+
p1.y + (p2.y - p0.y) * k,
|
|
212
|
+
p2.x - (p3.x - p1.x) * k,
|
|
213
|
+
p2.y - (p3.y - p1.y) * k,
|
|
214
|
+
p2.x,
|
|
215
|
+
p2.y
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
ctx.closePath();
|
|
219
|
+
}
|
|
220
|
+
function traceOpenSpline(ctx, points, tension = 0.5) {
|
|
221
|
+
const n = points.length;
|
|
222
|
+
if (n < 2) return;
|
|
223
|
+
if (n === 2) {
|
|
224
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
225
|
+
ctx.lineTo(points[1].x, points[1].y);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const k = tension / 3;
|
|
229
|
+
const at = (i) => points[Math.max(0, Math.min(n - 1, i))];
|
|
230
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
231
|
+
for (let i = 0; i < n - 1; i++) {
|
|
232
|
+
const p0 = at(i - 1);
|
|
233
|
+
const p1 = at(i);
|
|
234
|
+
const p2 = at(i + 1);
|
|
235
|
+
const p3 = at(i + 2);
|
|
236
|
+
ctx.bezierCurveTo(
|
|
237
|
+
p1.x + (p2.x - p0.x) * k,
|
|
238
|
+
p1.y + (p2.y - p0.y) * k,
|
|
239
|
+
p2.x - (p3.x - p1.x) * k,
|
|
240
|
+
p2.y - (p3.y - p1.y) * k,
|
|
241
|
+
p2.x,
|
|
242
|
+
p2.y
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function traceRoundRect(ctx, x, y, width, height, radius) {
|
|
247
|
+
const r = Math.min(radius, width / 2, height / 2);
|
|
248
|
+
if (typeof ctx.roundRect === "function") {
|
|
249
|
+
ctx.roundRect(x, y, width, height, r);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
ctx.moveTo(x + r, y);
|
|
253
|
+
ctx.lineTo(x + width - r, y);
|
|
254
|
+
ctx.arcTo(x + width, y, x + width, y + r, r);
|
|
255
|
+
ctx.lineTo(x + width, y + height - r);
|
|
256
|
+
ctx.arcTo(x + width, y + height, x + width - r, y + height, r);
|
|
257
|
+
ctx.lineTo(x + r, y + height);
|
|
258
|
+
ctx.arcTo(x, y + height, x, y + height - r, r);
|
|
259
|
+
ctx.lineTo(x, y + r);
|
|
260
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
261
|
+
ctx.closePath();
|
|
262
|
+
}
|
|
263
|
+
var filterSupport = null;
|
|
264
|
+
function supportsCanvasFilter(ctx) {
|
|
265
|
+
if (filterSupport !== null) return filterSupport;
|
|
266
|
+
const previous = ctx.filter;
|
|
267
|
+
try {
|
|
268
|
+
ctx.filter = "blur(1px)";
|
|
269
|
+
filterSupport = ctx.filter === "blur(1px)";
|
|
270
|
+
} catch {
|
|
271
|
+
filterSupport = false;
|
|
272
|
+
} finally {
|
|
273
|
+
ctx.filter = previous;
|
|
274
|
+
}
|
|
275
|
+
return filterSupport;
|
|
276
|
+
}
|
|
277
|
+
function resetFilterSupport() {
|
|
278
|
+
filterSupport = null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/effects/mount.ts
|
|
282
|
+
var CANVAS_STYLE = {
|
|
283
|
+
position: "absolute",
|
|
284
|
+
inset: "0",
|
|
285
|
+
width: "100%",
|
|
286
|
+
height: "100%",
|
|
287
|
+
display: "block",
|
|
288
|
+
// A background canvas must never intercept clicks meant for the content above it.
|
|
289
|
+
pointerEvents: "none"
|
|
290
|
+
};
|
|
291
|
+
function mountEffect(definition, host, userOptions = {}) {
|
|
292
|
+
chunkMMO7KO53_cjs.getRegistry().requireCapability(definition.capability ?? "effect:core");
|
|
293
|
+
let options = { ...definition.defaults, ...stripUndefined(userOptions) };
|
|
294
|
+
const canvas = document.createElement("canvas");
|
|
295
|
+
canvas.setAttribute("aria-hidden", "true");
|
|
296
|
+
canvas.setAttribute("role", "presentation");
|
|
297
|
+
Object.assign(canvas.style, CANVAS_STYLE);
|
|
298
|
+
if (getComputedStyle(host).position === "static") host.style.position = "relative";
|
|
299
|
+
host.appendChild(canvas);
|
|
300
|
+
let instance = null;
|
|
301
|
+
let context = null;
|
|
302
|
+
let pointer = null;
|
|
303
|
+
let tickerSubscription = null;
|
|
304
|
+
let intersectionSubscription = null;
|
|
305
|
+
let reducedMotionSubscription = null;
|
|
306
|
+
let destroyed = false;
|
|
307
|
+
let userPaused = options.paused ?? false;
|
|
308
|
+
let offscreen = false;
|
|
309
|
+
let motion = chunkMMO7KO53_cjs.resolveMotion(options.reducedMotion, 0.25);
|
|
310
|
+
let pendingSingleFrame = false;
|
|
311
|
+
const ticker = chunkMMO7KO53_cjs.getTicker();
|
|
312
|
+
const surface = createSurface(canvas, {
|
|
313
|
+
resolution: options.resolution ?? 1,
|
|
314
|
+
alpha: true,
|
|
315
|
+
onResize: (size) => {
|
|
316
|
+
instance?.resize?.(size);
|
|
317
|
+
pendingSingleFrame = true;
|
|
318
|
+
},
|
|
319
|
+
onRestore: () => {
|
|
320
|
+
rebuild();
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
function buildContext() {
|
|
324
|
+
const ctx = surface.ctx;
|
|
325
|
+
if (!ctx) return null;
|
|
326
|
+
const seed = options.seed ?? 1;
|
|
327
|
+
const colors = resolveResolvedColors(options.colors, host);
|
|
328
|
+
return {
|
|
329
|
+
ctx,
|
|
330
|
+
surface,
|
|
331
|
+
size: surface.size,
|
|
332
|
+
rng: chunkFNR35HTL_cjs.createRng(seed),
|
|
333
|
+
noise: chunkDXJ63QAR_cjs.createNoise(seed),
|
|
334
|
+
pointer: pointer ? pointer.state : null,
|
|
335
|
+
host,
|
|
336
|
+
colors
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function rebuild() {
|
|
340
|
+
instance?.destroy?.();
|
|
341
|
+
instance = null;
|
|
342
|
+
context = buildContext();
|
|
343
|
+
if (!context) return;
|
|
344
|
+
instance = definition.setup(context, options);
|
|
345
|
+
instance.resize?.(surface.size);
|
|
346
|
+
pendingSingleFrame = true;
|
|
347
|
+
}
|
|
348
|
+
function ensurePointer() {
|
|
349
|
+
const wants = options.interactive !== false;
|
|
350
|
+
if (wants && !pointer) {
|
|
351
|
+
pointer = chunkMMO7KO53_cjs.trackPointer(host);
|
|
352
|
+
} else if (!wants && pointer) {
|
|
353
|
+
pointer.destroy();
|
|
354
|
+
pointer = null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
ensurePointer();
|
|
358
|
+
rebuild();
|
|
359
|
+
const applyQuality = (quality) => {
|
|
360
|
+
if (options.performance === "high" || options.performance === "low") return;
|
|
361
|
+
const base = options.resolution ?? 1;
|
|
362
|
+
surface.setResolution(base * chunkILKUL7GD_cjs.clamp(0.5 + quality * 0.5, 0.5, 1));
|
|
363
|
+
};
|
|
364
|
+
const frame = (state) => {
|
|
365
|
+
if (destroyed || surface.isLost || !instance || !context) return;
|
|
366
|
+
if ((userPaused || offscreen || !motion.animate) && !pendingSingleFrame) return;
|
|
367
|
+
pendingSingleFrame = false;
|
|
368
|
+
const ctx = surface.ctx;
|
|
369
|
+
if (!ctx) return;
|
|
370
|
+
applyQuality(state.quality);
|
|
371
|
+
ctx.save();
|
|
372
|
+
const opacity = chunkILKUL7GD_cjs.clamp(options.opacity ?? 1, 0, 1);
|
|
373
|
+
if (opacity < 1) ctx.globalAlpha = opacity;
|
|
374
|
+
const blur = options.blur ?? 0;
|
|
375
|
+
if (blur > 0 && supportsCanvasFilter(ctx)) ctx.filter = `blur(${blur}px)`;
|
|
376
|
+
const speed = (options.speed ?? 1) * motion.timeScale;
|
|
377
|
+
instance.frame({
|
|
378
|
+
...state,
|
|
379
|
+
time: state.time * speed,
|
|
380
|
+
delta: state.delta * speed
|
|
381
|
+
});
|
|
382
|
+
ctx.restore();
|
|
383
|
+
};
|
|
384
|
+
function startTicker() {
|
|
385
|
+
if (tickerSubscription || destroyed) return;
|
|
386
|
+
tickerSubscription = ticker.add(frame, { phase: "write", name: `effect:${definition.name}` });
|
|
387
|
+
}
|
|
388
|
+
function stopTicker() {
|
|
389
|
+
tickerSubscription?.();
|
|
390
|
+
tickerSubscription = null;
|
|
391
|
+
}
|
|
392
|
+
if (options.performance === "high") ticker.lockQuality(1);
|
|
393
|
+
else if (options.performance === "low") ticker.lockQuality(0.4);
|
|
394
|
+
if (options.pauseOffscreen !== false) {
|
|
395
|
+
intersectionSubscription = chunkMMO7KO53_cjs.observeIntersection(
|
|
396
|
+
host,
|
|
397
|
+
(entry) => {
|
|
398
|
+
offscreen = !entry.isIntersecting;
|
|
399
|
+
if (offscreen) stopTicker();
|
|
400
|
+
else if (!userPaused && motion.animate) startTicker();
|
|
401
|
+
},
|
|
402
|
+
// A generous margin means the effect is already warm by the time it scrolls in, rather
|
|
403
|
+
// than starting from its first frame in view.
|
|
404
|
+
{ rootMargin: "200px" }
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
reducedMotionSubscription = chunkMMO7KO53_cjs.onReducedMotionChange(() => {
|
|
408
|
+
motion = chunkMMO7KO53_cjs.resolveMotion(options.reducedMotion, 0.25);
|
|
409
|
+
if (motion.animate && !userPaused && !offscreen) {
|
|
410
|
+
startTicker();
|
|
411
|
+
} else {
|
|
412
|
+
stopTicker();
|
|
413
|
+
if (motion.renderOnce) renderSingleFrame();
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
function renderSingleFrame() {
|
|
417
|
+
pendingSingleFrame = true;
|
|
418
|
+
ticker.once((state) => frame(state));
|
|
419
|
+
}
|
|
420
|
+
if (motion.animate && !userPaused) startTicker();
|
|
421
|
+
else if (motion.renderOnce) renderSingleFrame();
|
|
422
|
+
return {
|
|
423
|
+
canvas,
|
|
424
|
+
update(next) {
|
|
425
|
+
if (destroyed) return;
|
|
426
|
+
const incoming = stripUndefined(next);
|
|
427
|
+
const structural = definition.structuralOptions ?? [];
|
|
428
|
+
const needsRebuild = (
|
|
429
|
+
// A changed seed changes every stochastic decision; nothing can be salvaged.
|
|
430
|
+
"seed" in incoming && incoming.seed !== options.seed || structural.some(
|
|
431
|
+
(key) => key in incoming && incoming[key] !== options[key]
|
|
432
|
+
) || // No live-update path means the only correct response is to rebuild.
|
|
433
|
+
!instance?.update && Object.keys(incoming).length > 0
|
|
434
|
+
);
|
|
435
|
+
const previousInteractive = options.interactive;
|
|
436
|
+
options = { ...options, ...incoming };
|
|
437
|
+
if (options.interactive !== previousInteractive) {
|
|
438
|
+
ensurePointer();
|
|
439
|
+
rebuild();
|
|
440
|
+
} else if (needsRebuild) {
|
|
441
|
+
rebuild();
|
|
442
|
+
} else if (instance?.update && context) {
|
|
443
|
+
const resolved = resolveResolvedColors(options.colors, host);
|
|
444
|
+
context.colors = resolved;
|
|
445
|
+
instance.update(incoming, context);
|
|
446
|
+
pendingSingleFrame = true;
|
|
447
|
+
}
|
|
448
|
+
if (options.resolution !== void 0) surface.setResolution(options.resolution);
|
|
449
|
+
const wasPaused = userPaused;
|
|
450
|
+
userPaused = options.paused ?? false;
|
|
451
|
+
motion = chunkMMO7KO53_cjs.resolveMotion(options.reducedMotion, 0.25);
|
|
452
|
+
if (wasPaused && !userPaused && motion.animate && !offscreen) startTicker();
|
|
453
|
+
if (!wasPaused && userPaused) stopTicker();
|
|
454
|
+
if (!motion.animate && motion.renderOnce) renderSingleFrame();
|
|
455
|
+
},
|
|
456
|
+
pause() {
|
|
457
|
+
userPaused = true;
|
|
458
|
+
stopTicker();
|
|
459
|
+
},
|
|
460
|
+
resume() {
|
|
461
|
+
userPaused = false;
|
|
462
|
+
if (motion.animate && !offscreen) startTicker();
|
|
463
|
+
},
|
|
464
|
+
renderOnce: renderSingleFrame,
|
|
465
|
+
destroy() {
|
|
466
|
+
if (destroyed) return;
|
|
467
|
+
destroyed = true;
|
|
468
|
+
stopTicker();
|
|
469
|
+
intersectionSubscription?.();
|
|
470
|
+
reducedMotionSubscription?.();
|
|
471
|
+
pointer?.destroy();
|
|
472
|
+
instance?.destroy?.();
|
|
473
|
+
surface.destroy();
|
|
474
|
+
if (canvas.parentNode === host) host.removeChild(canvas);
|
|
475
|
+
instance = null;
|
|
476
|
+
context = null;
|
|
477
|
+
pointer = null;
|
|
478
|
+
},
|
|
479
|
+
get isRunning() {
|
|
480
|
+
return tickerSubscription !== null;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function resolveResolvedColors(colors, host) {
|
|
485
|
+
if (Array.isArray(colors)) {
|
|
486
|
+
return colors.map((color) => {
|
|
487
|
+
if (typeof color === "string" && color.includes("var(")) {
|
|
488
|
+
const resolved = resolveAgainst(color, host);
|
|
489
|
+
if (resolved) return resolved;
|
|
490
|
+
}
|
|
491
|
+
return chunkLGA3N3YJ_cjs.parseColor(color);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return chunk23GSSIMK_cjs.resolvePalette(colors);
|
|
495
|
+
}
|
|
496
|
+
function resolveAgainst(value, host) {
|
|
497
|
+
const probe = document.createElement("span");
|
|
498
|
+
probe.style.display = "none";
|
|
499
|
+
probe.style.color = value;
|
|
500
|
+
host.appendChild(probe);
|
|
501
|
+
const computed = getComputedStyle(probe).color;
|
|
502
|
+
host.removeChild(probe);
|
|
503
|
+
try {
|
|
504
|
+
return computed ? chunkLGA3N3YJ_cjs.parseColor(computed) : null;
|
|
505
|
+
} catch {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function stripUndefined(input) {
|
|
510
|
+
const out = {};
|
|
511
|
+
for (const [key, value] of Object.entries(input)) {
|
|
512
|
+
if (value !== void 0) out[key] = value;
|
|
513
|
+
}
|
|
514
|
+
return out;
|
|
515
|
+
}
|
|
516
|
+
function defineEffect(definition) {
|
|
517
|
+
const registry = chunkMMO7KO53_cjs.getRegistry();
|
|
518
|
+
if (!registry.has("effect", definition.name)) {
|
|
519
|
+
registry.register("effect", definition.name, definition);
|
|
520
|
+
}
|
|
521
|
+
return definition;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/effects/particles/buffer.ts
|
|
525
|
+
var ParticleBuffer = class {
|
|
526
|
+
constructor(capacity) {
|
|
527
|
+
this.capacity = capacity;
|
|
528
|
+
const n = Math.max(1, Math.floor(capacity));
|
|
529
|
+
this.x = new Float32Array(n);
|
|
530
|
+
this.y = new Float32Array(n);
|
|
531
|
+
this.vx = new Float32Array(n);
|
|
532
|
+
this.vy = new Float32Array(n);
|
|
533
|
+
this.life = new Float32Array(n);
|
|
534
|
+
this.maxLife = new Float32Array(n);
|
|
535
|
+
this.size = new Float32Array(n);
|
|
536
|
+
this.colorIndex = new Uint8Array(n);
|
|
537
|
+
this.seed = new Float32Array(n);
|
|
538
|
+
this.extra = new Float32Array(n);
|
|
539
|
+
}
|
|
540
|
+
capacity;
|
|
541
|
+
x;
|
|
542
|
+
y;
|
|
543
|
+
vx;
|
|
544
|
+
vy;
|
|
545
|
+
/** Remaining life in seconds. `Infinity` for particles that never expire. */
|
|
546
|
+
life;
|
|
547
|
+
/** Life the particle started with, for computing normalised age. */
|
|
548
|
+
maxLife;
|
|
549
|
+
size;
|
|
550
|
+
/** Index into the effect's resolved palette. */
|
|
551
|
+
colorIndex;
|
|
552
|
+
/** Free-form per-particle scalar — phase, spin, depth, whatever the effect needs. */
|
|
553
|
+
seed;
|
|
554
|
+
/** Second free-form scalar. */
|
|
555
|
+
extra;
|
|
556
|
+
liveCount = 0;
|
|
557
|
+
get count() {
|
|
558
|
+
return this.liveCount;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Claim a slot.
|
|
562
|
+
* @returns The particle index, or `-1` when the buffer is full. Callers must check.
|
|
563
|
+
*/
|
|
564
|
+
spawn() {
|
|
565
|
+
if (this.liveCount >= this.capacity) return -1;
|
|
566
|
+
return this.liveCount++;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Kill particle `index` by swapping the last live particle into its slot.
|
|
570
|
+
*
|
|
571
|
+
* The caller's loop must **not** advance its cursor after calling this, since a new particle
|
|
572
|
+
* now occupies the current index. Every consumer in this package follows that contract with
|
|
573
|
+
* a `while` loop rather than a `for`.
|
|
574
|
+
*/
|
|
575
|
+
kill(index) {
|
|
576
|
+
const last = --this.liveCount;
|
|
577
|
+
if (index === last) return;
|
|
578
|
+
this.x[index] = this.x[last];
|
|
579
|
+
this.y[index] = this.y[last];
|
|
580
|
+
this.vx[index] = this.vx[last];
|
|
581
|
+
this.vy[index] = this.vy[last];
|
|
582
|
+
this.life[index] = this.life[last];
|
|
583
|
+
this.maxLife[index] = this.maxLife[last];
|
|
584
|
+
this.size[index] = this.size[last];
|
|
585
|
+
this.colorIndex[index] = this.colorIndex[last];
|
|
586
|
+
this.seed[index] = this.seed[last];
|
|
587
|
+
this.extra[index] = this.extra[last];
|
|
588
|
+
}
|
|
589
|
+
/** Normalised age in `[0, 1]`. `0` for immortal particles. */
|
|
590
|
+
age(index) {
|
|
591
|
+
const max = this.maxLife[index];
|
|
592
|
+
if (!Number.isFinite(max) || max <= 0) return 0;
|
|
593
|
+
return 1 - this.life[index] / max;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Reduce the live count, keeping the first `target` particles.
|
|
597
|
+
*
|
|
598
|
+
* Used by adaptive quality: dropping from 1,200 to 800 particles must not resample or
|
|
599
|
+
* reshuffle, because a visible reshuffle draws far more attention than the density change.
|
|
600
|
+
*/
|
|
601
|
+
truncate(target) {
|
|
602
|
+
this.liveCount = Math.max(0, Math.min(this.liveCount, Math.floor(target)));
|
|
603
|
+
}
|
|
604
|
+
clear() {
|
|
605
|
+
this.liveCount = 0;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
function integrate(buffer, dt, drag = 0) {
|
|
609
|
+
const { x, y, vx, vy } = buffer;
|
|
610
|
+
const count = buffer.count;
|
|
611
|
+
if (drag > 0) {
|
|
612
|
+
const retain = Math.pow(1 - Math.min(0.999, drag), dt);
|
|
613
|
+
for (let i = 0; i < count; i++) {
|
|
614
|
+
vx[i] *= retain;
|
|
615
|
+
vy[i] *= retain;
|
|
616
|
+
x[i] += vx[i] * dt;
|
|
617
|
+
y[i] += vy[i] * dt;
|
|
618
|
+
}
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
for (let i = 0; i < count; i++) {
|
|
622
|
+
x[i] += vx[i] * dt;
|
|
623
|
+
y[i] += vy[i] * dt;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function ageAndCull(buffer, dt) {
|
|
627
|
+
let i = 0;
|
|
628
|
+
while (i < buffer.count) {
|
|
629
|
+
buffer.life[i] -= dt;
|
|
630
|
+
if (buffer.life[i] <= 0) {
|
|
631
|
+
buffer.kill(i);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
i++;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function wrapBounds(buffer, width, height, margin = 0) {
|
|
638
|
+
const { x, y } = buffer;
|
|
639
|
+
const count = buffer.count;
|
|
640
|
+
const left = -margin;
|
|
641
|
+
const top = -margin;
|
|
642
|
+
const right = width + margin;
|
|
643
|
+
const bottom = height + margin;
|
|
644
|
+
const spanX = right - left;
|
|
645
|
+
const spanY = bottom - top;
|
|
646
|
+
for (let i = 0; i < count; i++) {
|
|
647
|
+
if (x[i] < left) x[i] += spanX;
|
|
648
|
+
else if (x[i] > right) x[i] -= spanX;
|
|
649
|
+
if (y[i] < top) y[i] += spanY;
|
|
650
|
+
else if (y[i] > bottom) y[i] -= spanY;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
var SpatialGrid = class {
|
|
654
|
+
constructor(capacity) {
|
|
655
|
+
this.capacity = capacity;
|
|
656
|
+
this.cellStart = new Int32Array(2);
|
|
657
|
+
this.items = new Int32Array(Math.max(1, capacity));
|
|
658
|
+
this.counts = new Int32Array(1);
|
|
659
|
+
}
|
|
660
|
+
capacity;
|
|
661
|
+
cols = 1;
|
|
662
|
+
rows = 1;
|
|
663
|
+
cellSize = 1;
|
|
664
|
+
/** Start index of each cell's run inside `items`. Length `cols * rows + 1`. */
|
|
665
|
+
cellStart;
|
|
666
|
+
/** Particle indices, grouped by cell. */
|
|
667
|
+
items;
|
|
668
|
+
/** Scratch counters used while building. */
|
|
669
|
+
counts;
|
|
670
|
+
/**
|
|
671
|
+
* Bucket every live particle. Must be called before {@link forEachNeighbour}.
|
|
672
|
+
* @param cellSize Should equal the query radius — smaller means more cells to scan, larger
|
|
673
|
+
* means more candidates per cell.
|
|
674
|
+
*/
|
|
675
|
+
build(buffer, width, height, cellSize) {
|
|
676
|
+
this.cellSize = Math.max(1, cellSize);
|
|
677
|
+
this.cols = Math.max(1, Math.ceil(width / this.cellSize));
|
|
678
|
+
this.rows = Math.max(1, Math.ceil(height / this.cellSize));
|
|
679
|
+
const cellCount = this.cols * this.rows;
|
|
680
|
+
if (this.counts.length < cellCount) this.counts = new Int32Array(cellCount);
|
|
681
|
+
else this.counts.fill(0, 0, cellCount);
|
|
682
|
+
if (this.cellStart.length < cellCount + 1) this.cellStart = new Int32Array(cellCount + 1);
|
|
683
|
+
if (this.items.length < this.capacity) this.items = new Int32Array(this.capacity);
|
|
684
|
+
const count = buffer.count;
|
|
685
|
+
for (let i = 0; i < count; i++) {
|
|
686
|
+
this.counts[this.cellIndex(buffer.x[i], buffer.y[i])]++;
|
|
687
|
+
}
|
|
688
|
+
let running = 0;
|
|
689
|
+
for (let c = 0; c < cellCount; c++) {
|
|
690
|
+
this.cellStart[c] = running;
|
|
691
|
+
running += this.counts[c];
|
|
692
|
+
}
|
|
693
|
+
this.cellStart[cellCount] = running;
|
|
694
|
+
this.counts.fill(0, 0, cellCount);
|
|
695
|
+
for (let i = 0; i < count; i++) {
|
|
696
|
+
const cell = this.cellIndex(buffer.x[i], buffer.y[i]);
|
|
697
|
+
this.items[this.cellStart[cell] + this.counts[cell]++] = i;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
cellIndex(x, y) {
|
|
701
|
+
const cx = Math.min(this.cols - 1, Math.max(0, Math.floor(x / this.cellSize)));
|
|
702
|
+
const cy = Math.min(this.rows - 1, Math.max(0, Math.floor(y / this.cellSize)));
|
|
703
|
+
return cy * this.cols + cx;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Visit candidate neighbours of particle `index` in the 3×3 cell block around it.
|
|
707
|
+
*
|
|
708
|
+
* Only pairs where `neighbour > index` are visited, so each pair is reported exactly once —
|
|
709
|
+
* which halves the work and means the caller doesn't need a "have I drawn this line" check.
|
|
710
|
+
* The callback still receives candidates outside the radius; the caller does the exact test,
|
|
711
|
+
* because it usually needs the distance anyway.
|
|
712
|
+
*/
|
|
713
|
+
forEachNeighbour(buffer, index, visit) {
|
|
714
|
+
const cx = Math.floor(buffer.x[index] / this.cellSize);
|
|
715
|
+
const cy = Math.floor(buffer.y[index] / this.cellSize);
|
|
716
|
+
const minX = Math.max(0, cx - 1);
|
|
717
|
+
const maxX = Math.min(this.cols - 1, cx + 1);
|
|
718
|
+
const minY = Math.max(0, cy - 1);
|
|
719
|
+
const maxY = Math.min(this.rows - 1, cy + 1);
|
|
720
|
+
for (let y = minY; y <= maxY; y++) {
|
|
721
|
+
const rowBase = y * this.cols;
|
|
722
|
+
for (let x = minX; x <= maxX; x++) {
|
|
723
|
+
const cell = rowBase + x;
|
|
724
|
+
const start = this.cellStart[cell];
|
|
725
|
+
const end = this.cellStart[cell + 1];
|
|
726
|
+
for (let k = start; k < end; k++) {
|
|
727
|
+
const neighbour = this.items[k];
|
|
728
|
+
if (neighbour > index) visit(neighbour);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
// src/effects/backgrounds/aurora.ts
|
|
736
|
+
var DEFAULTS = {
|
|
737
|
+
colors: "aurora",
|
|
738
|
+
speed: 1,
|
|
739
|
+
intensity: 1,
|
|
740
|
+
opacity: 1,
|
|
741
|
+
blur: 24,
|
|
742
|
+
blend: "lighter",
|
|
743
|
+
seed: 1,
|
|
744
|
+
interactive: true,
|
|
745
|
+
// Aurora is entirely soft-focus. Rendering above half resolution is spending fill rate on
|
|
746
|
+
// detail the blur immediately destroys.
|
|
747
|
+
resolution: 0.5,
|
|
748
|
+
performance: "auto",
|
|
749
|
+
reducedMotion: "static",
|
|
750
|
+
paused: false,
|
|
751
|
+
pauseOffscreen: true,
|
|
752
|
+
curtains: 4,
|
|
753
|
+
slices: 48,
|
|
754
|
+
height: 0.75,
|
|
755
|
+
position: 0.42,
|
|
756
|
+
detail: 2.4,
|
|
757
|
+
drift: 0.18,
|
|
758
|
+
falloff: 0.55,
|
|
759
|
+
pointerInfluence: 0.35
|
|
760
|
+
};
|
|
761
|
+
var aurora = defineEffect({
|
|
762
|
+
name: "aurora",
|
|
763
|
+
defaults: DEFAULTS,
|
|
764
|
+
// Changing the curtain count rebuilds the whole composition; there is nothing to reconcile.
|
|
765
|
+
structuralOptions: ["curtains", "slices"],
|
|
766
|
+
setup(context, options) {
|
|
767
|
+
let opts = options;
|
|
768
|
+
let curtains = [];
|
|
769
|
+
const ridged = context.noise.ridged2D({ octaves: 4, gain: 0.55, lacunarity: 2.2 });
|
|
770
|
+
const flow = context.noise.fbm2D({ octaves: 2, gain: 0.5 });
|
|
771
|
+
function build() {
|
|
772
|
+
const count = Math.max(1, Math.floor(opts.curtains));
|
|
773
|
+
const palette = chunk23GSSIMK_cjs.expandPalette(context.colors, count);
|
|
774
|
+
const rng = context.rng;
|
|
775
|
+
curtains = [];
|
|
776
|
+
for (let i = 0; i < count; i++) {
|
|
777
|
+
const slot = (i + 0.5) / count;
|
|
778
|
+
curtains.push({
|
|
779
|
+
color: palette[i],
|
|
780
|
+
center: chunkILKUL7GD_cjs.clamp(slot + rng.range(-0.12, 0.12), 0.05, 0.95),
|
|
781
|
+
width: rng.range(0.28, 0.6),
|
|
782
|
+
noiseOffset: rng.range(0, 100),
|
|
783
|
+
speed: rng.range(0.6, 1.4),
|
|
784
|
+
yOffset: rng.range(-0.08, 0.08),
|
|
785
|
+
// Later curtains are dimmer, which creates depth ordering without a z-buffer.
|
|
786
|
+
alpha: chunkILKUL7GD_cjs.lerp(0.95, 0.5, count === 1 ? 0 : i / (count - 1))
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
build();
|
|
791
|
+
const instance = {
|
|
792
|
+
frame({ time }) {
|
|
793
|
+
const { ctx, size, pointer } = context;
|
|
794
|
+
const { width, height } = size;
|
|
795
|
+
context.surface.clear();
|
|
796
|
+
const sliceCount = Math.max(4, Math.floor(opts.slices));
|
|
797
|
+
const bandHeight = height * chunkILKUL7GD_cjs.clamp(opts.height, 0.05, 2);
|
|
798
|
+
const bandCenter = height * chunkILKUL7GD_cjs.clamp(opts.position, 0, 1);
|
|
799
|
+
const intensity = Math.max(0, opts.intensity);
|
|
800
|
+
const detail = Math.max(0.1, opts.detail);
|
|
801
|
+
const drift = opts.drift;
|
|
802
|
+
const falloff = chunkILKUL7GD_cjs.clamp(opts.falloff, 0, 1);
|
|
803
|
+
const sway = pointer && opts.interactive !== false ? (pointer.smoothNx - 0.5) * width * opts.pointerInfluence : 0;
|
|
804
|
+
const previousBlend = ctx.globalCompositeOperation;
|
|
805
|
+
ctx.globalCompositeOperation = opts.blend ?? "lighter";
|
|
806
|
+
for (const curtain of curtains) {
|
|
807
|
+
const t = time * curtain.speed;
|
|
808
|
+
const curtainWidth = width * curtain.width;
|
|
809
|
+
const centerX = width * curtain.center + sway * curtain.speed * 0.5;
|
|
810
|
+
const startX = centerX - curtainWidth / 2;
|
|
811
|
+
const sliceWidth = curtainWidth / sliceCount;
|
|
812
|
+
const top = bandCenter - bandHeight / 2 + height * curtain.yOffset;
|
|
813
|
+
const bottom = top + bandHeight;
|
|
814
|
+
const gradient = ctx.createLinearGradient(0, top, 0, bottom);
|
|
815
|
+
const rgb = `${Math.round(curtain.color.r * 255)},${Math.round(curtain.color.g * 255)},${Math.round(curtain.color.b * 255)}`;
|
|
816
|
+
const peak = curtain.alpha * intensity;
|
|
817
|
+
gradient.addColorStop(0, `rgba(${rgb},0)`);
|
|
818
|
+
gradient.addColorStop(chunkILKUL7GD_cjs.lerp(0.45, 0.25, falloff), `rgba(${rgb},${peak * 0.75})`);
|
|
819
|
+
gradient.addColorStop(0.72, `rgba(${rgb},${peak})`);
|
|
820
|
+
gradient.addColorStop(1, `rgba(${rgb},0)`);
|
|
821
|
+
ctx.fillStyle = gradient;
|
|
822
|
+
for (let i = 0; i < sliceCount; i++) {
|
|
823
|
+
const u = i / sliceCount;
|
|
824
|
+
const filament = ridged(
|
|
825
|
+
u * detail * 4 + curtain.noiseOffset,
|
|
826
|
+
t * 0.12 + curtain.noiseOffset
|
|
827
|
+
);
|
|
828
|
+
const fold = flow(u * detail + curtain.noiseOffset, t * 0.18) * width * drift * 0.5;
|
|
829
|
+
const rise = Math.sin(u * chunkILKUL7GD_cjs.TAU * 1.5 + t * 0.6 + curtain.noiseOffset) * height * 0.03;
|
|
830
|
+
const alpha = 0.3 + 0.7 * filament * filament;
|
|
831
|
+
if (alpha < 0.012) continue;
|
|
832
|
+
ctx.globalAlpha = chunkILKUL7GD_cjs.clamp(alpha, 0, 1);
|
|
833
|
+
ctx.fillRect(startX + i * sliceWidth + fold, top + rise, sliceWidth + 1, bandHeight);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
ctx.globalAlpha = 1;
|
|
837
|
+
ctx.globalCompositeOperation = previousBlend;
|
|
838
|
+
},
|
|
839
|
+
update(next) {
|
|
840
|
+
opts = { ...opts, ...next };
|
|
841
|
+
if ("colors" in next) build();
|
|
842
|
+
},
|
|
843
|
+
destroy() {
|
|
844
|
+
curtains = [];
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
return instance;
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// src/effects/backgrounds/mesh-gradient.ts
|
|
852
|
+
var DEFAULTS2 = {
|
|
853
|
+
colors: "ansh",
|
|
854
|
+
speed: 1,
|
|
855
|
+
intensity: 1,
|
|
856
|
+
opacity: 1,
|
|
857
|
+
blur: 40,
|
|
858
|
+
blend: "lighter",
|
|
859
|
+
seed: 7,
|
|
860
|
+
interactive: true,
|
|
861
|
+
// Quarter resolution. The output is a heavily blurred gradient field — there is literally no
|
|
862
|
+
// recoverable detail above this, and it cuts fill cost by 16×.
|
|
863
|
+
resolution: 0.25,
|
|
864
|
+
performance: "auto",
|
|
865
|
+
reducedMotion: "static",
|
|
866
|
+
paused: false,
|
|
867
|
+
pauseOffscreen: true,
|
|
868
|
+
points: 5,
|
|
869
|
+
radius: 0.55,
|
|
870
|
+
wander: 0.22,
|
|
871
|
+
pointerInfluence: 0.4,
|
|
872
|
+
pointerRadius: 0.45,
|
|
873
|
+
backdrop: true,
|
|
874
|
+
grain: 0.04
|
|
875
|
+
};
|
|
876
|
+
var meshGradient = defineEffect({
|
|
877
|
+
name: "mesh-gradient",
|
|
878
|
+
defaults: DEFAULTS2,
|
|
879
|
+
structuralOptions: ["points"],
|
|
880
|
+
setup(context, options) {
|
|
881
|
+
let opts = options;
|
|
882
|
+
let points = [];
|
|
883
|
+
let backdropColor = { r: 0, g: 0, b: 0};
|
|
884
|
+
let grainTile = null;
|
|
885
|
+
function build() {
|
|
886
|
+
const count = Math.max(1, Math.floor(opts.points));
|
|
887
|
+
const palette = chunk23GSSIMK_cjs.expandPalette(context.colors, count);
|
|
888
|
+
const rng = context.rng;
|
|
889
|
+
backdropColor = palette.reduce(
|
|
890
|
+
(darkest, c) => c.r + c.g + c.b < darkest.r + darkest.g + darkest.b ? c : darkest
|
|
891
|
+
);
|
|
892
|
+
points = [];
|
|
893
|
+
for (let i = 0; i < count; i++) {
|
|
894
|
+
const angle = i / count * chunkILKUL7GD_cjs.TAU + rng.range(-0.4, 0.4);
|
|
895
|
+
const distance = rng.range(0.18, 0.42);
|
|
896
|
+
points.push({
|
|
897
|
+
color: palette[i],
|
|
898
|
+
homeX: 0.5 + Math.cos(angle) * distance,
|
|
899
|
+
homeY: 0.5 + Math.sin(angle) * distance,
|
|
900
|
+
// Irrational-ish frequency ratios: the path's period is the LCM of the two, which
|
|
901
|
+
// for these values is long enough that no viewer will see it close.
|
|
902
|
+
freqX1: rng.range(0.08, 0.17),
|
|
903
|
+
freqX2: rng.range(0.19, 0.31),
|
|
904
|
+
freqY1: rng.range(0.09, 0.19),
|
|
905
|
+
freqY2: rng.range(0.21, 0.34),
|
|
906
|
+
phaseX: rng.range(0, chunkILKUL7GD_cjs.TAU),
|
|
907
|
+
phaseY: rng.range(0, chunkILKUL7GD_cjs.TAU),
|
|
908
|
+
scale: rng.range(0.75, 1.3),
|
|
909
|
+
x: 0,
|
|
910
|
+
y: 0
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function buildGrain() {
|
|
915
|
+
const strength = chunkILKUL7GD_cjs.clamp(opts.grain, 0, 1);
|
|
916
|
+
if (strength <= 0) {
|
|
917
|
+
grainTile = null;
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
const SIZE = 128;
|
|
921
|
+
const tile = document.createElement("canvas");
|
|
922
|
+
tile.width = SIZE;
|
|
923
|
+
tile.height = SIZE;
|
|
924
|
+
const tileCtx = tile.getContext("2d");
|
|
925
|
+
if (!tileCtx) {
|
|
926
|
+
grainTile = null;
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const image = tileCtx.createImageData(SIZE, SIZE);
|
|
930
|
+
const data = image.data;
|
|
931
|
+
const rng = context.rng.fork();
|
|
932
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
933
|
+
const value = rng.next() * 255;
|
|
934
|
+
data[i] = value;
|
|
935
|
+
data[i + 1] = value;
|
|
936
|
+
data[i + 2] = value;
|
|
937
|
+
data[i + 3] = 255;
|
|
938
|
+
}
|
|
939
|
+
tileCtx.putImageData(image, 0, 0);
|
|
940
|
+
grainTile = tile;
|
|
941
|
+
}
|
|
942
|
+
build();
|
|
943
|
+
buildGrain();
|
|
944
|
+
const instance = {
|
|
945
|
+
frame({ time }) {
|
|
946
|
+
const { ctx, size, pointer, surface } = context;
|
|
947
|
+
const { width, height } = size;
|
|
948
|
+
const diagonal = Math.hypot(width, height);
|
|
949
|
+
surface.clear();
|
|
950
|
+
const intensity = chunkILKUL7GD_cjs.clamp(opts.intensity, 0, 4);
|
|
951
|
+
const wander = opts.wander;
|
|
952
|
+
const baseRadius = diagonal * chunkILKUL7GD_cjs.clamp(opts.radius, 0.05, 2);
|
|
953
|
+
if (opts.backdrop) {
|
|
954
|
+
ctx.globalCompositeOperation = "source-over";
|
|
955
|
+
ctx.fillStyle = `rgb(${Math.round(backdropColor.r * 255)},${Math.round(backdropColor.g * 255)},${Math.round(backdropColor.b * 255)})`;
|
|
956
|
+
ctx.fillRect(0, 0, width, height);
|
|
957
|
+
}
|
|
958
|
+
const interactive = opts.interactive !== false && pointer !== null;
|
|
959
|
+
const pointerX = interactive ? pointer.smoothNx : 0.5;
|
|
960
|
+
const pointerY = interactive ? pointer.smoothNy : 0.5;
|
|
961
|
+
const pointerActive = interactive && pointer.everInside;
|
|
962
|
+
const pointerStrength = pointerActive ? chunkILKUL7GD_cjs.clamp(opts.pointerInfluence, 0, 1) : 0;
|
|
963
|
+
const pointerRadius = chunkILKUL7GD_cjs.clamp(opts.pointerRadius, 0.01, 2);
|
|
964
|
+
ctx.globalCompositeOperation = opts.blend ?? "lighter";
|
|
965
|
+
for (const point of points) {
|
|
966
|
+
const driftX = (Math.sin(time * point.freqX1 + point.phaseX) * 0.6 + Math.sin(time * point.freqX2 + point.phaseX * 1.7) * 0.4) * wander;
|
|
967
|
+
const driftY = (Math.cos(time * point.freqY1 + point.phaseY) * 0.6 + Math.cos(time * point.freqY2 + point.phaseY * 1.3) * 0.4) * wander;
|
|
968
|
+
let nx = point.homeX + driftX;
|
|
969
|
+
let ny = point.homeY + driftY;
|
|
970
|
+
if (pointerStrength > 0) {
|
|
971
|
+
const dx = pointerX - nx;
|
|
972
|
+
const dy = pointerY - ny;
|
|
973
|
+
const distance = Math.hypot(dx, dy);
|
|
974
|
+
const falloff = chunkILKUL7GD_cjs.clamp(1 - distance / pointerRadius, 0, 1);
|
|
975
|
+
const pull = falloff * falloff * pointerStrength;
|
|
976
|
+
nx += dx * pull;
|
|
977
|
+
ny += dy * pull;
|
|
978
|
+
}
|
|
979
|
+
point.x = nx * width;
|
|
980
|
+
point.y = ny * height;
|
|
981
|
+
const radius = baseRadius * point.scale;
|
|
982
|
+
const rgb = `${Math.round(point.color.r * 255)},${Math.round(point.color.g * 255)},${Math.round(point.color.b * 255)}`;
|
|
983
|
+
const peak = chunkILKUL7GD_cjs.clamp(point.color.a * intensity * 0.85, 0, 1);
|
|
984
|
+
const gradient = ctx.createRadialGradient(point.x, point.y, 0, point.x, point.y, radius);
|
|
985
|
+
gradient.addColorStop(0, `rgba(${rgb},${peak})`);
|
|
986
|
+
gradient.addColorStop(0.3, `rgba(${rgb},${peak * 0.62})`);
|
|
987
|
+
gradient.addColorStop(0.62, `rgba(${rgb},${peak * 0.22})`);
|
|
988
|
+
gradient.addColorStop(1, `rgba(${rgb},0)`);
|
|
989
|
+
ctx.fillStyle = gradient;
|
|
990
|
+
ctx.fillRect(point.x - radius, point.y - radius, radius * 2, radius * 2);
|
|
991
|
+
}
|
|
992
|
+
if (grainTile) {
|
|
993
|
+
const strength = chunkILKUL7GD_cjs.clamp(opts.grain, 0, 1);
|
|
994
|
+
ctx.globalCompositeOperation = "overlay";
|
|
995
|
+
ctx.globalAlpha = strength;
|
|
996
|
+
const pattern = ctx.createPattern(grainTile, "repeat");
|
|
997
|
+
if (pattern) {
|
|
998
|
+
ctx.fillStyle = pattern;
|
|
999
|
+
ctx.fillRect(0, 0, width, height);
|
|
1000
|
+
}
|
|
1001
|
+
ctx.globalAlpha = 1;
|
|
1002
|
+
}
|
|
1003
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1004
|
+
},
|
|
1005
|
+
update(next) {
|
|
1006
|
+
const previousGrain = opts.grain;
|
|
1007
|
+
opts = {
|
|
1008
|
+
...opts,
|
|
1009
|
+
...next
|
|
1010
|
+
};
|
|
1011
|
+
if ("colors" in next) build();
|
|
1012
|
+
if (opts.grain !== previousGrain) buildGrain();
|
|
1013
|
+
},
|
|
1014
|
+
destroy() {
|
|
1015
|
+
points = [];
|
|
1016
|
+
if (grainTile) {
|
|
1017
|
+
grainTile.width = 1;
|
|
1018
|
+
grainTile.height = 1;
|
|
1019
|
+
grainTile = null;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
return instance;
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
var GRADIENT_DEFAULTS = {
|
|
1027
|
+
colors: "ansh",
|
|
1028
|
+
speed: 1,
|
|
1029
|
+
intensity: 1,
|
|
1030
|
+
opacity: 1,
|
|
1031
|
+
blur: 0,
|
|
1032
|
+
blend: "source-over",
|
|
1033
|
+
seed: 1,
|
|
1034
|
+
interactive: false,
|
|
1035
|
+
resolution: 1,
|
|
1036
|
+
performance: "auto",
|
|
1037
|
+
reducedMotion: "static",
|
|
1038
|
+
paused: false,
|
|
1039
|
+
pauseOffscreen: true,
|
|
1040
|
+
rotation: 4,
|
|
1041
|
+
angle: 120,
|
|
1042
|
+
travel: 0.25,
|
|
1043
|
+
stops: 24
|
|
1044
|
+
};
|
|
1045
|
+
var animatedGradient = defineEffect({
|
|
1046
|
+
name: "animated-gradient",
|
|
1047
|
+
defaults: GRADIENT_DEFAULTS,
|
|
1048
|
+
setup(context, options) {
|
|
1049
|
+
let opts = options;
|
|
1050
|
+
let samples = [];
|
|
1051
|
+
function build() {
|
|
1052
|
+
const count = Math.max(2, Math.floor(opts.stops));
|
|
1053
|
+
const doubled = [...context.colors, ...context.colors, context.colors[0]];
|
|
1054
|
+
samples = chunk23GSSIMK_cjs.expandPalette(doubled, count * 2);
|
|
1055
|
+
}
|
|
1056
|
+
build();
|
|
1057
|
+
return {
|
|
1058
|
+
frame({ time }) {
|
|
1059
|
+
const { ctx, size, surface } = context;
|
|
1060
|
+
const { width, height } = size;
|
|
1061
|
+
surface.clear();
|
|
1062
|
+
const stopCount = Math.max(2, Math.floor(opts.stops));
|
|
1063
|
+
const angleDeg = opts.angle + opts.rotation / 60 * time * 360;
|
|
1064
|
+
const angle = angleDeg * Math.PI / 180;
|
|
1065
|
+
const halfDiagonal = Math.hypot(width, height) / 2;
|
|
1066
|
+
const cx = width / 2;
|
|
1067
|
+
const cy = height / 2;
|
|
1068
|
+
const dx = Math.cos(angle) * halfDiagonal;
|
|
1069
|
+
const dy = Math.sin(angle) * halfDiagonal;
|
|
1070
|
+
const gradient = ctx.createLinearGradient(cx - dx, cy - dy, cx + dx, cy + dy);
|
|
1071
|
+
const phase = time * opts.travel * 0.1 % 1;
|
|
1072
|
+
for (let i = 0; i < stopCount; i++) {
|
|
1073
|
+
const t = i / (stopCount - 1);
|
|
1074
|
+
const sampleIndex = Math.floor((t + phase) % 1 * (samples.length - 1));
|
|
1075
|
+
const color = samples[sampleIndex];
|
|
1076
|
+
const alpha = chunkILKUL7GD_cjs.clamp(color.a * opts.intensity, 0, 1);
|
|
1077
|
+
gradient.addColorStop(
|
|
1078
|
+
t,
|
|
1079
|
+
`rgba(${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)},${alpha})`
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
ctx.globalCompositeOperation = opts.blend ?? "source-over";
|
|
1083
|
+
ctx.fillStyle = gradient;
|
|
1084
|
+
ctx.fillRect(0, 0, width, height);
|
|
1085
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1086
|
+
},
|
|
1087
|
+
update(next) {
|
|
1088
|
+
opts = {
|
|
1089
|
+
...opts,
|
|
1090
|
+
...next
|
|
1091
|
+
};
|
|
1092
|
+
if ("colors" in next || "stops" in next) build();
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// src/effects/particles/field.ts
|
|
1099
|
+
var DEFAULTS3 = {
|
|
1100
|
+
colors: "neon",
|
|
1101
|
+
speed: 1,
|
|
1102
|
+
intensity: 1,
|
|
1103
|
+
opacity: 1,
|
|
1104
|
+
blur: 0,
|
|
1105
|
+
blend: "lighter",
|
|
1106
|
+
seed: 3,
|
|
1107
|
+
interactive: true,
|
|
1108
|
+
resolution: 1,
|
|
1109
|
+
performance: "auto",
|
|
1110
|
+
reducedMotion: "pause",
|
|
1111
|
+
paused: false,
|
|
1112
|
+
pauseOffscreen: true,
|
|
1113
|
+
count: 700,
|
|
1114
|
+
size: 1.6,
|
|
1115
|
+
sizeVariance: 0.6,
|
|
1116
|
+
scale: 16e-4,
|
|
1117
|
+
flowSpeed: 48,
|
|
1118
|
+
fieldDrift: 0.12,
|
|
1119
|
+
glow: true,
|
|
1120
|
+
trails: 0,
|
|
1121
|
+
pointerRadius: 140,
|
|
1122
|
+
pointerForce: 1
|
|
1123
|
+
};
|
|
1124
|
+
var MAX_PARTICLES = 4e3;
|
|
1125
|
+
var particleField = defineEffect({
|
|
1126
|
+
name: "particle-field",
|
|
1127
|
+
defaults: DEFAULTS3,
|
|
1128
|
+
structuralOptions: ["count"],
|
|
1129
|
+
setup(context, options) {
|
|
1130
|
+
let opts = options;
|
|
1131
|
+
const capacity = Math.min(MAX_PARTICLES, Math.max(1, Math.floor(opts.count)));
|
|
1132
|
+
const buffer = new ParticleBuffer(capacity);
|
|
1133
|
+
const gradients = new GradientCache(64);
|
|
1134
|
+
const curl = context.noise.curlAnimated({ octaves: 2, gain: 0.5 });
|
|
1135
|
+
let seeded = false;
|
|
1136
|
+
function seed() {
|
|
1137
|
+
const { width, height } = context.size;
|
|
1138
|
+
const rng = context.rng;
|
|
1139
|
+
const colorCount = Math.max(1, context.colors.length);
|
|
1140
|
+
buffer.clear();
|
|
1141
|
+
for (let i = 0; i < capacity; i++) {
|
|
1142
|
+
const index = buffer.spawn();
|
|
1143
|
+
if (index < 0) break;
|
|
1144
|
+
buffer.x[index] = rng.next() * width;
|
|
1145
|
+
buffer.y[index] = rng.next() * height;
|
|
1146
|
+
buffer.vx[index] = 0;
|
|
1147
|
+
buffer.vy[index] = 0;
|
|
1148
|
+
buffer.life[index] = Infinity;
|
|
1149
|
+
buffer.maxLife[index] = Infinity;
|
|
1150
|
+
buffer.size[index] = opts.size * (1 + (rng.next() * 2 - 1) * chunkILKUL7GD_cjs.clamp(opts.sizeVariance, 0, 1));
|
|
1151
|
+
buffer.colorIndex[index] = rng.int(0, colorCount - 1);
|
|
1152
|
+
buffer.seed[index] = rng.next() * chunkILKUL7GD_cjs.TAU;
|
|
1153
|
+
}
|
|
1154
|
+
seeded = true;
|
|
1155
|
+
}
|
|
1156
|
+
return {
|
|
1157
|
+
frame({ time, delta, quality }) {
|
|
1158
|
+
const { ctx, size, pointer, surface, colors } = context;
|
|
1159
|
+
const { width, height } = size;
|
|
1160
|
+
if (!seeded || buffer.count === 0 && width > 1) seed();
|
|
1161
|
+
const targetCount = Math.max(
|
|
1162
|
+
8,
|
|
1163
|
+
Math.floor(capacity * (opts.performance === "high" ? 1 : quality))
|
|
1164
|
+
);
|
|
1165
|
+
if (buffer.count > targetCount) buffer.truncate(targetCount);
|
|
1166
|
+
const trails = chunkILKUL7GD_cjs.clamp(opts.trails, 0, 0.98);
|
|
1167
|
+
if (trails <= 0) {
|
|
1168
|
+
surface.clear();
|
|
1169
|
+
} else {
|
|
1170
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
1171
|
+
ctx.fillStyle = `rgba(0,0,0,${1 - trails})`;
|
|
1172
|
+
ctx.fillRect(0, 0, width, height);
|
|
1173
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1174
|
+
}
|
|
1175
|
+
const scale = Math.max(1e-6, opts.scale);
|
|
1176
|
+
const flowSpeed = opts.flowSpeed * opts.intensity;
|
|
1177
|
+
const fieldTime = time * opts.fieldDrift;
|
|
1178
|
+
const count = buffer.count;
|
|
1179
|
+
for (let i = 0; i < count; i++) {
|
|
1180
|
+
const flow = curl(buffer.x[i] * scale, buffer.y[i] * scale, fieldTime);
|
|
1181
|
+
buffer.vx[i] = flow.x * flowSpeed;
|
|
1182
|
+
buffer.vy[i] = flow.y * flowSpeed;
|
|
1183
|
+
}
|
|
1184
|
+
if (opts.interactive !== false && pointer && pointer.everInside) {
|
|
1185
|
+
const px = pointer.smoothX;
|
|
1186
|
+
const py = pointer.smoothY;
|
|
1187
|
+
const radius = Math.max(1, opts.pointerRadius);
|
|
1188
|
+
const radiusSq = radius * radius;
|
|
1189
|
+
const force = opts.pointerForce * flowSpeed * 2.4;
|
|
1190
|
+
for (let i = 0; i < count; i++) {
|
|
1191
|
+
const dx = buffer.x[i] - px;
|
|
1192
|
+
const dy = buffer.y[i] - py;
|
|
1193
|
+
const distSq = dx * dx + dy * dy;
|
|
1194
|
+
if (distSq > radiusSq || distSq < 1e-4) continue;
|
|
1195
|
+
const distance = Math.sqrt(distSq);
|
|
1196
|
+
const falloff = 1 - distance / radius;
|
|
1197
|
+
const push = force * falloff * falloff / distance;
|
|
1198
|
+
buffer.vx[i] += dx * push;
|
|
1199
|
+
buffer.vy[i] += dy * push;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
integrate(buffer, delta, 0);
|
|
1203
|
+
wrapBounds(buffer, width, height, opts.size * 4);
|
|
1204
|
+
ctx.globalCompositeOperation = opts.blend ?? "lighter";
|
|
1205
|
+
const colorCount = Math.max(1, colors.length);
|
|
1206
|
+
const glow = opts.glow;
|
|
1207
|
+
for (let i = 0; i < count; i++) {
|
|
1208
|
+
const color = colors[buffer.colorIndex[i] % colorCount];
|
|
1209
|
+
const shimmer = 0.72 + 0.28 * Math.sin(time * 1.6 + buffer.seed[i]);
|
|
1210
|
+
const radius = buffer.size[i];
|
|
1211
|
+
if (glow) {
|
|
1212
|
+
drawGlow(ctx, buffer.x[i], buffer.y[i], radius * 4, color, shimmer, gradients);
|
|
1213
|
+
} else {
|
|
1214
|
+
ctx.globalAlpha = chunkILKUL7GD_cjs.clamp(color.a * shimmer, 0, 1);
|
|
1215
|
+
ctx.fillStyle = `rgb(${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)})`;
|
|
1216
|
+
ctx.beginPath();
|
|
1217
|
+
ctx.arc(buffer.x[i], buffer.y[i], radius, 0, chunkILKUL7GD_cjs.TAU);
|
|
1218
|
+
ctx.fill();
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
ctx.globalAlpha = 1;
|
|
1222
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1223
|
+
},
|
|
1224
|
+
resize() {
|
|
1225
|
+
seeded = false;
|
|
1226
|
+
},
|
|
1227
|
+
update(next) {
|
|
1228
|
+
const previousSize = opts.size;
|
|
1229
|
+
opts = {
|
|
1230
|
+
...opts,
|
|
1231
|
+
...next
|
|
1232
|
+
};
|
|
1233
|
+
if (opts.size !== previousSize) {
|
|
1234
|
+
const rng = context.rng;
|
|
1235
|
+
for (let i = 0; i < buffer.count; i++) {
|
|
1236
|
+
buffer.size[i] = opts.size * (1 + (rng.next() * 2 - 1) * chunkILKUL7GD_cjs.clamp(opts.sizeVariance, 0, 1));
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if ("colors" in next) {
|
|
1240
|
+
const colorCount = Math.max(1, context.colors.length);
|
|
1241
|
+
for (let i = 0; i < buffer.count; i++) {
|
|
1242
|
+
buffer.colorIndex[i] = buffer.colorIndex[i] % colorCount;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
gradients.clear();
|
|
1246
|
+
},
|
|
1247
|
+
destroy() {
|
|
1248
|
+
buffer.clear();
|
|
1249
|
+
gradients.clear();
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
// src/effects/particles/constellation.ts
|
|
1256
|
+
var DEFAULTS4 = {
|
|
1257
|
+
colors: ["#8ab4ff", "#c9d7ff"],
|
|
1258
|
+
speed: 1,
|
|
1259
|
+
intensity: 1,
|
|
1260
|
+
opacity: 1,
|
|
1261
|
+
blur: 0,
|
|
1262
|
+
blend: "source-over",
|
|
1263
|
+
seed: 11,
|
|
1264
|
+
interactive: true,
|
|
1265
|
+
resolution: 1,
|
|
1266
|
+
performance: "auto",
|
|
1267
|
+
reducedMotion: "static",
|
|
1268
|
+
paused: false,
|
|
1269
|
+
pauseOffscreen: true,
|
|
1270
|
+
count: 120,
|
|
1271
|
+
linkDistance: 140,
|
|
1272
|
+
nodeSize: 2,
|
|
1273
|
+
lineWidth: 1,
|
|
1274
|
+
drift: 18,
|
|
1275
|
+
pointerRadius: 180,
|
|
1276
|
+
pointerLinks: true,
|
|
1277
|
+
alphaSteps: 6
|
|
1278
|
+
};
|
|
1279
|
+
var MAX_NODES = 1200;
|
|
1280
|
+
var constellation = defineEffect({
|
|
1281
|
+
name: "constellation",
|
|
1282
|
+
defaults: DEFAULTS4,
|
|
1283
|
+
structuralOptions: ["count"],
|
|
1284
|
+
setup(context, options) {
|
|
1285
|
+
let opts = options;
|
|
1286
|
+
const capacity = Math.min(MAX_NODES, Math.max(2, Math.floor(opts.count)));
|
|
1287
|
+
const buffer = new ParticleBuffer(capacity);
|
|
1288
|
+
const grid = new SpatialGrid(capacity);
|
|
1289
|
+
let buckets = [];
|
|
1290
|
+
let seeded = false;
|
|
1291
|
+
function rebuildBuckets() {
|
|
1292
|
+
const steps = Math.max(1, Math.floor(opts.alphaSteps));
|
|
1293
|
+
buckets = Array.from({ length: steps }, () => []);
|
|
1294
|
+
}
|
|
1295
|
+
function seed() {
|
|
1296
|
+
const { width, height } = context.size;
|
|
1297
|
+
const rng = context.rng;
|
|
1298
|
+
const colorCount = Math.max(1, context.colors.length);
|
|
1299
|
+
buffer.clear();
|
|
1300
|
+
for (let i = 0; i < capacity; i++) {
|
|
1301
|
+
const index = buffer.spawn();
|
|
1302
|
+
if (index < 0) break;
|
|
1303
|
+
buffer.x[index] = rng.next() * width;
|
|
1304
|
+
buffer.y[index] = rng.next() * height;
|
|
1305
|
+
const angle = rng.angle();
|
|
1306
|
+
const speed = rng.range(0.4, 1.2);
|
|
1307
|
+
buffer.vx[index] = Math.cos(angle) * speed;
|
|
1308
|
+
buffer.vy[index] = Math.sin(angle) * speed;
|
|
1309
|
+
buffer.life[index] = Infinity;
|
|
1310
|
+
buffer.maxLife[index] = Infinity;
|
|
1311
|
+
buffer.size[index] = opts.nodeSize * rng.range(0.7, 1.4);
|
|
1312
|
+
buffer.colorIndex[index] = rng.int(0, colorCount - 1);
|
|
1313
|
+
buffer.seed[index] = rng.next() * chunkILKUL7GD_cjs.TAU;
|
|
1314
|
+
}
|
|
1315
|
+
seeded = true;
|
|
1316
|
+
}
|
|
1317
|
+
rebuildBuckets();
|
|
1318
|
+
return {
|
|
1319
|
+
frame({ time, delta, quality }) {
|
|
1320
|
+
const { ctx, size, pointer, surface, colors } = context;
|
|
1321
|
+
const { width, height } = size;
|
|
1322
|
+
if (!seeded || buffer.count === 0 && width > 1) seed();
|
|
1323
|
+
const targetCount = Math.max(
|
|
1324
|
+
6,
|
|
1325
|
+
Math.floor(capacity * (opts.performance === "high" ? 1 : quality))
|
|
1326
|
+
);
|
|
1327
|
+
if (buffer.count > targetCount) buffer.truncate(targetCount);
|
|
1328
|
+
surface.clear();
|
|
1329
|
+
const drift = opts.drift * opts.intensity;
|
|
1330
|
+
const count = buffer.count;
|
|
1331
|
+
for (let i = 0; i < count; i++) {
|
|
1332
|
+
buffer.x[i] += buffer.vx[i] * drift * delta;
|
|
1333
|
+
buffer.y[i] += buffer.vy[i] * drift * delta;
|
|
1334
|
+
}
|
|
1335
|
+
wrapBounds(buffer, width, height, opts.nodeSize * 2);
|
|
1336
|
+
const linkDistance = Math.max(1, opts.linkDistance);
|
|
1337
|
+
grid.build(buffer, width, height, linkDistance);
|
|
1338
|
+
const steps = buckets.length;
|
|
1339
|
+
for (const bucket of buckets) bucket.length = 0;
|
|
1340
|
+
const linkDistanceSq = linkDistance * linkDistance;
|
|
1341
|
+
for (let i = 0; i < count; i++) {
|
|
1342
|
+
const xi = buffer.x[i];
|
|
1343
|
+
const yi = buffer.y[i];
|
|
1344
|
+
grid.forEachNeighbour(buffer, i, (j) => {
|
|
1345
|
+
const dx = buffer.x[j] - xi;
|
|
1346
|
+
const dy = buffer.y[j] - yi;
|
|
1347
|
+
const distSq = dx * dx + dy * dy;
|
|
1348
|
+
if (distSq > linkDistanceSq) return;
|
|
1349
|
+
const proximity = 1 - Math.sqrt(distSq) / linkDistance;
|
|
1350
|
+
const bucketIndex = Math.min(steps - 1, Math.floor(proximity * proximity * steps));
|
|
1351
|
+
const bucket = buckets[bucketIndex];
|
|
1352
|
+
bucket.push(xi, yi, buffer.x[j], buffer.y[j]);
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
const interactive = opts.interactive !== false && pointer !== null && pointer.everInside;
|
|
1356
|
+
if (interactive && opts.pointerLinks) {
|
|
1357
|
+
const px = pointer.smoothX;
|
|
1358
|
+
const py = pointer.smoothY;
|
|
1359
|
+
const radius = Math.max(1, opts.pointerRadius);
|
|
1360
|
+
const radiusSq = radius * radius;
|
|
1361
|
+
for (let i = 0; i < count; i++) {
|
|
1362
|
+
const dx = buffer.x[i] - px;
|
|
1363
|
+
const dy = buffer.y[i] - py;
|
|
1364
|
+
const distSq = dx * dx + dy * dy;
|
|
1365
|
+
if (distSq > radiusSq) continue;
|
|
1366
|
+
const proximity = 1 - Math.sqrt(distSq) / radius;
|
|
1367
|
+
const bucketIndex = Math.min(steps - 1, Math.floor(proximity * steps));
|
|
1368
|
+
buckets[bucketIndex].push(px, py, buffer.x[i], buffer.y[i]);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
const lineColor = colors[0];
|
|
1372
|
+
const lineRgb = `${Math.round(lineColor.r * 255)},${Math.round(lineColor.g * 255)},${Math.round(lineColor.b * 255)}`;
|
|
1373
|
+
ctx.globalCompositeOperation = opts.blend ?? "source-over";
|
|
1374
|
+
ctx.lineWidth = Math.max(0.1, opts.lineWidth);
|
|
1375
|
+
ctx.lineCap = "round";
|
|
1376
|
+
for (let b = 0; b < steps; b++) {
|
|
1377
|
+
const bucket = buckets[b];
|
|
1378
|
+
if (bucket.length === 0) continue;
|
|
1379
|
+
const alpha = (b + 1) / steps * 0.55 * opts.intensity * lineColor.a;
|
|
1380
|
+
ctx.strokeStyle = `rgba(${lineRgb},${chunkILKUL7GD_cjs.clamp(alpha, 0, 1).toFixed(3)})`;
|
|
1381
|
+
ctx.beginPath();
|
|
1382
|
+
for (let k = 0; k < bucket.length; k += 4) {
|
|
1383
|
+
ctx.moveTo(bucket[k], bucket[k + 1]);
|
|
1384
|
+
ctx.lineTo(bucket[k + 2], bucket[k + 3]);
|
|
1385
|
+
}
|
|
1386
|
+
ctx.stroke();
|
|
1387
|
+
}
|
|
1388
|
+
const colorCount = Math.max(1, colors.length);
|
|
1389
|
+
for (let c = 0; c < colorCount; c++) {
|
|
1390
|
+
const color = colors[c];
|
|
1391
|
+
ctx.fillStyle = `rgba(${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)},${chunkILKUL7GD_cjs.clamp(color.a * opts.intensity, 0, 1)})`;
|
|
1392
|
+
ctx.beginPath();
|
|
1393
|
+
for (let i = 0; i < count; i++) {
|
|
1394
|
+
if (buffer.colorIndex[i] % colorCount !== c) continue;
|
|
1395
|
+
const twinkle = 0.75 + 0.25 * Math.sin(time * 2 + buffer.seed[i]);
|
|
1396
|
+
const radius = buffer.size[i] * twinkle;
|
|
1397
|
+
ctx.moveTo(buffer.x[i] + radius, buffer.y[i]);
|
|
1398
|
+
ctx.arc(buffer.x[i], buffer.y[i], radius, 0, chunkILKUL7GD_cjs.TAU);
|
|
1399
|
+
}
|
|
1400
|
+
ctx.fill();
|
|
1401
|
+
}
|
|
1402
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1403
|
+
},
|
|
1404
|
+
resize() {
|
|
1405
|
+
seeded = false;
|
|
1406
|
+
},
|
|
1407
|
+
update(next) {
|
|
1408
|
+
const previousSteps = opts.alphaSteps;
|
|
1409
|
+
opts = {
|
|
1410
|
+
...opts,
|
|
1411
|
+
...next
|
|
1412
|
+
};
|
|
1413
|
+
if (opts.alphaSteps !== previousSteps) rebuildBuckets();
|
|
1414
|
+
},
|
|
1415
|
+
destroy() {
|
|
1416
|
+
buffer.clear();
|
|
1417
|
+
buckets = [];
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
// src/effects/atmosphere/meteors.ts
|
|
1424
|
+
var DEFAULTS5 = {
|
|
1425
|
+
colors: ["#ffffff", "#a5c8ff", "#8a2be2"],
|
|
1426
|
+
speed: 1,
|
|
1427
|
+
intensity: 1,
|
|
1428
|
+
opacity: 1,
|
|
1429
|
+
blur: 0,
|
|
1430
|
+
blend: "lighter",
|
|
1431
|
+
seed: 5,
|
|
1432
|
+
interactive: false,
|
|
1433
|
+
resolution: 1,
|
|
1434
|
+
performance: "auto",
|
|
1435
|
+
reducedMotion: "pause",
|
|
1436
|
+
paused: false,
|
|
1437
|
+
pauseOffscreen: true,
|
|
1438
|
+
count: 12,
|
|
1439
|
+
angle: 135,
|
|
1440
|
+
angleVariance: 8,
|
|
1441
|
+
velocity: 420,
|
|
1442
|
+
velocityVariance: 0.45,
|
|
1443
|
+
trailLength: 160,
|
|
1444
|
+
thickness: 2,
|
|
1445
|
+
head: true
|
|
1446
|
+
};
|
|
1447
|
+
var MAX_METEORS = 200;
|
|
1448
|
+
var meteors = defineEffect({
|
|
1449
|
+
name: "meteors",
|
|
1450
|
+
defaults: DEFAULTS5,
|
|
1451
|
+
structuralOptions: ["count"],
|
|
1452
|
+
setup(context, options) {
|
|
1453
|
+
let opts = options;
|
|
1454
|
+
const capacity = Math.min(MAX_METEORS, Math.max(1, Math.floor(opts.count * 2)));
|
|
1455
|
+
const buffer = new ParticleBuffer(capacity);
|
|
1456
|
+
let nextSpawn = 0;
|
|
1457
|
+
function sampleGap(rate) {
|
|
1458
|
+
const u = Math.max(1e-6, context.rng.next());
|
|
1459
|
+
return -Math.log(u) / Math.max(1e-3, rate);
|
|
1460
|
+
}
|
|
1461
|
+
function spawn() {
|
|
1462
|
+
const { width, height } = context.size;
|
|
1463
|
+
const rng = context.rng;
|
|
1464
|
+
const index = buffer.spawn();
|
|
1465
|
+
if (index < 0) return;
|
|
1466
|
+
const angle = chunkILKUL7GD_cjs.radians(opts.angle + rng.range(-opts.angleVariance, opts.angleVariance));
|
|
1467
|
+
const dirX = Math.cos(angle);
|
|
1468
|
+
const dirY = Math.sin(angle);
|
|
1469
|
+
const velocity = opts.velocity * (1 + rng.range(-1, 1) * chunkILKUL7GD_cjs.clamp(opts.velocityVariance, 0, 1));
|
|
1470
|
+
const margin = 48;
|
|
1471
|
+
const spread = rng.next();
|
|
1472
|
+
let x;
|
|
1473
|
+
let y;
|
|
1474
|
+
if (Math.abs(dirY) > Math.abs(dirX)) {
|
|
1475
|
+
x = spread * (width + margin * 2) - margin;
|
|
1476
|
+
y = dirY > 0 ? -margin : height + margin;
|
|
1477
|
+
} else {
|
|
1478
|
+
x = dirX > 0 ? -margin : width + margin;
|
|
1479
|
+
y = spread * (height + margin * 2) - margin;
|
|
1480
|
+
}
|
|
1481
|
+
buffer.x[index] = x;
|
|
1482
|
+
buffer.y[index] = y;
|
|
1483
|
+
buffer.vx[index] = dirX * velocity;
|
|
1484
|
+
buffer.vy[index] = dirY * velocity;
|
|
1485
|
+
buffer.size[index] = opts.thickness * rng.range(0.6, 1.5);
|
|
1486
|
+
buffer.colorIndex[index] = rng.int(0, Math.max(0, context.colors.length - 1));
|
|
1487
|
+
buffer.extra[index] = opts.trailLength * rng.range(0.6, 1.35);
|
|
1488
|
+
const travelDistance = Math.hypot(width, height) + margin * 2 + buffer.extra[index];
|
|
1489
|
+
buffer.maxLife[index] = travelDistance / Math.max(1, velocity);
|
|
1490
|
+
buffer.life[index] = buffer.maxLife[index];
|
|
1491
|
+
}
|
|
1492
|
+
return {
|
|
1493
|
+
frame({ delta, quality }) {
|
|
1494
|
+
const { ctx, size, surface, colors } = context;
|
|
1495
|
+
const { width, height } = size;
|
|
1496
|
+
surface.clear();
|
|
1497
|
+
const targetConcurrent = Math.max(
|
|
1498
|
+
1,
|
|
1499
|
+
Math.floor(opts.count * (opts.performance === "high" ? 1 : chunkILKUL7GD_cjs.clamp(quality, 0.3, 1)))
|
|
1500
|
+
);
|
|
1501
|
+
nextSpawn -= delta;
|
|
1502
|
+
const averageLife = Math.hypot(width, height) / Math.max(1, opts.velocity);
|
|
1503
|
+
const rate = targetConcurrent / Math.max(0.1, averageLife);
|
|
1504
|
+
let spawnsThisFrame = 0;
|
|
1505
|
+
while (nextSpawn <= 0 && spawnsThisFrame < 8) {
|
|
1506
|
+
if (buffer.count < targetConcurrent * 2) spawn();
|
|
1507
|
+
nextSpawn += sampleGap(rate);
|
|
1508
|
+
spawnsThisFrame++;
|
|
1509
|
+
}
|
|
1510
|
+
integrate(buffer, delta, 0);
|
|
1511
|
+
ageAndCull(buffer, delta);
|
|
1512
|
+
ctx.globalCompositeOperation = opts.blend ?? "lighter";
|
|
1513
|
+
ctx.lineCap = "round";
|
|
1514
|
+
const count = buffer.count;
|
|
1515
|
+
const colorCount = Math.max(1, colors.length);
|
|
1516
|
+
for (let i = 0; i < count; i++) {
|
|
1517
|
+
const age = buffer.age(i);
|
|
1518
|
+
const fade = age < 0.15 ? age / 0.15 : age > 0.65 ? 1 - (age - 0.65) / 0.35 : 1;
|
|
1519
|
+
const alpha = chunkILKUL7GD_cjs.clamp(fade * opts.intensity, 0, 1);
|
|
1520
|
+
if (alpha <= 0.01) continue;
|
|
1521
|
+
const color = colors[buffer.colorIndex[i] % colorCount];
|
|
1522
|
+
const rgb = `${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)}`;
|
|
1523
|
+
const headX = buffer.x[i];
|
|
1524
|
+
const headY = buffer.y[i];
|
|
1525
|
+
const speed = Math.hypot(buffer.vx[i], buffer.vy[i]);
|
|
1526
|
+
if (speed < 1e-3) continue;
|
|
1527
|
+
const trail = buffer.extra[i];
|
|
1528
|
+
const tailX = headX - buffer.vx[i] / speed * trail;
|
|
1529
|
+
const tailY = headY - buffer.vy[i] / speed * trail;
|
|
1530
|
+
const gradient = ctx.createLinearGradient(headX, headY, tailX, tailY);
|
|
1531
|
+
gradient.addColorStop(0, `rgba(${rgb},${alpha})`);
|
|
1532
|
+
gradient.addColorStop(0.25, `rgba(${rgb},${alpha * 0.45})`);
|
|
1533
|
+
gradient.addColorStop(1, `rgba(${rgb},0)`);
|
|
1534
|
+
ctx.strokeStyle = gradient;
|
|
1535
|
+
ctx.lineWidth = buffer.size[i];
|
|
1536
|
+
ctx.beginPath();
|
|
1537
|
+
ctx.moveTo(headX, headY);
|
|
1538
|
+
ctx.lineTo(tailX, tailY);
|
|
1539
|
+
ctx.stroke();
|
|
1540
|
+
if (opts.head) {
|
|
1541
|
+
const coreRadius = buffer.size[i] * 1.8;
|
|
1542
|
+
const core = ctx.createRadialGradient(headX, headY, 0, headX, headY, coreRadius);
|
|
1543
|
+
core.addColorStop(0, `rgba(255,255,255,${alpha})`);
|
|
1544
|
+
core.addColorStop(0.5, `rgba(${rgb},${alpha * 0.7})`);
|
|
1545
|
+
core.addColorStop(1, `rgba(${rgb},0)`);
|
|
1546
|
+
ctx.fillStyle = core;
|
|
1547
|
+
ctx.fillRect(headX - coreRadius, headY - coreRadius, coreRadius * 2, coreRadius * 2);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1551
|
+
},
|
|
1552
|
+
update(next) {
|
|
1553
|
+
opts = { ...opts, ...next };
|
|
1554
|
+
},
|
|
1555
|
+
destroy() {
|
|
1556
|
+
buffer.clear();
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
// src/effects/atmosphere/precipitation.ts
|
|
1563
|
+
var RAIN_DEFAULTS = {
|
|
1564
|
+
colors: ["#9fc4ff", "#dbe9ff"],
|
|
1565
|
+
speed: 1,
|
|
1566
|
+
intensity: 1,
|
|
1567
|
+
opacity: 0.7,
|
|
1568
|
+
blur: 0,
|
|
1569
|
+
blend: "source-over",
|
|
1570
|
+
seed: 17,
|
|
1571
|
+
interactive: false,
|
|
1572
|
+
resolution: 1,
|
|
1573
|
+
performance: "auto",
|
|
1574
|
+
reducedMotion: "pause",
|
|
1575
|
+
paused: false,
|
|
1576
|
+
pauseOffscreen: true,
|
|
1577
|
+
count: 320,
|
|
1578
|
+
angle: 96,
|
|
1579
|
+
wind: 0,
|
|
1580
|
+
velocity: 900,
|
|
1581
|
+
depthRange: [1, 0.35],
|
|
1582
|
+
length: 18,
|
|
1583
|
+
thickness: 1.2,
|
|
1584
|
+
splash: false
|
|
1585
|
+
};
|
|
1586
|
+
var rain = defineEffect({
|
|
1587
|
+
name: "rain",
|
|
1588
|
+
defaults: RAIN_DEFAULTS,
|
|
1589
|
+
structuralOptions: ["count"],
|
|
1590
|
+
setup(context, options) {
|
|
1591
|
+
let opts = options;
|
|
1592
|
+
const capacity = Math.min(2e3, Math.max(1, Math.floor(opts.count)));
|
|
1593
|
+
const buffer = new ParticleBuffer(capacity);
|
|
1594
|
+
let seeded = false;
|
|
1595
|
+
const splashes = [];
|
|
1596
|
+
function seedOne(index, initial) {
|
|
1597
|
+
const { width, height } = context.size;
|
|
1598
|
+
const rng = context.rng;
|
|
1599
|
+
const [near, far] = opts.depthRange;
|
|
1600
|
+
const depth = rng.range(Math.min(near, far), Math.max(near, far));
|
|
1601
|
+
buffer.seed[index] = depth;
|
|
1602
|
+
buffer.size[index] = opts.thickness * depth;
|
|
1603
|
+
buffer.extra[index] = opts.length * depth;
|
|
1604
|
+
buffer.colorIndex[index] = rng.int(0, Math.max(0, context.colors.length - 1));
|
|
1605
|
+
buffer.life[index] = Infinity;
|
|
1606
|
+
buffer.maxLife[index] = Infinity;
|
|
1607
|
+
buffer.x[index] = rng.range(-width * 0.2, width * 1.2);
|
|
1608
|
+
buffer.y[index] = initial ? rng.range(-height, height) : rng.range(-height * 0.3, -10);
|
|
1609
|
+
}
|
|
1610
|
+
function seed() {
|
|
1611
|
+
buffer.clear();
|
|
1612
|
+
for (let i = 0; i < capacity; i++) {
|
|
1613
|
+
const index = buffer.spawn();
|
|
1614
|
+
if (index < 0) break;
|
|
1615
|
+
seedOne(index, true);
|
|
1616
|
+
}
|
|
1617
|
+
seeded = true;
|
|
1618
|
+
}
|
|
1619
|
+
return {
|
|
1620
|
+
frame({ delta, quality }) {
|
|
1621
|
+
const { ctx, size, surface, colors } = context;
|
|
1622
|
+
const { width, height } = size;
|
|
1623
|
+
if (!seeded || buffer.count === 0 && width > 1) seed();
|
|
1624
|
+
const target = Math.max(
|
|
1625
|
+
8,
|
|
1626
|
+
Math.floor(capacity * (opts.performance === "high" ? 1 : quality))
|
|
1627
|
+
);
|
|
1628
|
+
if (buffer.count > target) buffer.truncate(target);
|
|
1629
|
+
surface.clear();
|
|
1630
|
+
const angle = chunkILKUL7GD_cjs.radians(opts.angle + opts.wind);
|
|
1631
|
+
const dirX = Math.cos(angle);
|
|
1632
|
+
const dirY = Math.sin(angle);
|
|
1633
|
+
const baseVelocity = opts.velocity * opts.intensity;
|
|
1634
|
+
const count = buffer.count;
|
|
1635
|
+
ctx.globalCompositeOperation = opts.blend ?? "source-over";
|
|
1636
|
+
ctx.lineCap = "butt";
|
|
1637
|
+
const colorCount = Math.max(1, colors.length);
|
|
1638
|
+
for (let i = 0; i < count; i++) {
|
|
1639
|
+
const depth = buffer.seed[i];
|
|
1640
|
+
const velocity = baseVelocity * depth;
|
|
1641
|
+
buffer.x[i] += dirX * velocity * delta;
|
|
1642
|
+
buffer.y[i] += dirY * velocity * delta;
|
|
1643
|
+
const trailLength = buffer.extra[i];
|
|
1644
|
+
if (buffer.y[i] - trailLength > height) {
|
|
1645
|
+
if (opts.splash && splashes.length < 24) {
|
|
1646
|
+
splashes.push({ x: buffer.x[i], y: height - 1, age: 0 });
|
|
1647
|
+
}
|
|
1648
|
+
seedOne(i, false);
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (buffer.x[i] < -width * 0.25 || buffer.x[i] > width * 1.25) {
|
|
1652
|
+
seedOne(i, false);
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
const color = colors[buffer.colorIndex[i] % colorCount];
|
|
1656
|
+
ctx.strokeStyle = `rgba(${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)},${chunkILKUL7GD_cjs.clamp(color.a * depth, 0, 1).toFixed(3)})`;
|
|
1657
|
+
ctx.lineWidth = buffer.size[i];
|
|
1658
|
+
ctx.beginPath();
|
|
1659
|
+
ctx.moveTo(buffer.x[i], buffer.y[i]);
|
|
1660
|
+
ctx.lineTo(buffer.x[i] - dirX * trailLength, buffer.y[i] - dirY * trailLength);
|
|
1661
|
+
ctx.stroke();
|
|
1662
|
+
}
|
|
1663
|
+
if (opts.splash && splashes.length > 0) {
|
|
1664
|
+
const SPLASH_DURATION = 0.45;
|
|
1665
|
+
const color = colors[0];
|
|
1666
|
+
const rgb = `${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)}`;
|
|
1667
|
+
for (let s = splashes.length - 1; s >= 0; s--) {
|
|
1668
|
+
const splash = splashes[s];
|
|
1669
|
+
splash.age += delta;
|
|
1670
|
+
if (splash.age >= SPLASH_DURATION) {
|
|
1671
|
+
splashes.splice(s, 1);
|
|
1672
|
+
continue;
|
|
1673
|
+
}
|
|
1674
|
+
const t = splash.age / SPLASH_DURATION;
|
|
1675
|
+
const radius = 2 + t * 14;
|
|
1676
|
+
ctx.strokeStyle = `rgba(${rgb},${(1 - t) * 0.4})`;
|
|
1677
|
+
ctx.lineWidth = 1;
|
|
1678
|
+
ctx.beginPath();
|
|
1679
|
+
ctx.ellipse(splash.x, splash.y, radius, radius * 0.35, 0, 0, chunkILKUL7GD_cjs.TAU);
|
|
1680
|
+
ctx.stroke();
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1684
|
+
},
|
|
1685
|
+
resize() {
|
|
1686
|
+
seeded = false;
|
|
1687
|
+
},
|
|
1688
|
+
update(next) {
|
|
1689
|
+
opts = { ...opts, ...next };
|
|
1690
|
+
},
|
|
1691
|
+
destroy() {
|
|
1692
|
+
buffer.clear();
|
|
1693
|
+
splashes.length = 0;
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
var SNOW_DEFAULTS = {
|
|
1699
|
+
colors: ["#ffffff", "#e8f1ff"],
|
|
1700
|
+
speed: 1,
|
|
1701
|
+
intensity: 1,
|
|
1702
|
+
opacity: 0.9,
|
|
1703
|
+
blur: 0,
|
|
1704
|
+
blend: "source-over",
|
|
1705
|
+
seed: 23,
|
|
1706
|
+
interactive: false,
|
|
1707
|
+
resolution: 1,
|
|
1708
|
+
performance: "auto",
|
|
1709
|
+
reducedMotion: "pause",
|
|
1710
|
+
paused: false,
|
|
1711
|
+
pauseOffscreen: true,
|
|
1712
|
+
count: 220,
|
|
1713
|
+
angle: 92,
|
|
1714
|
+
wind: 0,
|
|
1715
|
+
velocity: 70,
|
|
1716
|
+
depthRange: [1, 0.3],
|
|
1717
|
+
size: 2.6,
|
|
1718
|
+
sway: 28,
|
|
1719
|
+
swaySpeed: 0.35,
|
|
1720
|
+
soft: true
|
|
1721
|
+
};
|
|
1722
|
+
var snow = defineEffect({
|
|
1723
|
+
name: "snow",
|
|
1724
|
+
defaults: SNOW_DEFAULTS,
|
|
1725
|
+
structuralOptions: ["count"],
|
|
1726
|
+
setup(context, options) {
|
|
1727
|
+
let opts = options;
|
|
1728
|
+
const capacity = Math.min(1500, Math.max(1, Math.floor(opts.count)));
|
|
1729
|
+
const buffer = new ParticleBuffer(capacity);
|
|
1730
|
+
let seeded = false;
|
|
1731
|
+
let order = [];
|
|
1732
|
+
function seedOne(index, initial) {
|
|
1733
|
+
const { width, height } = context.size;
|
|
1734
|
+
const rng = context.rng;
|
|
1735
|
+
const [near, far] = opts.depthRange;
|
|
1736
|
+
const depth = rng.range(Math.min(near, far), Math.max(near, far));
|
|
1737
|
+
buffer.seed[index] = depth;
|
|
1738
|
+
buffer.size[index] = opts.size * depth;
|
|
1739
|
+
buffer.extra[index] = rng.range(0, chunkILKUL7GD_cjs.TAU);
|
|
1740
|
+
buffer.colorIndex[index] = rng.int(0, Math.max(0, context.colors.length - 1));
|
|
1741
|
+
buffer.life[index] = Infinity;
|
|
1742
|
+
buffer.maxLife[index] = Infinity;
|
|
1743
|
+
buffer.vx[index] = rng.range(0.6, 1.5);
|
|
1744
|
+
buffer.x[index] = rng.range(-width * 0.15, width * 1.15);
|
|
1745
|
+
buffer.y[index] = initial ? rng.range(-height, height) : rng.range(-height * 0.25, -8);
|
|
1746
|
+
}
|
|
1747
|
+
function seed() {
|
|
1748
|
+
buffer.clear();
|
|
1749
|
+
for (let i = 0; i < capacity; i++) {
|
|
1750
|
+
const index = buffer.spawn();
|
|
1751
|
+
if (index < 0) break;
|
|
1752
|
+
seedOne(index, true);
|
|
1753
|
+
}
|
|
1754
|
+
order = Array.from({ length: buffer.count }, (_, i) => i).sort(
|
|
1755
|
+
(a, b) => buffer.seed[a] - buffer.seed[b]
|
|
1756
|
+
);
|
|
1757
|
+
seeded = true;
|
|
1758
|
+
}
|
|
1759
|
+
return {
|
|
1760
|
+
frame({ time, delta, quality }) {
|
|
1761
|
+
const { ctx, size, surface, colors } = context;
|
|
1762
|
+
const { width, height } = size;
|
|
1763
|
+
if (!seeded || buffer.count === 0 && width > 1) seed();
|
|
1764
|
+
const target = Math.max(
|
|
1765
|
+
8,
|
|
1766
|
+
Math.floor(capacity * (opts.performance === "high" ? 1 : quality))
|
|
1767
|
+
);
|
|
1768
|
+
if (buffer.count > target) {
|
|
1769
|
+
buffer.truncate(target);
|
|
1770
|
+
order = order.filter((i) => i < buffer.count);
|
|
1771
|
+
}
|
|
1772
|
+
surface.clear();
|
|
1773
|
+
const angle = chunkILKUL7GD_cjs.radians(opts.angle + opts.wind);
|
|
1774
|
+
const dirX = Math.cos(angle);
|
|
1775
|
+
const dirY = Math.sin(angle);
|
|
1776
|
+
const baseVelocity = opts.velocity * opts.intensity;
|
|
1777
|
+
const sway = opts.sway;
|
|
1778
|
+
const swaySpeed = opts.swaySpeed;
|
|
1779
|
+
ctx.globalCompositeOperation = opts.blend ?? "source-over";
|
|
1780
|
+
const colorCount = Math.max(1, colors.length);
|
|
1781
|
+
for (const i of order) {
|
|
1782
|
+
if (i >= buffer.count) continue;
|
|
1783
|
+
const depth = buffer.seed[i];
|
|
1784
|
+
const velocity = baseVelocity * depth;
|
|
1785
|
+
buffer.y[i] += dirY * velocity * delta;
|
|
1786
|
+
const swayPhase = time * swaySpeed * chunkILKUL7GD_cjs.TAU * buffer.vx[i] + buffer.extra[i];
|
|
1787
|
+
buffer.x[i] += (dirX * velocity + Math.cos(swayPhase) * sway * depth) * delta;
|
|
1788
|
+
const radius = buffer.size[i];
|
|
1789
|
+
if (buffer.y[i] - radius > height) {
|
|
1790
|
+
seedOne(i, false);
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
if (buffer.x[i] < -width * 0.2 || buffer.x[i] > width * 1.2) {
|
|
1794
|
+
seedOne(i, false);
|
|
1795
|
+
continue;
|
|
1796
|
+
}
|
|
1797
|
+
const color = colors[buffer.colorIndex[i] % colorCount];
|
|
1798
|
+
const alpha = chunkILKUL7GD_cjs.clamp(color.a * depth, 0, 1);
|
|
1799
|
+
const rgb = `${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)}`;
|
|
1800
|
+
if (opts.soft) {
|
|
1801
|
+
const gradient = ctx.createRadialGradient(
|
|
1802
|
+
buffer.x[i],
|
|
1803
|
+
buffer.y[i],
|
|
1804
|
+
0,
|
|
1805
|
+
buffer.x[i],
|
|
1806
|
+
buffer.y[i],
|
|
1807
|
+
radius * 2
|
|
1808
|
+
);
|
|
1809
|
+
gradient.addColorStop(0, `rgba(${rgb},${alpha})`);
|
|
1810
|
+
gradient.addColorStop(0.5, `rgba(${rgb},${alpha * 0.5})`);
|
|
1811
|
+
gradient.addColorStop(1, `rgba(${rgb},0)`);
|
|
1812
|
+
ctx.fillStyle = gradient;
|
|
1813
|
+
ctx.fillRect(buffer.x[i] - radius * 2, buffer.y[i] - radius * 2, radius * 4, radius * 4);
|
|
1814
|
+
} else {
|
|
1815
|
+
ctx.fillStyle = `rgba(${rgb},${alpha})`;
|
|
1816
|
+
ctx.beginPath();
|
|
1817
|
+
ctx.arc(buffer.x[i], buffer.y[i], radius, 0, chunkILKUL7GD_cjs.TAU);
|
|
1818
|
+
ctx.fill();
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1822
|
+
},
|
|
1823
|
+
resize() {
|
|
1824
|
+
seeded = false;
|
|
1825
|
+
},
|
|
1826
|
+
update(next) {
|
|
1827
|
+
opts = { ...opts, ...next };
|
|
1828
|
+
},
|
|
1829
|
+
destroy() {
|
|
1830
|
+
buffer.clear();
|
|
1831
|
+
order = [];
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
// src/effects/atmosphere/fireflies.ts
|
|
1838
|
+
var DEFAULTS6 = {
|
|
1839
|
+
colors: ["#ffe98a", "#c8ff8a", "#8affd4"],
|
|
1840
|
+
speed: 1,
|
|
1841
|
+
intensity: 1,
|
|
1842
|
+
opacity: 1,
|
|
1843
|
+
blur: 0,
|
|
1844
|
+
blend: "lighter",
|
|
1845
|
+
seed: 29,
|
|
1846
|
+
interactive: true,
|
|
1847
|
+
resolution: 1,
|
|
1848
|
+
performance: "auto",
|
|
1849
|
+
reducedMotion: "slow",
|
|
1850
|
+
paused: false,
|
|
1851
|
+
pauseOffscreen: true,
|
|
1852
|
+
count: 40,
|
|
1853
|
+
size: 5,
|
|
1854
|
+
wanderSpeed: 22,
|
|
1855
|
+
turbulence: 0.35,
|
|
1856
|
+
pulseRate: 0.5,
|
|
1857
|
+
baseGlow: 0.12,
|
|
1858
|
+
pointerRadius: 120
|
|
1859
|
+
};
|
|
1860
|
+
var fireflies = defineEffect({
|
|
1861
|
+
name: "fireflies",
|
|
1862
|
+
defaults: DEFAULTS6,
|
|
1863
|
+
structuralOptions: ["count"],
|
|
1864
|
+
setup(context, options) {
|
|
1865
|
+
let opts = options;
|
|
1866
|
+
const capacity = Math.min(400, Math.max(1, Math.floor(opts.count)));
|
|
1867
|
+
const buffer = new ParticleBuffer(capacity);
|
|
1868
|
+
const gradients = new GradientCache(48);
|
|
1869
|
+
const heading = context.noise.fbm2D({ octaves: 2, gain: 0.5 });
|
|
1870
|
+
let seeded = false;
|
|
1871
|
+
function seed() {
|
|
1872
|
+
const { width, height } = context.size;
|
|
1873
|
+
const rng = context.rng;
|
|
1874
|
+
const colorCount = Math.max(1, context.colors.length);
|
|
1875
|
+
buffer.clear();
|
|
1876
|
+
for (let i = 0; i < capacity; i++) {
|
|
1877
|
+
const index = buffer.spawn();
|
|
1878
|
+
if (index < 0) break;
|
|
1879
|
+
buffer.x[index] = rng.next() * width;
|
|
1880
|
+
buffer.y[index] = rng.next() * height;
|
|
1881
|
+
buffer.size[index] = opts.size * rng.range(0.6, 1.5);
|
|
1882
|
+
buffer.colorIndex[index] = rng.int(0, colorCount - 1);
|
|
1883
|
+
buffer.life[index] = Infinity;
|
|
1884
|
+
buffer.maxLife[index] = Infinity;
|
|
1885
|
+
buffer.seed[index] = rng.range(0, chunkILKUL7GD_cjs.TAU);
|
|
1886
|
+
buffer.extra[index] = rng.range(0.6, 1.6);
|
|
1887
|
+
buffer.vx[index] = rng.range(0, 1e3);
|
|
1888
|
+
buffer.vy[index] = rng.range(0, 1e3);
|
|
1889
|
+
}
|
|
1890
|
+
seeded = true;
|
|
1891
|
+
}
|
|
1892
|
+
return {
|
|
1893
|
+
frame({ time, delta, quality }) {
|
|
1894
|
+
const { ctx, size, pointer, surface, colors } = context;
|
|
1895
|
+
const { width, height } = size;
|
|
1896
|
+
if (!seeded || buffer.count === 0 && width > 1) seed();
|
|
1897
|
+
const target = Math.max(
|
|
1898
|
+
4,
|
|
1899
|
+
Math.floor(capacity * (opts.performance === "high" ? 1 : quality))
|
|
1900
|
+
);
|
|
1901
|
+
if (buffer.count > target) buffer.truncate(target);
|
|
1902
|
+
surface.clear();
|
|
1903
|
+
const wanderSpeed = opts.wanderSpeed * opts.intensity;
|
|
1904
|
+
const turbulence = opts.turbulence;
|
|
1905
|
+
const baseGlow = chunkILKUL7GD_cjs.clamp(opts.baseGlow, 0, 1);
|
|
1906
|
+
const pulseRate = opts.pulseRate;
|
|
1907
|
+
const count = buffer.count;
|
|
1908
|
+
const interactive = opts.interactive !== false && pointer !== null && pointer.everInside;
|
|
1909
|
+
const px = interactive ? pointer.smoothX : 0;
|
|
1910
|
+
const py = interactive ? pointer.smoothY : 0;
|
|
1911
|
+
const scatterRadius = Math.max(1, opts.pointerRadius);
|
|
1912
|
+
ctx.globalCompositeOperation = opts.blend ?? "lighter";
|
|
1913
|
+
const colorCount = Math.max(1, colors.length);
|
|
1914
|
+
for (let i = 0; i < count; i++) {
|
|
1915
|
+
const n = heading(buffer.vx[i] * 0.01, buffer.vy[i] * 0.01 + time * turbulence);
|
|
1916
|
+
const angle = n * chunkILKUL7GD_cjs.TAU * 2;
|
|
1917
|
+
let dx = Math.cos(angle) * wanderSpeed;
|
|
1918
|
+
let dy = Math.sin(angle) * wanderSpeed;
|
|
1919
|
+
if (interactive) {
|
|
1920
|
+
const ox = buffer.x[i] - px;
|
|
1921
|
+
const oy = buffer.y[i] - py;
|
|
1922
|
+
const distance = Math.hypot(ox, oy);
|
|
1923
|
+
if (distance < scatterRadius && distance > 1e-3) {
|
|
1924
|
+
const falloff = 1 - distance / scatterRadius;
|
|
1925
|
+
const push = falloff * falloff * falloff * wanderSpeed * 6;
|
|
1926
|
+
dx += ox / distance * push;
|
|
1927
|
+
dy += oy / distance * push;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
buffer.x[i] += dx * delta;
|
|
1931
|
+
buffer.y[i] += dy * delta;
|
|
1932
|
+
const margin = buffer.size[i] * 3;
|
|
1933
|
+
if (buffer.x[i] < margin) buffer.x[i] += (margin - buffer.x[i]) * 0.08;
|
|
1934
|
+
else if (buffer.x[i] > width - margin)
|
|
1935
|
+
buffer.x[i] -= (buffer.x[i] - (width - margin)) * 0.08;
|
|
1936
|
+
if (buffer.y[i] < margin) buffer.y[i] += (margin - buffer.y[i]) * 0.08;
|
|
1937
|
+
else if (buffer.y[i] > height - margin)
|
|
1938
|
+
buffer.y[i] -= (buffer.y[i] - (height - margin)) * 0.08;
|
|
1939
|
+
const raw = (Math.sin(time * pulseRate * chunkILKUL7GD_cjs.TAU * buffer.extra[i] + buffer.seed[i]) + 1) / 2;
|
|
1940
|
+
const pulse = baseGlow + (1 - baseGlow) * raw ** 2.6;
|
|
1941
|
+
const color = colors[buffer.colorIndex[i] % colorCount];
|
|
1942
|
+
drawGlow(
|
|
1943
|
+
ctx,
|
|
1944
|
+
buffer.x[i],
|
|
1945
|
+
buffer.y[i],
|
|
1946
|
+
buffer.size[i] * 3.2,
|
|
1947
|
+
color,
|
|
1948
|
+
chunkILKUL7GD_cjs.clamp(pulse * opts.intensity, 0, 1),
|
|
1949
|
+
gradients
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1953
|
+
},
|
|
1954
|
+
resize() {
|
|
1955
|
+
seeded = false;
|
|
1956
|
+
},
|
|
1957
|
+
update(next) {
|
|
1958
|
+
opts = { ...opts, ...next };
|
|
1959
|
+
gradients.clear();
|
|
1960
|
+
},
|
|
1961
|
+
destroy() {
|
|
1962
|
+
buffer.clear();
|
|
1963
|
+
gradients.clear();
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
});
|
|
1968
|
+
|
|
1969
|
+
// src/effects/surfaces/waves.ts
|
|
1970
|
+
var DEFAULTS7 = {
|
|
1971
|
+
colors: "abyss",
|
|
1972
|
+
speed: 1,
|
|
1973
|
+
intensity: 1,
|
|
1974
|
+
opacity: 1,
|
|
1975
|
+
blur: 0,
|
|
1976
|
+
blend: "source-over",
|
|
1977
|
+
seed: 31,
|
|
1978
|
+
interactive: false,
|
|
1979
|
+
resolution: 1,
|
|
1980
|
+
performance: "auto",
|
|
1981
|
+
reducedMotion: "static",
|
|
1982
|
+
paused: false,
|
|
1983
|
+
pauseOffscreen: true,
|
|
1984
|
+
layers: 4,
|
|
1985
|
+
samples: 28,
|
|
1986
|
+
amplitude: 40,
|
|
1987
|
+
wavelength: 0.6,
|
|
1988
|
+
position: 0.68,
|
|
1989
|
+
roughness: 0.35,
|
|
1990
|
+
filled: true,
|
|
1991
|
+
lineWidth: 0
|
|
1992
|
+
};
|
|
1993
|
+
var waves = defineEffect({
|
|
1994
|
+
name: "waves",
|
|
1995
|
+
defaults: DEFAULTS7,
|
|
1996
|
+
structuralOptions: ["layers"],
|
|
1997
|
+
setup(context, options) {
|
|
1998
|
+
let opts = options;
|
|
1999
|
+
let layers = [];
|
|
2000
|
+
const profile = context.noise.fbm2D({ octaves: 3, gain: 0.5 });
|
|
2001
|
+
let crest = [];
|
|
2002
|
+
function build() {
|
|
2003
|
+
const count = Math.max(1, Math.floor(opts.layers));
|
|
2004
|
+
const palette = chunk23GSSIMK_cjs.expandPalette(context.colors, count);
|
|
2005
|
+
const rng = context.rng;
|
|
2006
|
+
layers = [];
|
|
2007
|
+
for (let i = 0; i < count; i++) {
|
|
2008
|
+
const t = count === 1 ? 1 : i / (count - 1);
|
|
2009
|
+
layers.push({
|
|
2010
|
+
color: palette[i],
|
|
2011
|
+
f1: rng.range(0.8, 1.2),
|
|
2012
|
+
f2: rng.range(1.7, 2.4),
|
|
2013
|
+
f3: rng.range(3.1, 4.3),
|
|
2014
|
+
phase: rng.range(0, chunkILKUL7GD_cjs.TAU),
|
|
2015
|
+
speed: rng.range(0.5, 1.1),
|
|
2016
|
+
// Back layers are taller: they need to show above the front ones to read as distance.
|
|
2017
|
+
amplitude: chunkILKUL7GD_cjs.lerp(1.35, 0.7, t),
|
|
2018
|
+
offset: chunkILKUL7GD_cjs.lerp(-0.1, 0.06, t),
|
|
2019
|
+
alpha: chunkILKUL7GD_cjs.lerp(0.5, 0.95, t),
|
|
2020
|
+
noiseOffset: rng.range(0, 100)
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
function ensureCrest(sampleCount) {
|
|
2025
|
+
if (crest.length === sampleCount + 1) return;
|
|
2026
|
+
crest = Array.from({ length: sampleCount + 1 }, () => ({ x: 0, y: 0 }));
|
|
2027
|
+
}
|
|
2028
|
+
build();
|
|
2029
|
+
return {
|
|
2030
|
+
frame({ time }) {
|
|
2031
|
+
const { ctx, size, surface } = context;
|
|
2032
|
+
const { width, height } = size;
|
|
2033
|
+
surface.clear();
|
|
2034
|
+
const sampleCount = Math.max(4, Math.floor(opts.samples));
|
|
2035
|
+
ensureCrest(sampleCount);
|
|
2036
|
+
const amplitude = opts.amplitude * opts.intensity;
|
|
2037
|
+
const baseY = height * chunkILKUL7GD_cjs.clamp(opts.position, 0, 1);
|
|
2038
|
+
const wavelength = Math.max(0.05, opts.wavelength);
|
|
2039
|
+
const roughness = chunkILKUL7GD_cjs.clamp(opts.roughness, 0, 1);
|
|
2040
|
+
ctx.globalCompositeOperation = opts.blend ?? "source-over";
|
|
2041
|
+
for (const layer of layers) {
|
|
2042
|
+
const t = time * layer.speed;
|
|
2043
|
+
const layerAmplitude = amplitude * layer.amplitude;
|
|
2044
|
+
const layerY = baseY + height * layer.offset;
|
|
2045
|
+
for (let s = 0; s <= sampleCount; s++) {
|
|
2046
|
+
const u = s / sampleCount;
|
|
2047
|
+
const p = u / wavelength * chunkILKUL7GD_cjs.TAU + layer.phase;
|
|
2048
|
+
const sines = Math.sin(p * layer.f1 + t * 0.9) * 0.5 + Math.sin(p * layer.f2 - t * 0.6) * 0.32 + Math.sin(p * layer.f3 + t * 1.4) * 0.18;
|
|
2049
|
+
const noise = roughness > 0 ? profile(u * 3 + layer.noiseOffset, t * 0.25) : 0;
|
|
2050
|
+
const y = layerY + (sines * (1 - roughness) + noise * roughness) * layerAmplitude;
|
|
2051
|
+
crest[s].x = u * width;
|
|
2052
|
+
crest[s].y = y;
|
|
2053
|
+
}
|
|
2054
|
+
const rgb = `${Math.round(layer.color.r * 255)},${Math.round(layer.color.g * 255)},${Math.round(layer.color.b * 255)}`;
|
|
2055
|
+
if (opts.filled) {
|
|
2056
|
+
ctx.beginPath();
|
|
2057
|
+
traceOpenSpline(ctx, crest, 0.5);
|
|
2058
|
+
ctx.lineTo(width, height + layerAmplitude * 2);
|
|
2059
|
+
ctx.lineTo(0, height + layerAmplitude * 2);
|
|
2060
|
+
ctx.closePath();
|
|
2061
|
+
const gradient = ctx.createLinearGradient(0, layerY - layerAmplitude, 0, height);
|
|
2062
|
+
gradient.addColorStop(0, `rgba(${rgb},${chunkILKUL7GD_cjs.clamp(layer.alpha, 0, 1)})`);
|
|
2063
|
+
gradient.addColorStop(1, `rgba(${rgb},${chunkILKUL7GD_cjs.clamp(layer.alpha * 0.82, 0, 1)})`);
|
|
2064
|
+
ctx.fillStyle = gradient;
|
|
2065
|
+
ctx.fill();
|
|
2066
|
+
}
|
|
2067
|
+
if (opts.lineWidth > 0) {
|
|
2068
|
+
ctx.beginPath();
|
|
2069
|
+
traceOpenSpline(ctx, crest, 0.5);
|
|
2070
|
+
ctx.strokeStyle = `rgba(255,255,255,${chunkILKUL7GD_cjs.clamp(layer.alpha * 0.25, 0, 1)})`;
|
|
2071
|
+
ctx.lineWidth = opts.lineWidth;
|
|
2072
|
+
ctx.stroke();
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2076
|
+
},
|
|
2077
|
+
update(next) {
|
|
2078
|
+
opts = { ...opts, ...next };
|
|
2079
|
+
if ("colors" in next) build();
|
|
2080
|
+
},
|
|
2081
|
+
destroy() {
|
|
2082
|
+
layers = [];
|
|
2083
|
+
crest = [];
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
|
|
2089
|
+
// src/effects/surfaces/blob.ts
|
|
2090
|
+
var DEFAULTS8 = {
|
|
2091
|
+
colors: "ansh",
|
|
2092
|
+
speed: 1,
|
|
2093
|
+
intensity: 1,
|
|
2094
|
+
opacity: 1,
|
|
2095
|
+
blur: 30,
|
|
2096
|
+
blend: "lighter",
|
|
2097
|
+
seed: 37,
|
|
2098
|
+
interactive: true,
|
|
2099
|
+
resolution: 0.6,
|
|
2100
|
+
performance: "auto",
|
|
2101
|
+
reducedMotion: "static",
|
|
2102
|
+
paused: false,
|
|
2103
|
+
pauseOffscreen: true,
|
|
2104
|
+
count: 3,
|
|
2105
|
+
segments: 48,
|
|
2106
|
+
radius: 0.32,
|
|
2107
|
+
distortion: 0.35,
|
|
2108
|
+
detail: 1.6,
|
|
2109
|
+
morphSpeed: 0.4,
|
|
2110
|
+
drift: 0.1,
|
|
2111
|
+
outline: false,
|
|
2112
|
+
lineWidth: 2,
|
|
2113
|
+
pointerInfluence: 0.25
|
|
2114
|
+
};
|
|
2115
|
+
var blob = defineEffect({
|
|
2116
|
+
name: "blob",
|
|
2117
|
+
defaults: DEFAULTS8,
|
|
2118
|
+
structuralOptions: ["count", "segments"],
|
|
2119
|
+
setup(context, options) {
|
|
2120
|
+
let opts = options;
|
|
2121
|
+
let blobs = [];
|
|
2122
|
+
const field = context.noise.fbm3D({ octaves: 3, gain: 0.5 });
|
|
2123
|
+
let ring = [];
|
|
2124
|
+
function build() {
|
|
2125
|
+
const count = Math.max(1, Math.floor(opts.count));
|
|
2126
|
+
const palette = chunk23GSSIMK_cjs.expandPalette(context.colors, count);
|
|
2127
|
+
const rng = context.rng;
|
|
2128
|
+
blobs = [];
|
|
2129
|
+
for (let i = 0; i < count; i++) {
|
|
2130
|
+
const angle = i / count * chunkILKUL7GD_cjs.TAU + rng.range(-0.5, 0.5);
|
|
2131
|
+
const distance = rng.range(0.1, 0.3);
|
|
2132
|
+
blobs.push({
|
|
2133
|
+
color: palette[i],
|
|
2134
|
+
homeX: 0.5 + Math.cos(angle) * distance,
|
|
2135
|
+
homeY: 0.5 + Math.sin(angle) * distance,
|
|
2136
|
+
radiusScale: rng.range(0.7, 1.25),
|
|
2137
|
+
noiseOffset: rng.range(0, 60),
|
|
2138
|
+
driftFreqX: rng.range(0.07, 0.16),
|
|
2139
|
+
driftFreqY: rng.range(0.08, 0.18),
|
|
2140
|
+
phase: rng.range(0, chunkILKUL7GD_cjs.TAU),
|
|
2141
|
+
alpha: chunkILKUL7GD_cjs.lerp(0.85, 0.5, count === 1 ? 0 : i / (count - 1))
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
function ensureRing(pointCount) {
|
|
2146
|
+
if (ring.length === pointCount) return;
|
|
2147
|
+
ring = Array.from({ length: pointCount }, () => ({ x: 0, y: 0 }));
|
|
2148
|
+
}
|
|
2149
|
+
build();
|
|
2150
|
+
return {
|
|
2151
|
+
frame({ time }) {
|
|
2152
|
+
const { ctx, size, pointer, surface } = context;
|
|
2153
|
+
const { width, height } = size;
|
|
2154
|
+
surface.clear();
|
|
2155
|
+
const pointCount = Math.max(8, Math.floor(opts.segments));
|
|
2156
|
+
ensureRing(pointCount);
|
|
2157
|
+
const minDimension = Math.min(width, height);
|
|
2158
|
+
const baseRadius = minDimension * chunkILKUL7GD_cjs.clamp(opts.radius, 0.02, 1);
|
|
2159
|
+
const distortion = chunkILKUL7GD_cjs.clamp(opts.distortion, 0, 1) * opts.intensity;
|
|
2160
|
+
const detail = Math.max(0.1, opts.detail);
|
|
2161
|
+
const morph = time * opts.morphSpeed;
|
|
2162
|
+
const drift = opts.drift;
|
|
2163
|
+
const interactive = opts.interactive !== false && pointer !== null && pointer.everInside;
|
|
2164
|
+
const pointerX = interactive ? pointer.smoothNx : 0.5;
|
|
2165
|
+
const pointerY = interactive ? pointer.smoothNy : 0.5;
|
|
2166
|
+
const pull = interactive ? chunkILKUL7GD_cjs.clamp(opts.pointerInfluence, 0, 1) : 0;
|
|
2167
|
+
ctx.globalCompositeOperation = opts.blend ?? "lighter";
|
|
2168
|
+
for (const b of blobs) {
|
|
2169
|
+
let nx = b.homeX + Math.sin(time * b.driftFreqX + b.phase) * drift;
|
|
2170
|
+
let ny = b.homeY + Math.cos(time * b.driftFreqY + b.phase * 1.4) * drift;
|
|
2171
|
+
if (pull > 0) {
|
|
2172
|
+
nx += (pointerX - nx) * pull * 0.5;
|
|
2173
|
+
ny += (pointerY - ny) * pull * 0.5;
|
|
2174
|
+
}
|
|
2175
|
+
const cx = nx * width;
|
|
2176
|
+
const cy = ny * height;
|
|
2177
|
+
const radius = baseRadius * b.radiusScale;
|
|
2178
|
+
for (let i = 0; i < pointCount; i++) {
|
|
2179
|
+
const angle = i / pointCount * chunkILKUL7GD_cjs.TAU;
|
|
2180
|
+
const sampleX = Math.cos(angle) * detail + b.noiseOffset;
|
|
2181
|
+
const sampleY = Math.sin(angle) * detail + b.noiseOffset;
|
|
2182
|
+
const n = field(sampleX, sampleY, morph);
|
|
2183
|
+
const r = radius * (1 + n * distortion);
|
|
2184
|
+
ring[i].x = cx + Math.cos(angle) * r;
|
|
2185
|
+
ring[i].y = cy + Math.sin(angle) * r;
|
|
2186
|
+
}
|
|
2187
|
+
ctx.beginPath();
|
|
2188
|
+
traceClosedSpline(ctx, ring, 0.5);
|
|
2189
|
+
const rgb = `${Math.round(b.color.r * 255)},${Math.round(b.color.g * 255)},${Math.round(b.color.b * 255)}`;
|
|
2190
|
+
if (opts.outline) {
|
|
2191
|
+
ctx.strokeStyle = `rgba(${rgb},${chunkILKUL7GD_cjs.clamp(b.alpha * b.color.a, 0, 1)})`;
|
|
2192
|
+
ctx.lineWidth = opts.lineWidth;
|
|
2193
|
+
ctx.stroke();
|
|
2194
|
+
} else {
|
|
2195
|
+
const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius * 1.4);
|
|
2196
|
+
const peak = chunkILKUL7GD_cjs.clamp(b.alpha * b.color.a * opts.intensity, 0, 1);
|
|
2197
|
+
gradient.addColorStop(0, `rgba(${rgb},${peak})`);
|
|
2198
|
+
gradient.addColorStop(0.55, `rgba(${rgb},${peak * 0.72})`);
|
|
2199
|
+
gradient.addColorStop(1, `rgba(${rgb},${peak * 0.25})`);
|
|
2200
|
+
ctx.fillStyle = gradient;
|
|
2201
|
+
ctx.fill();
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2205
|
+
},
|
|
2206
|
+
update(next) {
|
|
2207
|
+
opts = { ...opts, ...next };
|
|
2208
|
+
if ("colors" in next) build();
|
|
2209
|
+
},
|
|
2210
|
+
destroy() {
|
|
2211
|
+
blobs = [];
|
|
2212
|
+
ring = [];
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
// src/effects/overlays/noise-layer.ts
|
|
2219
|
+
var DEFAULTS9 = {
|
|
2220
|
+
colors: ["#ffffff"],
|
|
2221
|
+
speed: 1,
|
|
2222
|
+
intensity: 1,
|
|
2223
|
+
opacity: 1,
|
|
2224
|
+
blur: 0,
|
|
2225
|
+
// `overlay` preserves the underlying luminance while adding texture, which is what grain
|
|
2226
|
+
// should do. `source-over` would just wash the image out with grey.
|
|
2227
|
+
blend: "overlay",
|
|
2228
|
+
seed: 41,
|
|
2229
|
+
interactive: false,
|
|
2230
|
+
resolution: 1,
|
|
2231
|
+
performance: "auto",
|
|
2232
|
+
// Grain has no directional motion, so it causes no vestibular discomfort. But flickering can
|
|
2233
|
+
// trigger photosensitivity, so under reduced motion we hold a single static tile.
|
|
2234
|
+
reducedMotion: "static",
|
|
2235
|
+
paused: false,
|
|
2236
|
+
pauseOffscreen: true,
|
|
2237
|
+
amount: 0.08,
|
|
2238
|
+
tileSize: 160,
|
|
2239
|
+
frames: 4,
|
|
2240
|
+
frameRate: 12,
|
|
2241
|
+
grainSize: 1,
|
|
2242
|
+
monochrome: true
|
|
2243
|
+
};
|
|
2244
|
+
var noiseLayer = defineEffect({
|
|
2245
|
+
name: "noise-layer",
|
|
2246
|
+
defaults: DEFAULTS9,
|
|
2247
|
+
structuralOptions: ["tileSize", "frames", "grainSize", "monochrome"],
|
|
2248
|
+
setup(context, options) {
|
|
2249
|
+
let opts = options;
|
|
2250
|
+
let tiles = [];
|
|
2251
|
+
let patterns = [];
|
|
2252
|
+
function buildTiles() {
|
|
2253
|
+
disposeTiles();
|
|
2254
|
+
const size = Math.max(16, Math.floor(opts.tileSize));
|
|
2255
|
+
const frameCount = Math.max(1, Math.min(12, Math.floor(opts.frames)));
|
|
2256
|
+
const grain = Math.max(1, Math.floor(opts.grainSize));
|
|
2257
|
+
const rng = context.rng.fork();
|
|
2258
|
+
const monochrome = opts.monochrome;
|
|
2259
|
+
const generated = Math.max(8, Math.floor(size / grain));
|
|
2260
|
+
for (let f = 0; f < frameCount; f++) {
|
|
2261
|
+
const tile = document.createElement("canvas");
|
|
2262
|
+
tile.width = generated;
|
|
2263
|
+
tile.height = generated;
|
|
2264
|
+
const tileCtx = tile.getContext("2d");
|
|
2265
|
+
if (!tileCtx) continue;
|
|
2266
|
+
const image = tileCtx.createImageData(generated, generated);
|
|
2267
|
+
const data = image.data;
|
|
2268
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
2269
|
+
if (monochrome) {
|
|
2270
|
+
const value = rng.next() * 255;
|
|
2271
|
+
data[i] = value;
|
|
2272
|
+
data[i + 1] = value;
|
|
2273
|
+
data[i + 2] = value;
|
|
2274
|
+
} else {
|
|
2275
|
+
data[i] = rng.next() * 255;
|
|
2276
|
+
data[i + 1] = rng.next() * 255;
|
|
2277
|
+
data[i + 2] = rng.next() * 255;
|
|
2278
|
+
}
|
|
2279
|
+
data[i + 3] = 255;
|
|
2280
|
+
}
|
|
2281
|
+
tileCtx.putImageData(image, 0, 0);
|
|
2282
|
+
tiles.push(tile);
|
|
2283
|
+
}
|
|
2284
|
+
patterns = new Array(tiles.length).fill(null);
|
|
2285
|
+
}
|
|
2286
|
+
function disposeTiles() {
|
|
2287
|
+
for (const tile of tiles) {
|
|
2288
|
+
tile.width = 1;
|
|
2289
|
+
tile.height = 1;
|
|
2290
|
+
}
|
|
2291
|
+
tiles = [];
|
|
2292
|
+
patterns = [];
|
|
2293
|
+
}
|
|
2294
|
+
buildTiles();
|
|
2295
|
+
return {
|
|
2296
|
+
frame({ time }) {
|
|
2297
|
+
const { ctx, size, surface } = context;
|
|
2298
|
+
const { width, height } = size;
|
|
2299
|
+
surface.clear();
|
|
2300
|
+
const amount = chunkILKUL7GD_cjs.clamp(opts.amount * opts.intensity, 0, 1);
|
|
2301
|
+
if (amount <= 1e-3 || tiles.length === 0) return;
|
|
2302
|
+
const index = opts.frameRate > 0 ? Math.floor(time * opts.frameRate) % tiles.length : 0;
|
|
2303
|
+
let pattern = patterns[index];
|
|
2304
|
+
if (!pattern) {
|
|
2305
|
+
pattern = ctx.createPattern(tiles[index], "repeat");
|
|
2306
|
+
patterns[index] = pattern;
|
|
2307
|
+
}
|
|
2308
|
+
if (!pattern) return;
|
|
2309
|
+
ctx.save();
|
|
2310
|
+
ctx.globalCompositeOperation = opts.blend ?? "overlay";
|
|
2311
|
+
ctx.globalAlpha = amount;
|
|
2312
|
+
const grain = Math.max(1, Math.floor(opts.grainSize));
|
|
2313
|
+
if (grain > 1) {
|
|
2314
|
+
ctx.scale(grain, grain);
|
|
2315
|
+
}
|
|
2316
|
+
if (opts.frameRate > 0) {
|
|
2317
|
+
const jitterX = index * 37 % Math.max(1, Math.floor(opts.tileSize));
|
|
2318
|
+
const jitterY = index * 53 % Math.max(1, Math.floor(opts.tileSize));
|
|
2319
|
+
ctx.translate(-jitterX, -jitterY);
|
|
2320
|
+
}
|
|
2321
|
+
ctx.fillStyle = pattern;
|
|
2322
|
+
const overdraw = Math.max(1, Math.floor(opts.tileSize));
|
|
2323
|
+
ctx.fillRect(0, 0, width / grain + overdraw, height / grain + overdraw);
|
|
2324
|
+
ctx.restore();
|
|
2325
|
+
},
|
|
2326
|
+
update(next) {
|
|
2327
|
+
opts = { ...opts, ...next };
|
|
2328
|
+
},
|
|
2329
|
+
destroy() {
|
|
2330
|
+
disposeTiles();
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
});
|
|
2335
|
+
|
|
2336
|
+
// src/effects/overlays/spotlight.ts
|
|
2337
|
+
var DEFAULTS10 = {
|
|
2338
|
+
colors: ["#ffffff"],
|
|
2339
|
+
speed: 1,
|
|
2340
|
+
intensity: 0.35,
|
|
2341
|
+
opacity: 1,
|
|
2342
|
+
blur: 0,
|
|
2343
|
+
blend: "lighter",
|
|
2344
|
+
seed: 43,
|
|
2345
|
+
interactive: true,
|
|
2346
|
+
// The output is one enormous soft gradient. Even quarter resolution is more than enough.
|
|
2347
|
+
resolution: 0.35,
|
|
2348
|
+
performance: "auto",
|
|
2349
|
+
// The light only moves in response to the user's own pointer, which is not the kind of
|
|
2350
|
+
// autonomous motion reduced-motion is protecting against. A static centred light is the
|
|
2351
|
+
// sensible fallback.
|
|
2352
|
+
reducedMotion: "static",
|
|
2353
|
+
paused: false,
|
|
2354
|
+
pauseOffscreen: true,
|
|
2355
|
+
radius: 320,
|
|
2356
|
+
falloff: 1.6,
|
|
2357
|
+
fade: 0.35,
|
|
2358
|
+
ambient: false,
|
|
2359
|
+
ambientDrift: 0.25,
|
|
2360
|
+
core: true
|
|
2361
|
+
};
|
|
2362
|
+
var spotlight = defineEffect({
|
|
2363
|
+
name: "spotlight",
|
|
2364
|
+
defaults: DEFAULTS10,
|
|
2365
|
+
setup(context, options) {
|
|
2366
|
+
let opts = options;
|
|
2367
|
+
let presence = opts.ambient ? 1 : 0;
|
|
2368
|
+
const drift = { phaseX: context.rng.range(0, chunkILKUL7GD_cjs.TAU), phaseY: context.rng.range(0, chunkILKUL7GD_cjs.TAU) };
|
|
2369
|
+
return {
|
|
2370
|
+
frame({ time, delta }) {
|
|
2371
|
+
const { ctx, size, pointer, surface, colors } = context;
|
|
2372
|
+
const { width, height } = size;
|
|
2373
|
+
surface.clear();
|
|
2374
|
+
const interactive = opts.interactive !== false && pointer !== null;
|
|
2375
|
+
const hovering = interactive && pointer.inside;
|
|
2376
|
+
const fadeSeconds = Math.max(0, opts.fade);
|
|
2377
|
+
const target = hovering || opts.ambient ? 1 : 0;
|
|
2378
|
+
if (fadeSeconds <= 0) {
|
|
2379
|
+
presence = target;
|
|
2380
|
+
} else {
|
|
2381
|
+
const step = delta / fadeSeconds;
|
|
2382
|
+
presence = presence < target ? Math.min(target, presence + step) : Math.max(target, presence - step);
|
|
2383
|
+
}
|
|
2384
|
+
if (presence <= 2e-3) return;
|
|
2385
|
+
let x;
|
|
2386
|
+
let y;
|
|
2387
|
+
if (interactive && pointer.everInside) {
|
|
2388
|
+
x = pointer.smoothX;
|
|
2389
|
+
y = pointer.smoothY;
|
|
2390
|
+
} else {
|
|
2391
|
+
const driftAmount = opts.ambientDrift;
|
|
2392
|
+
x = width * (0.5 + Math.sin(time * 0.11 + drift.phaseX) * driftAmount);
|
|
2393
|
+
y = height * (0.5 + Math.cos(time * 0.13 + drift.phaseY) * driftAmount);
|
|
2394
|
+
}
|
|
2395
|
+
const radius = Math.max(1, opts.radius);
|
|
2396
|
+
const color = colors[0];
|
|
2397
|
+
const rgb = `${Math.round(color.r * 255)},${Math.round(color.g * 255)},${Math.round(color.b * 255)}`;
|
|
2398
|
+
const peak = chunkILKUL7GD_cjs.clamp(color.a * opts.intensity * presence, 0, 1);
|
|
2399
|
+
const falloff = Math.max(0.2, opts.falloff);
|
|
2400
|
+
ctx.globalCompositeOperation = opts.blend ?? "lighter";
|
|
2401
|
+
const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
|
|
2402
|
+
const STOPS = 6;
|
|
2403
|
+
for (let i = 0; i <= STOPS; i++) {
|
|
2404
|
+
const t = i / STOPS;
|
|
2405
|
+
const alpha = peak * (1 - t) ** falloff;
|
|
2406
|
+
gradient.addColorStop(t, `rgba(${rgb},${alpha.toFixed(4)})`);
|
|
2407
|
+
}
|
|
2408
|
+
ctx.fillStyle = gradient;
|
|
2409
|
+
ctx.fillRect(x - radius, y - radius, radius * 2, radius * 2);
|
|
2410
|
+
if (opts.core) {
|
|
2411
|
+
const coreRadius = radius * 0.32;
|
|
2412
|
+
const core = ctx.createRadialGradient(x, y, 0, x, y, coreRadius);
|
|
2413
|
+
core.addColorStop(0, `rgba(${rgb},${(peak * 0.75).toFixed(4)})`);
|
|
2414
|
+
core.addColorStop(0.5, `rgba(${rgb},${(peak * 0.28).toFixed(4)})`);
|
|
2415
|
+
core.addColorStop(1, `rgba(${rgb},0)`);
|
|
2416
|
+
ctx.fillStyle = core;
|
|
2417
|
+
ctx.fillRect(x - coreRadius, y - coreRadius, coreRadius * 2, coreRadius * 2);
|
|
2418
|
+
}
|
|
2419
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2420
|
+
},
|
|
2421
|
+
update(next) {
|
|
2422
|
+
opts = { ...opts, ...next };
|
|
2423
|
+
if (opts.ambient) presence = Math.max(presence, 1e-3);
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
});
|
|
2428
|
+
|
|
2429
|
+
// src/effects/index.ts
|
|
2430
|
+
var effects = {
|
|
2431
|
+
aurora,
|
|
2432
|
+
"mesh-gradient": meshGradient,
|
|
2433
|
+
"animated-gradient": animatedGradient,
|
|
2434
|
+
"particle-field": particleField,
|
|
2435
|
+
constellation,
|
|
2436
|
+
meteors,
|
|
2437
|
+
rain,
|
|
2438
|
+
snow,
|
|
2439
|
+
fireflies,
|
|
2440
|
+
waves,
|
|
2441
|
+
blob,
|
|
2442
|
+
"noise-layer": noiseLayer,
|
|
2443
|
+
spotlight
|
|
2444
|
+
};
|
|
2445
|
+
|
|
2446
|
+
exports.GradientCache = GradientCache;
|
|
2447
|
+
exports.ParticleBuffer = ParticleBuffer;
|
|
2448
|
+
exports.SpatialGrid = SpatialGrid;
|
|
2449
|
+
exports.ageAndCull = ageAndCull;
|
|
2450
|
+
exports.animatedGradient = animatedGradient;
|
|
2451
|
+
exports.aurora = aurora;
|
|
2452
|
+
exports.blob = blob;
|
|
2453
|
+
exports.constellation = constellation;
|
|
2454
|
+
exports.createSurface = createSurface;
|
|
2455
|
+
exports.defineEffect = defineEffect;
|
|
2456
|
+
exports.drawDot = drawDot;
|
|
2457
|
+
exports.drawGlow = drawGlow;
|
|
2458
|
+
exports.effects = effects;
|
|
2459
|
+
exports.fireflies = fireflies;
|
|
2460
|
+
exports.integrate = integrate;
|
|
2461
|
+
exports.meshGradient = meshGradient;
|
|
2462
|
+
exports.meteors = meteors;
|
|
2463
|
+
exports.mountEffect = mountEffect;
|
|
2464
|
+
exports.noiseLayer = noiseLayer;
|
|
2465
|
+
exports.particleField = particleField;
|
|
2466
|
+
exports.rain = rain;
|
|
2467
|
+
exports.resetFilterSupport = resetFilterSupport;
|
|
2468
|
+
exports.snow = snow;
|
|
2469
|
+
exports.spotlight = spotlight;
|
|
2470
|
+
exports.supportsCanvasFilter = supportsCanvasFilter;
|
|
2471
|
+
exports.supportsOffscreenCanvas = supportsOffscreenCanvas;
|
|
2472
|
+
exports.supportsWorkerRendering = supportsWorkerRendering;
|
|
2473
|
+
exports.traceClosedSpline = traceClosedSpline;
|
|
2474
|
+
exports.traceOpenSpline = traceOpenSpline;
|
|
2475
|
+
exports.traceRoundRect = traceRoundRect;
|
|
2476
|
+
exports.waves = waves;
|
|
2477
|
+
exports.wrapBounds = wrapBounds;
|
|
2478
|
+
//# sourceMappingURL=chunk-R7UJ7MWP.cjs.map
|
|
2479
|
+
//# sourceMappingURL=chunk-R7UJ7MWP.cjs.map
|