@basmilius/sparkle 2.3.0 → 2.5.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/dist/index.d.mts +637 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +6964 -2577
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/balloons/layer.ts +4 -3
- package/src/black-hole/consts.ts +3 -0
- package/src/black-hole/index.ts +10 -0
- package/src/black-hole/layer.ts +193 -0
- package/src/black-hole/types.ts +8 -0
- package/src/boids/consts.ts +8 -0
- package/src/boids/index.ts +9 -0
- package/src/boids/layer.ts +245 -0
- package/src/boids/types.ts +7 -0
- package/src/butterflies/consts.ts +3 -0
- package/src/butterflies/index.ts +9 -0
- package/src/butterflies/layer.ts +246 -0
- package/src/butterflies/types.ts +23 -0
- package/src/caustics/consts.ts +3 -0
- package/src/caustics/index.ts +9 -0
- package/src/caustics/layer.ts +107 -0
- package/src/clouds/consts.ts +3 -0
- package/src/clouds/index.ts +9 -0
- package/src/clouds/layer.ts +167 -0
- package/src/clouds/types.ts +9 -0
- package/src/confetti/layer.ts +3 -2
- package/src/constellation/consts.ts +3 -0
- package/src/constellation/index.ts +10 -0
- package/src/constellation/layer.ts +256 -0
- package/src/constellation/types.ts +11 -0
- package/src/coral-reef/consts.ts +3 -0
- package/src/coral-reef/index.ts +10 -0
- package/src/coral-reef/layer.ts +276 -0
- package/src/coral-reef/types.ts +31 -0
- package/src/crystallization/consts.ts +3 -0
- package/src/crystallization/index.ts +10 -0
- package/src/crystallization/layer.ts +318 -0
- package/src/crystallization/types.ts +25 -0
- package/src/digital-rain/consts.ts +7 -0
- package/src/digital-rain/index.ts +10 -0
- package/src/digital-rain/layer.ts +195 -0
- package/src/digital-rain/types.ts +10 -0
- package/src/donuts/layer.ts +5 -3
- package/src/glitch/consts.ts +3 -0
- package/src/glitch/index.ts +9 -0
- package/src/glitch/layer.ts +231 -0
- package/src/glitch/types.ts +28 -0
- package/src/gradient-flow/consts.ts +3 -0
- package/src/gradient-flow/index.ts +9 -0
- package/src/gradient-flow/layer.ts +134 -0
- package/src/gradient-flow/types.ts +8 -0
- package/src/hologram/consts.ts +5 -0
- package/src/hologram/index.ts +9 -0
- package/src/hologram/layer.ts +205 -0
- package/src/hologram/types.ts +20 -0
- package/src/hyper-space/consts.ts +3 -0
- package/src/hyper-space/index.ts +10 -0
- package/src/hyper-space/layer.ts +167 -0
- package/src/hyper-space/types.ts +8 -0
- package/src/index.ts +29 -0
- package/src/interference/consts.ts +9 -0
- package/src/interference/index.ts +9 -0
- package/src/interference/layer.ts +129 -0
- package/src/kaleidoscope/consts.ts +12 -0
- package/src/kaleidoscope/index.ts +9 -0
- package/src/kaleidoscope/layer.ts +213 -0
- package/src/kaleidoscope/types.ts +19 -0
- package/src/lanterns/layer.ts +3 -2
- package/src/lava/consts.ts +3 -0
- package/src/lava/index.ts +9 -0
- package/src/lava/layer.ts +152 -0
- package/src/lava/types.ts +13 -0
- package/src/leaves/layer.ts +3 -2
- package/src/murmuration/consts.ts +3 -0
- package/src/murmuration/index.ts +10 -0
- package/src/murmuration/layer.ts +279 -0
- package/src/murmuration/types.ts +7 -0
- package/src/nebula/consts.ts +3 -0
- package/src/nebula/index.ts +10 -0
- package/src/nebula/layer.ts +150 -0
- package/src/nebula/types.ts +20 -0
- package/src/neon/consts.ts +5 -0
- package/src/neon/index.ts +9 -0
- package/src/neon/layer.ts +213 -0
- package/src/neon/types.ts +18 -0
- package/src/petals/layer.ts +3 -2
- package/src/pollen/consts.ts +3 -0
- package/src/pollen/index.ts +10 -0
- package/src/pollen/layer.ts +181 -0
- package/src/pollen/types.ts +10 -0
- package/src/popcorn/consts.ts +3 -0
- package/src/popcorn/index.ts +10 -0
- package/src/popcorn/layer.ts +218 -0
- package/src/popcorn/types.ts +13 -0
- package/src/portal/consts.ts +3 -0
- package/src/portal/index.ts +10 -0
- package/src/portal/layer.ts +251 -0
- package/src/portal/types.ts +10 -0
- package/src/pulse-grid/consts.ts +3 -0
- package/src/pulse-grid/index.ts +10 -0
- package/src/pulse-grid/layer.ts +185 -0
- package/src/pulse-grid/types.ts +8 -0
- package/src/roots/consts.ts +3 -0
- package/src/roots/index.ts +9 -0
- package/src/roots/layer.ts +218 -0
- package/src/roots/types.ts +23 -0
- package/src/smoke/consts.ts +3 -0
- package/src/smoke/index.ts +9 -0
- package/src/smoke/layer.ts +182 -0
- package/src/smoke/types.ts +14 -0
- package/src/snow/layer.ts +3 -2
- package/src/topography/consts.ts +3 -0
- package/src/topography/index.ts +9 -0
- package/src/topography/layer.ts +141 -0
- package/src/tornado/consts.ts +3 -0
- package/src/tornado/index.ts +10 -0
- package/src/tornado/layer.ts +271 -0
- package/src/tornado/types.ts +22 -0
- package/src/volcano/consts.ts +3 -0
- package/src/volcano/index.ts +10 -0
- package/src/volcano/layer.ts +261 -0
- package/src/volcano/types.ts +10 -0
- package/src/voronoi/consts.ts +3 -0
- package/src/voronoi/index.ts +10 -0
- package/src/voronoi/layer.ts +197 -0
- package/src/voronoi/types.ts +7 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { parseColor } from '../color';
|
|
2
|
+
import { Effect } from '../effect';
|
|
3
|
+
import { MULBERRY } from './consts';
|
|
4
|
+
import type { Butterfly } from './types';
|
|
5
|
+
|
|
6
|
+
export interface ButterfliesConfig {
|
|
7
|
+
readonly colors?: string[];
|
|
8
|
+
readonly count?: number;
|
|
9
|
+
readonly scale?: number;
|
|
10
|
+
readonly size?: number;
|
|
11
|
+
readonly speed?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_COLORS = [
|
|
15
|
+
'#f4a261',
|
|
16
|
+
'#e76f51',
|
|
17
|
+
'#e9c46a',
|
|
18
|
+
'#2a9d8f',
|
|
19
|
+
'#8ecae6',
|
|
20
|
+
'#ffb7c5',
|
|
21
|
+
'#c77dff',
|
|
22
|
+
'#f8edeb'
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export class Butterflies extends Effect<ButterfliesConfig> {
|
|
26
|
+
readonly #scale: number;
|
|
27
|
+
#speed: number;
|
|
28
|
+
#count: number;
|
|
29
|
+
#size: number;
|
|
30
|
+
#time: number = 0;
|
|
31
|
+
#butterflies: Butterfly[] = [];
|
|
32
|
+
|
|
33
|
+
constructor(config: ButterfliesConfig = {}) {
|
|
34
|
+
super();
|
|
35
|
+
|
|
36
|
+
this.#scale = config.scale ?? 1;
|
|
37
|
+
this.#speed = config.speed ?? 1;
|
|
38
|
+
this.#count = config.count ?? 12;
|
|
39
|
+
this.#size = (config.size ?? 20) * this.#scale;
|
|
40
|
+
|
|
41
|
+
const colors = config.colors ?? DEFAULT_COLORS;
|
|
42
|
+
|
|
43
|
+
if (innerWidth < 991) {
|
|
44
|
+
this.#count = Math.floor(this.#count / 2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < this.#count; ++i) {
|
|
48
|
+
this.#butterflies.push(this.#createButterfly(colors));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
configure(config: Partial<ButterfliesConfig>): void {
|
|
53
|
+
if (config.speed !== undefined) {
|
|
54
|
+
this.#speed = config.speed;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
tick(dt: number, _width: number, _height: number): void {
|
|
59
|
+
this.#time += 0.003 * this.#speed * dt;
|
|
60
|
+
|
|
61
|
+
for (const butterfly of this.#butterflies) {
|
|
62
|
+
const timeX = this.#time * butterfly.orbitSpeedX + butterfly.orbitOffsetX;
|
|
63
|
+
const timeY = this.#time * butterfly.orbitSpeedY + butterfly.orbitOffsetY;
|
|
64
|
+
|
|
65
|
+
const orbitX = Math.sin(timeX) * butterfly.orbitRadiusX;
|
|
66
|
+
const orbitY = Math.cos(timeY) * butterfly.orbitRadiusY;
|
|
67
|
+
|
|
68
|
+
const targetX = butterfly.driftX + orbitX;
|
|
69
|
+
const targetY = butterfly.driftY + orbitY;
|
|
70
|
+
|
|
71
|
+
const dx = targetX - butterfly.x;
|
|
72
|
+
const dy = targetY - butterfly.y;
|
|
73
|
+
|
|
74
|
+
butterfly.x += dx * 0.05 * dt;
|
|
75
|
+
butterfly.y += dy * 0.05 * dt;
|
|
76
|
+
|
|
77
|
+
butterfly.driftX += butterfly.driftVX * dt;
|
|
78
|
+
butterfly.driftY += butterfly.driftVY * dt;
|
|
79
|
+
|
|
80
|
+
if (butterfly.driftX < 0.05 || butterfly.driftX > 0.95) {
|
|
81
|
+
butterfly.driftVX *= -1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (butterfly.driftY < 0.05 || butterfly.driftY > 0.95) {
|
|
85
|
+
butterfly.driftVY *= -1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const moveDx = dx * 0.05 * dt;
|
|
89
|
+
const moveDy = dy * 0.05 * dt;
|
|
90
|
+
|
|
91
|
+
if (Math.abs(moveDx) > 0.000001 || Math.abs(moveDy) > 0.000001) {
|
|
92
|
+
const targetAngle = Math.atan2(moveDy, moveDx) + Math.PI / 2;
|
|
93
|
+
let angleDiff = targetAngle - butterfly.angle;
|
|
94
|
+
|
|
95
|
+
if (angleDiff > Math.PI) {
|
|
96
|
+
angleDiff -= Math.PI * 2;
|
|
97
|
+
} else if (angleDiff < -Math.PI) {
|
|
98
|
+
angleDiff += Math.PI * 2;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
butterfly.angle += angleDiff * 0.08 * dt;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
|
|
107
|
+
const globalTime = this.#time * 1000;
|
|
108
|
+
|
|
109
|
+
for (const butterfly of this.#butterflies) {
|
|
110
|
+
const px = butterfly.x * width;
|
|
111
|
+
const py = butterfly.y * height;
|
|
112
|
+
const flapAngle = Math.sin(globalTime * butterfly.flapSpeed * 0.012 + butterfly.flapOffset);
|
|
113
|
+
const wingScale = Math.abs(flapAngle);
|
|
114
|
+
const isFlipped = flapAngle < 0;
|
|
115
|
+
|
|
116
|
+
const size = butterfly.size * this.#size;
|
|
117
|
+
|
|
118
|
+
ctx.globalAlpha = 0.85;
|
|
119
|
+
|
|
120
|
+
this.#drawButterfly(ctx, px, py, butterfly.angle, size, wingScale, isFlipped, butterfly.colorR, butterfly.colorG, butterfly.colorB);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
ctx.globalAlpha = 1;
|
|
124
|
+
ctx.resetTransform();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#drawButterfly(
|
|
128
|
+
ctx: CanvasRenderingContext2D,
|
|
129
|
+
px: number,
|
|
130
|
+
py: number,
|
|
131
|
+
angle: number,
|
|
132
|
+
size: number,
|
|
133
|
+
wingScale: number,
|
|
134
|
+
isFlipped: boolean,
|
|
135
|
+
r: number,
|
|
136
|
+
g: number,
|
|
137
|
+
b: number
|
|
138
|
+
): void {
|
|
139
|
+
const cos = Math.cos(angle);
|
|
140
|
+
const sin = Math.sin(angle);
|
|
141
|
+
|
|
142
|
+
ctx.save();
|
|
143
|
+
ctx.transform(cos, sin, -sin, cos, px, py);
|
|
144
|
+
|
|
145
|
+
const ws = Math.max(0.05, wingScale);
|
|
146
|
+
|
|
147
|
+
const wingColor = `rgba(${r}, ${g}, ${b}, 0.75)`;
|
|
148
|
+
const wingEdgeColor = `rgba(${r}, ${g}, ${b}, 0.4)`;
|
|
149
|
+
const bodyColor = `rgba(${Math.floor(r * 0.4)}, ${Math.floor(g * 0.4)}, ${Math.floor(b * 0.4)}, 0.9)`;
|
|
150
|
+
|
|
151
|
+
for (const side of [-1, 1]) {
|
|
152
|
+
const scaleX = side * ws;
|
|
153
|
+
|
|
154
|
+
ctx.save();
|
|
155
|
+
ctx.scale(scaleX, 1);
|
|
156
|
+
|
|
157
|
+
ctx.fillStyle = wingColor;
|
|
158
|
+
ctx.beginPath();
|
|
159
|
+
ctx.moveTo(0, 0);
|
|
160
|
+
ctx.bezierCurveTo(
|
|
161
|
+
size * 0.8, -size * 0.2,
|
|
162
|
+
size * 1.1, -size * 0.9,
|
|
163
|
+
size * 0.5, -size * 1.0
|
|
164
|
+
);
|
|
165
|
+
ctx.bezierCurveTo(
|
|
166
|
+
size * 0.1, -size * 1.1,
|
|
167
|
+
-size * 0.1, -size * 0.6,
|
|
168
|
+
0, 0
|
|
169
|
+
);
|
|
170
|
+
ctx.fill();
|
|
171
|
+
|
|
172
|
+
ctx.fillStyle = wingEdgeColor;
|
|
173
|
+
ctx.beginPath();
|
|
174
|
+
ctx.moveTo(0, 0);
|
|
175
|
+
ctx.bezierCurveTo(
|
|
176
|
+
size * 0.9, size * 0.1,
|
|
177
|
+
size * 1.0, size * 0.7,
|
|
178
|
+
size * 0.4, size * 0.8
|
|
179
|
+
);
|
|
180
|
+
ctx.bezierCurveTo(
|
|
181
|
+
size * 0.0, size * 0.85,
|
|
182
|
+
-size * 0.1, size * 0.4,
|
|
183
|
+
0, 0
|
|
184
|
+
);
|
|
185
|
+
ctx.fill();
|
|
186
|
+
|
|
187
|
+
ctx.restore();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
ctx.fillStyle = bodyColor;
|
|
191
|
+
const bodyLength = size * 1.2;
|
|
192
|
+
ctx.beginPath();
|
|
193
|
+
ctx.ellipse(0, 0, size * 0.08, bodyLength * 0.5, 0, 0, Math.PI * 2);
|
|
194
|
+
ctx.fill();
|
|
195
|
+
|
|
196
|
+
const antennaLength = size * 0.7;
|
|
197
|
+
ctx.strokeStyle = bodyColor;
|
|
198
|
+
ctx.lineWidth = 1;
|
|
199
|
+
|
|
200
|
+
for (const antSide of [-1, 1]) {
|
|
201
|
+
ctx.beginPath();
|
|
202
|
+
ctx.moveTo(antSide * size * 0.04, -bodyLength * 0.4);
|
|
203
|
+
ctx.quadraticCurveTo(
|
|
204
|
+
antSide * size * 0.3, -bodyLength * 0.6,
|
|
205
|
+
antSide * size * 0.25, -bodyLength * 0.5 - antennaLength
|
|
206
|
+
);
|
|
207
|
+
ctx.stroke();
|
|
208
|
+
|
|
209
|
+
ctx.fillStyle = bodyColor;
|
|
210
|
+
ctx.beginPath();
|
|
211
|
+
ctx.arc(antSide * size * 0.25, -bodyLength * 0.5 - antennaLength, size * 0.06, 0, Math.PI * 2);
|
|
212
|
+
ctx.fill();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ctx.restore();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#createButterfly(colors: string[]): Butterfly {
|
|
219
|
+
const colorStr = colors[Math.floor(MULBERRY.next() * colors.length)];
|
|
220
|
+
const {r, g, b} = parseColor(colorStr);
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
x: MULBERRY.next(),
|
|
224
|
+
y: MULBERRY.next(),
|
|
225
|
+
angle: MULBERRY.next() * Math.PI * 2 + Math.PI / 2,
|
|
226
|
+
speed: 0.5 + MULBERRY.next() * 0.5,
|
|
227
|
+
flapSpeed: 4 + MULBERRY.next() * 4,
|
|
228
|
+
flapOffset: MULBERRY.next() * Math.PI * 2,
|
|
229
|
+
size: 0.7 + MULBERRY.next() * 0.6,
|
|
230
|
+
color: colorStr,
|
|
231
|
+
colorR: r,
|
|
232
|
+
colorG: g,
|
|
233
|
+
colorB: b,
|
|
234
|
+
orbitRadiusX: 0.06 + MULBERRY.next() * 0.15,
|
|
235
|
+
orbitRadiusY: 0.05 + MULBERRY.next() * 0.12,
|
|
236
|
+
orbitSpeedX: 0.6 + MULBERRY.next() * 1.2,
|
|
237
|
+
orbitSpeedY: 0.5 + MULBERRY.next() * 1.0,
|
|
238
|
+
orbitOffsetX: MULBERRY.next() * Math.PI * 2,
|
|
239
|
+
orbitOffsetY: MULBERRY.next() * Math.PI * 2,
|
|
240
|
+
driftX: 0.15 + MULBERRY.next() * 0.7,
|
|
241
|
+
driftY: 0.15 + MULBERRY.next() * 0.7,
|
|
242
|
+
driftVX: (MULBERRY.next() - 0.5) * 0.0005,
|
|
243
|
+
driftVY: (MULBERRY.next() - 0.5) * 0.0004
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface Butterfly {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
angle: number;
|
|
5
|
+
speed: number;
|
|
6
|
+
flapSpeed: number;
|
|
7
|
+
flapOffset: number;
|
|
8
|
+
size: number;
|
|
9
|
+
color: string;
|
|
10
|
+
colorR: number;
|
|
11
|
+
colorG: number;
|
|
12
|
+
colorB: number;
|
|
13
|
+
orbitRadiusX: number;
|
|
14
|
+
orbitRadiusY: number;
|
|
15
|
+
orbitSpeedX: number;
|
|
16
|
+
orbitSpeedY: number;
|
|
17
|
+
orbitOffsetX: number;
|
|
18
|
+
orbitOffsetY: number;
|
|
19
|
+
driftX: number;
|
|
20
|
+
driftY: number;
|
|
21
|
+
driftVX: number;
|
|
22
|
+
driftVY: number;
|
|
23
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Caustics } from './layer';
|
|
2
|
+
import type { CausticsConfig } from './layer';
|
|
3
|
+
import type { Effect } from '../effect';
|
|
4
|
+
|
|
5
|
+
export function createCaustics(config?: CausticsConfig): Effect<CausticsConfig> {
|
|
6
|
+
return new Caustics(config);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type { CausticsConfig };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { hexToRGB } from '@basmilius/utils';
|
|
2
|
+
import { Effect } from '../effect';
|
|
3
|
+
|
|
4
|
+
export interface CausticsConfig {
|
|
5
|
+
readonly speed?: number;
|
|
6
|
+
readonly scale?: number;
|
|
7
|
+
readonly resolution?: number;
|
|
8
|
+
readonly intensity?: number;
|
|
9
|
+
readonly color?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Caustics extends Effect<CausticsConfig> {
|
|
13
|
+
#speed: number;
|
|
14
|
+
readonly #scale: number;
|
|
15
|
+
readonly #resolution: number;
|
|
16
|
+
#intensity: number;
|
|
17
|
+
readonly #colorR: number;
|
|
18
|
+
readonly #colorG: number;
|
|
19
|
+
readonly #colorB: number;
|
|
20
|
+
#time: number = 0;
|
|
21
|
+
#offscreen: HTMLCanvasElement | null = null;
|
|
22
|
+
#offscreenCtx: CanvasRenderingContext2D | null = null;
|
|
23
|
+
#imageData: ImageData | null = null;
|
|
24
|
+
|
|
25
|
+
constructor(config: CausticsConfig = {}) {
|
|
26
|
+
super();
|
|
27
|
+
|
|
28
|
+
this.#speed = config.speed ?? 1;
|
|
29
|
+
this.#scale = config.scale ?? 1;
|
|
30
|
+
this.#resolution = config.resolution ?? 4;
|
|
31
|
+
this.#intensity = config.intensity ?? 0.7;
|
|
32
|
+
|
|
33
|
+
const [r, g, b] = hexToRGB(config.color ?? '#4488cc');
|
|
34
|
+
this.#colorR = r;
|
|
35
|
+
this.#colorG = g;
|
|
36
|
+
this.#colorB = b;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
configure(config: Partial<CausticsConfig>): void {
|
|
40
|
+
if (config.speed !== undefined) {
|
|
41
|
+
this.#speed = config.speed;
|
|
42
|
+
}
|
|
43
|
+
if (config.intensity !== undefined) {
|
|
44
|
+
this.#intensity = config.intensity;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
tick(dt: number, _width: number, _height: number): void {
|
|
49
|
+
this.#time += 0.015 * dt * this.#speed;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
|
|
53
|
+
const resolution = this.#resolution;
|
|
54
|
+
const offWidth = Math.ceil(width / resolution);
|
|
55
|
+
const offHeight = Math.ceil(height / resolution);
|
|
56
|
+
|
|
57
|
+
if (!this.#offscreen || this.#offscreen.width !== offWidth || this.#offscreen.height !== offHeight) {
|
|
58
|
+
this.#offscreen = document.createElement('canvas');
|
|
59
|
+
this.#offscreen.width = offWidth;
|
|
60
|
+
this.#offscreen.height = offHeight;
|
|
61
|
+
this.#offscreenCtx = this.#offscreen.getContext('2d');
|
|
62
|
+
this.#imageData = this.#offscreenCtx!.createImageData(offWidth, offHeight);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const data = this.#imageData!.data;
|
|
66
|
+
const time = this.#time;
|
|
67
|
+
const scale = this.#scale;
|
|
68
|
+
const intensity = this.#intensity;
|
|
69
|
+
const colorR = this.#colorR;
|
|
70
|
+
const colorG = this.#colorG;
|
|
71
|
+
const colorB = this.#colorB;
|
|
72
|
+
|
|
73
|
+
const freq1 = 40 * scale;
|
|
74
|
+
const freq2 = 30 * scale;
|
|
75
|
+
const freq3 = 35 * scale;
|
|
76
|
+
const freq4 = 25 * scale;
|
|
77
|
+
const freq5 = 45 * scale;
|
|
78
|
+
const amplitude = 2.5;
|
|
79
|
+
|
|
80
|
+
for (let py = 0; py < offHeight; py++) {
|
|
81
|
+
const worldY = py * resolution;
|
|
82
|
+
|
|
83
|
+
for (let px = 0; px < offWidth; px++) {
|
|
84
|
+
const worldX = px * resolution;
|
|
85
|
+
|
|
86
|
+
const v1 = Math.sin(worldX / freq1 + Math.sin(worldY / freq2 + time) * amplitude);
|
|
87
|
+
const v2 = Math.sin(worldY / freq3 + Math.sin(worldX / freq4 + time * 0.7) * amplitude);
|
|
88
|
+
const v3 = Math.sin((worldX + worldY) / freq5 + time * 0.5);
|
|
89
|
+
|
|
90
|
+
const caustic = (v1 + v2 + v3) / 3;
|
|
91
|
+
const clamped = Math.max(0, caustic);
|
|
92
|
+
const brightness = Math.pow(clamped, 2) * intensity;
|
|
93
|
+
|
|
94
|
+
const offset = (py * offWidth + px) * 4;
|
|
95
|
+
data[offset] = Math.min(255, colorR * brightness + (1 - brightness) * colorR * 0.15);
|
|
96
|
+
data[offset + 1] = Math.min(255, colorG * brightness + (1 - brightness) * colorG * 0.15);
|
|
97
|
+
data[offset + 2] = Math.min(255, colorB * brightness + (1 - brightness) * colorB * 0.15);
|
|
98
|
+
data[offset + 3] = 255;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.#offscreenCtx!.putImageData(this.#imageData!, 0, 0);
|
|
103
|
+
|
|
104
|
+
ctx.imageSmoothingEnabled = true;
|
|
105
|
+
ctx.drawImage(this.#offscreen!, 0, 0, width, height);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Clouds } from './layer';
|
|
2
|
+
import type { CloudsConfig } from './layer';
|
|
3
|
+
import type { Effect } from '../effect';
|
|
4
|
+
|
|
5
|
+
export function createClouds(config?: CloudsConfig): Effect<CloudsConfig> {
|
|
6
|
+
return new Clouds(config);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type { CloudsConfig };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { parseColor } from '../color';
|
|
2
|
+
import { Effect } from '../effect';
|
|
3
|
+
import { MULBERRY } from './consts';
|
|
4
|
+
import type { Cloud } from './types';
|
|
5
|
+
|
|
6
|
+
export interface CloudsConfig {
|
|
7
|
+
readonly color?: string;
|
|
8
|
+
readonly count?: number;
|
|
9
|
+
readonly opacity?: number;
|
|
10
|
+
readonly scale?: number;
|
|
11
|
+
readonly speed?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const SPRITE_COUNT = 6;
|
|
15
|
+
const SPRITE_SIZE = 256;
|
|
16
|
+
|
|
17
|
+
export class Clouds extends Effect<CloudsConfig> {
|
|
18
|
+
readonly #scale: number;
|
|
19
|
+
#speed: number;
|
|
20
|
+
#count: number;
|
|
21
|
+
#opacity: number;
|
|
22
|
+
#clouds: Cloud[] = [];
|
|
23
|
+
#sprites: HTMLCanvasElement[] = [];
|
|
24
|
+
#colorR: number = 255;
|
|
25
|
+
#colorG: number = 255;
|
|
26
|
+
#colorB: number = 255;
|
|
27
|
+
|
|
28
|
+
constructor(config: CloudsConfig = {}) {
|
|
29
|
+
super();
|
|
30
|
+
|
|
31
|
+
this.#scale = config.scale ?? 1;
|
|
32
|
+
this.#speed = config.speed ?? 0.3;
|
|
33
|
+
this.#count = config.count ?? 8;
|
|
34
|
+
this.#opacity = config.opacity ?? 0.8;
|
|
35
|
+
|
|
36
|
+
const {r, g, b} = parseColor(config.color ?? '#ffffff');
|
|
37
|
+
this.#colorR = r;
|
|
38
|
+
this.#colorG = g;
|
|
39
|
+
this.#colorB = b;
|
|
40
|
+
|
|
41
|
+
if (innerWidth < 991) {
|
|
42
|
+
this.#count = Math.max(3, Math.floor(this.#count / 2));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.#sprites = this.#createSprites(r, g, b);
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < this.#count; ++i) {
|
|
48
|
+
this.#clouds.push(this.#createCloud(true));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
configure(config: Partial<CloudsConfig>): void {
|
|
53
|
+
if (config.speed !== undefined) {
|
|
54
|
+
this.#speed = config.speed;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (config.opacity !== undefined) {
|
|
58
|
+
this.#opacity = config.opacity;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (config.color !== undefined) {
|
|
62
|
+
const {r, g, b} = parseColor(config.color);
|
|
63
|
+
this.#colorR = r;
|
|
64
|
+
this.#colorG = g;
|
|
65
|
+
this.#colorB = b;
|
|
66
|
+
this.#sprites = this.#createSprites(r, g, b);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
tick(dt: number, _width: number, _height: number): void {
|
|
71
|
+
for (const cloud of this.#clouds) {
|
|
72
|
+
const layerSpeed = this.#speed * (0.5 + cloud.layer * 0.5);
|
|
73
|
+
cloud.x += layerSpeed * 0.002 * dt;
|
|
74
|
+
|
|
75
|
+
if (cloud.x > 1.4) {
|
|
76
|
+
cloud.x = -0.4;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
|
|
82
|
+
const sortedClouds = [...this.#clouds].sort((a, b) => a.layer - b.layer);
|
|
83
|
+
|
|
84
|
+
for (const cloud of sortedClouds) {
|
|
85
|
+
const px = cloud.x * width;
|
|
86
|
+
const py = cloud.y * height;
|
|
87
|
+
const cloudWidth = SPRITE_SIZE * cloud.scale * this.#scale * (width / 800);
|
|
88
|
+
const cloudHeight = (SPRITE_SIZE * 0.45) * cloud.scale * this.#scale * (width / 800);
|
|
89
|
+
|
|
90
|
+
const depthAlpha = 0.4 + cloud.layer * 0.3;
|
|
91
|
+
ctx.globalAlpha = this.#opacity * depthAlpha * cloud.opacity;
|
|
92
|
+
|
|
93
|
+
ctx.drawImage(
|
|
94
|
+
this.#sprites[cloud.spriteIndex],
|
|
95
|
+
px - cloudWidth * 0.5,
|
|
96
|
+
py - cloudHeight * 0.5,
|
|
97
|
+
cloudWidth,
|
|
98
|
+
cloudHeight
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ctx.globalAlpha = 1;
|
|
103
|
+
ctx.resetTransform();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#createSprites(r: number, g: number, b: number): HTMLCanvasElement[] {
|
|
107
|
+
const sprites: HTMLCanvasElement[] = [];
|
|
108
|
+
|
|
109
|
+
for (let variant = 0; variant < SPRITE_COUNT; variant++) {
|
|
110
|
+
const canvas = document.createElement('canvas');
|
|
111
|
+
canvas.width = SPRITE_SIZE;
|
|
112
|
+
canvas.height = Math.floor(SPRITE_SIZE * 0.5);
|
|
113
|
+
const spriteCtx = canvas.getContext('2d')!;
|
|
114
|
+
|
|
115
|
+
const blobCount = 3 + Math.floor(MULBERRY.next() * 3);
|
|
116
|
+
const cx = SPRITE_SIZE * 0.5;
|
|
117
|
+
const cy = SPRITE_SIZE * 0.2;
|
|
118
|
+
|
|
119
|
+
const blobs: Array<{bx: number; by: number; br: number}> = [];
|
|
120
|
+
|
|
121
|
+
blobs.push({bx: cx, by: cy, br: SPRITE_SIZE * (0.18 + MULBERRY.next() * 0.08)});
|
|
122
|
+
|
|
123
|
+
for (let i = 1; i < blobCount; i++) {
|
|
124
|
+
const spread = SPRITE_SIZE * 0.28;
|
|
125
|
+
blobs.push({
|
|
126
|
+
bx: cx + (MULBERRY.next() - 0.5) * spread * 2,
|
|
127
|
+
by: cy + (MULBERRY.next() - 0.3) * spread * 0.5,
|
|
128
|
+
br: SPRITE_SIZE * (0.1 + MULBERRY.next() * 0.12)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const blob of blobs) {
|
|
133
|
+
const gradient = spriteCtx.createRadialGradient(
|
|
134
|
+
blob.bx, blob.by, 0,
|
|
135
|
+
blob.bx, blob.by, blob.br
|
|
136
|
+
);
|
|
137
|
+
gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.9)`);
|
|
138
|
+
gradient.addColorStop(0.35, `rgba(${r}, ${g}, ${b}, 0.7)`);
|
|
139
|
+
gradient.addColorStop(0.7, `rgba(${r}, ${g}, ${b}, 0.25)`);
|
|
140
|
+
gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`);
|
|
141
|
+
|
|
142
|
+
spriteCtx.fillStyle = gradient;
|
|
143
|
+
spriteCtx.beginPath();
|
|
144
|
+
spriteCtx.arc(blob.bx, blob.by, blob.br, 0, Math.PI * 2);
|
|
145
|
+
spriteCtx.fill();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
sprites.push(canvas);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return sprites;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#createCloud(initialSpread: boolean): Cloud {
|
|
155
|
+
const layer = Math.floor(MULBERRY.next() * 3);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
x: initialSpread ? MULBERRY.next() * 1.8 - 0.4 : -0.4,
|
|
159
|
+
y: 0.05 + MULBERRY.next() * 0.55,
|
|
160
|
+
speed: 0.5 + MULBERRY.next() * 0.5,
|
|
161
|
+
layer,
|
|
162
|
+
scale: 0.6 + MULBERRY.next() * 0.8 + layer * 0.3,
|
|
163
|
+
opacity: 0.6 + MULBERRY.next() * 0.4,
|
|
164
|
+
spriteIndex: Math.floor(MULBERRY.next() * SPRITE_COUNT)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
package/src/confetti/layer.ts
CHANGED
|
@@ -89,7 +89,8 @@ export class Confetti extends Effect<ConfettiConfig> {
|
|
|
89
89
|
const flipCos = Math.cos(p.flipAngle);
|
|
90
90
|
const size = p.size;
|
|
91
91
|
|
|
92
|
-
ctx.
|
|
92
|
+
ctx.save();
|
|
93
|
+
ctx.transform(
|
|
93
94
|
p.rotCos * flipCos * size,
|
|
94
95
|
p.rotSin * flipCos * size,
|
|
95
96
|
-p.rotSin * size,
|
|
@@ -100,9 +101,9 @@ export class Confetti extends Effect<ConfettiConfig> {
|
|
|
100
101
|
ctx.globalAlpha = 1 - p.tick / p.totalTicks;
|
|
101
102
|
ctx.fillStyle = p.colorStr;
|
|
102
103
|
ctx.fill(SHAPE_PATHS[p.shape]);
|
|
104
|
+
ctx.restore();
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
ctx.resetTransform();
|
|
106
107
|
ctx.globalAlpha = 1;
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Constellation } from './layer';
|
|
2
|
+
import type { ConstellationConfig } from './layer';
|
|
3
|
+
import type { Effect } from '../effect';
|
|
4
|
+
|
|
5
|
+
export function createConstellation(config?: ConstellationConfig): Effect<ConstellationConfig> {
|
|
6
|
+
return new Constellation(config);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type { ConstellationConfig };
|
|
10
|
+
export type { ConstellationStar } from './types';
|