celebrate-js 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/dist/index.cjs +486 -0
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +462 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License Copyright (c) 2025 akshaywritescode
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted,
|
|
4
|
+
free of charge, to any person obtaining a copy of this software and associated
|
|
5
|
+
documentation files (the "Software"), to deal in the Software without
|
|
6
|
+
restriction, including without limitation the rights to use, copy, modify, merge,
|
|
7
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice
|
|
12
|
+
(including the next paragraph) shall be included in all copies or substantial
|
|
13
|
+
portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
16
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
18
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
19
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# celebrate-js 🎉
|
|
2
|
+
|
|
3
|
+
A lightweight, zero-dependency JavaScript library to add beautiful celebration effects to your website - confetti, fireworks, balloons, emoji rain, and more.
|
|
4
|
+
|
|
5
|
+
Perfect for success screens, onboarding, achievements, or fun UI moments.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Multiple celebration effects
|
|
12
|
+
- Customizable colors 🎨
|
|
13
|
+
- Built-in and custom sound support
|
|
14
|
+
- Lightweight and fast
|
|
15
|
+
- Simple API
|
|
16
|
+
- TypeScript support
|
|
17
|
+
- Optional reduced-motion support
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install celebrate-js
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
or
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
yarn add celebrate-js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
import { celebrate } from "celebrate-js";
|
|
39
|
+
|
|
40
|
+
// Default: confetti
|
|
41
|
+
celebrate();
|
|
42
|
+
|
|
43
|
+
// Use a specific effect
|
|
44
|
+
celebrate.confetti();
|
|
45
|
+
celebrate.balloons({ particleCount: 20 });
|
|
46
|
+
celebrate.fireworks({ count: 6 });
|
|
47
|
+
celebrate.stars({ sound: true });
|
|
48
|
+
celebrate.popper({ sound: "/sounds/success.mp3" });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Effects
|
|
54
|
+
|
|
55
|
+
### Confetti
|
|
56
|
+
|
|
57
|
+
Small flat squares falling from the top of the screen.
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
celebrate.confetti({
|
|
61
|
+
duration: 2000,
|
|
62
|
+
particleCount: 150,
|
|
63
|
+
colors: ["#4285F4", "#34A853", "#FBBC05", "#EA4335"],
|
|
64
|
+
sound: true
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You can change:
|
|
69
|
+
|
|
70
|
+
- `duration` - how long the animation runs
|
|
71
|
+
- `particleCount` - number of pieces
|
|
72
|
+
- `colors` - colors used per piece
|
|
73
|
+
- `sound` - built-in chime or custom audio path
|
|
74
|
+
|
|
75
|
+
### Balloons
|
|
76
|
+
|
|
77
|
+
Flat balloons rising from the bottom of the screen.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
celebrate.balloons({
|
|
81
|
+
duration: 3000,
|
|
82
|
+
particleCount: 20,
|
|
83
|
+
colors: ["#4285F4", "#34A853", "#FBBC05", "#EA4335"],
|
|
84
|
+
sound: true
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
You can change:
|
|
89
|
+
|
|
90
|
+
- `duration` - how long balloons keep rising
|
|
91
|
+
- `particleCount` - number of balloons
|
|
92
|
+
- `colors` - colors used per balloon
|
|
93
|
+
- `sound` - built-in chime or custom audio path
|
|
94
|
+
|
|
95
|
+
### Popper
|
|
96
|
+
|
|
97
|
+
Confetti bursts from both bottom sides of the screen.
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
celebrate.popper({
|
|
101
|
+
duration: 3200,
|
|
102
|
+
particleCount: 120,
|
|
103
|
+
colors: ["#4285F4", "#34A853", "#FBBC05", "#EA4335"],
|
|
104
|
+
sound: "/sounds/success.mp3"
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
You can change:
|
|
109
|
+
|
|
110
|
+
- `duration` - how long the burst runs
|
|
111
|
+
- `particleCount` - number of particles
|
|
112
|
+
- `colors` - colors used per particle
|
|
113
|
+
- `sound` - built-in chime or custom audio path
|
|
114
|
+
|
|
115
|
+
### Fireworks
|
|
116
|
+
|
|
117
|
+
Circular bursts that appear across the upper half of the screen.
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
celebrate.fireworks({
|
|
121
|
+
duration: 4000,
|
|
122
|
+
count: 5,
|
|
123
|
+
interval: 500,
|
|
124
|
+
colors: ["#4285F4", "#34A853", "#FBBC05", "#EA4335"]
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
You can change:
|
|
129
|
+
|
|
130
|
+
- `duration` - total animation time
|
|
131
|
+
- `count` - number of bursts
|
|
132
|
+
- `interval` - delay between bursts in milliseconds
|
|
133
|
+
- `colors` - colors used per burst
|
|
134
|
+
- `sound` - built-in chime or custom audio path
|
|
135
|
+
|
|
136
|
+
### Bubbles
|
|
137
|
+
|
|
138
|
+
Soft bubbles floating upward.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
celebrate.bubbles({
|
|
142
|
+
duration: 3000,
|
|
143
|
+
particleCount: 25
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
You can change:
|
|
148
|
+
|
|
149
|
+
- `duration` - how long bubbles keep floating
|
|
150
|
+
- `particleCount` - number of bubbles
|
|
151
|
+
- `sound` - built-in chime or custom audio path
|
|
152
|
+
|
|
153
|
+
### Emojis
|
|
154
|
+
|
|
155
|
+
Emoji rain falling from the top of the screen.
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
celebrate.emojis({
|
|
159
|
+
duration: 3000,
|
|
160
|
+
particleCount: 30,
|
|
161
|
+
emojis: ["🎉", "✨", "🔥", "💯", "😍"]
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
You can change:
|
|
166
|
+
|
|
167
|
+
- `duration` - how long emojis fall
|
|
168
|
+
- `particleCount` - number of emojis
|
|
169
|
+
- `emojis` - custom emoji list
|
|
170
|
+
- `sound` - built-in chime or custom audio path
|
|
171
|
+
|
|
172
|
+
### Stars
|
|
173
|
+
|
|
174
|
+
Shooting stars crossing the screen.
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
celebrate.stars({
|
|
178
|
+
duration: 3000,
|
|
179
|
+
count: 5,
|
|
180
|
+
interval: 500
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
You can change:
|
|
185
|
+
|
|
186
|
+
- `duration` - total animation time
|
|
187
|
+
- `count` - number of shooting stars
|
|
188
|
+
- `interval` - delay between stars in milliseconds
|
|
189
|
+
- `sound` - built-in chime or custom audio path
|
|
190
|
+
|
|
191
|
+
### Snowfall
|
|
192
|
+
|
|
193
|
+
Light snow falling from the top of the screen.
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
celebrate.snowfall({
|
|
197
|
+
duration: 5000,
|
|
198
|
+
particleCount: 60
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
You can change:
|
|
203
|
+
|
|
204
|
+
- `duration` - how long snow keeps falling
|
|
205
|
+
- `particleCount` - number of flakes
|
|
206
|
+
- `sound` - built-in chime or custom audio path
|
|
207
|
+
|
|
208
|
+
## API
|
|
209
|
+
|
|
210
|
+
### celebrate(options?)
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
celebrate({
|
|
214
|
+
effect: "confetti",
|
|
215
|
+
duration: 5000,
|
|
216
|
+
particleCount: 100,
|
|
217
|
+
colors: ["#ff0", "#0ff"],
|
|
218
|
+
sound: true,
|
|
219
|
+
soundVolume: 0.35
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Shortcuts
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
celebrate.confetti(options);
|
|
229
|
+
celebrate.balloons(options);
|
|
230
|
+
celebrate.popper(options);
|
|
231
|
+
celebrate.fireworks(options);
|
|
232
|
+
celebrate.bubbles(options);
|
|
233
|
+
celebrate.emojis(options);
|
|
234
|
+
celebrate.stars(options);
|
|
235
|
+
celebrate.snowfall(options);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Options
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
type CelebrateOptions = {
|
|
244
|
+
effect?: "confetti" | "balloons" | "popper" | "fireworks" | "bubbles" | "emojis" | "stars" | "snowfall";
|
|
245
|
+
duration?: number;
|
|
246
|
+
particleCount?: number;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Colors used by supported effects (confetti, balloons, fireworks, popper, stars)
|
|
250
|
+
*/
|
|
251
|
+
colors?: string[];
|
|
252
|
+
|
|
253
|
+
respectReducedMotion?: boolean;
|
|
254
|
+
sound?: boolean | string;
|
|
255
|
+
soundVolume?: number;
|
|
256
|
+
|
|
257
|
+
// Fireworks only
|
|
258
|
+
count?: number;
|
|
259
|
+
interval?: number;
|
|
260
|
+
|
|
261
|
+
// Emoji rain only
|
|
262
|
+
emojis?: string[];
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Defaults use a modern flat Google-style palette:
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
["#4285F4", "#34A853", "#FBBC05", "#EA4335"]
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Sound
|
|
275
|
+
|
|
276
|
+
Sound works with every effect and is off by default.
|
|
277
|
+
|
|
278
|
+
Use the built-in completion chime:
|
|
279
|
+
|
|
280
|
+
```js
|
|
281
|
+
celebrate.balloons({
|
|
282
|
+
sound: true
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Use your own audio file by passing a path or URL:
|
|
287
|
+
|
|
288
|
+
```js
|
|
289
|
+
celebrate.balloons({
|
|
290
|
+
sound: "/sounds/success.mp3"
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
In Vite, files inside `public/` are served from the root:
|
|
295
|
+
|
|
296
|
+
```text
|
|
297
|
+
public/success.mp3 -> /success.mp3
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Adjust volume:
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
celebrate.fireworks({
|
|
304
|
+
sound: "/sounds/success.mp3",
|
|
305
|
+
soundVolume: 0.2
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Sound is off by default. Browsers usually require sound to start from a user action, like a button click.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Reduced Motion
|
|
314
|
+
|
|
315
|
+
If `respectReducedMotion` is enabled and the user has `prefers-reduced-motion` set,
|
|
316
|
+
animations will be skipped.
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
celebrate({
|
|
320
|
+
respectReducedMotion: true
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Local Demo
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
npm run dev
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Open `http://localhost:5173/` and click the demo button.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## License
|
|
337
|
+
|
|
338
|
+
MIT © Akshay Yadav
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
celebrate: () => celebrate
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(index_exports);
|
|
25
|
+
var googleColors = ["#4285F4", "#34A853", "#FBBC05", "#EA4335"];
|
|
26
|
+
function shouldSkip(options) {
|
|
27
|
+
return options.respectReducedMotion && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
28
|
+
}
|
|
29
|
+
function createCanvas() {
|
|
30
|
+
const canvas = document.createElement("canvas");
|
|
31
|
+
const ctx = canvas.getContext("2d");
|
|
32
|
+
if (!ctx) {
|
|
33
|
+
throw new Error("Canvas 2D context is not available.");
|
|
34
|
+
}
|
|
35
|
+
canvas.style.position = "fixed";
|
|
36
|
+
canvas.style.top = "0";
|
|
37
|
+
canvas.style.left = "0";
|
|
38
|
+
canvas.style.width = "100vw";
|
|
39
|
+
canvas.style.height = "100vh";
|
|
40
|
+
canvas.style.pointerEvents = "none";
|
|
41
|
+
canvas.style.zIndex = "9999";
|
|
42
|
+
document.body.appendChild(canvas);
|
|
43
|
+
const resize = () => {
|
|
44
|
+
canvas.width = window.innerWidth;
|
|
45
|
+
canvas.height = window.innerHeight;
|
|
46
|
+
};
|
|
47
|
+
resize();
|
|
48
|
+
window.addEventListener("resize", resize);
|
|
49
|
+
return {
|
|
50
|
+
canvas,
|
|
51
|
+
ctx,
|
|
52
|
+
cleanup: () => {
|
|
53
|
+
window.removeEventListener("resize", resize);
|
|
54
|
+
canvas.remove();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function playCompletionSound(volume = 0.35) {
|
|
59
|
+
const AudioContextClass = window.AudioContext ?? window.webkitAudioContext;
|
|
60
|
+
if (!AudioContextClass) return;
|
|
61
|
+
const audio = new AudioContextClass();
|
|
62
|
+
const master = audio.createGain();
|
|
63
|
+
const now = audio.currentTime;
|
|
64
|
+
const safeVolume = Math.min(Math.max(volume, 0), 1);
|
|
65
|
+
const tone = (frequency, startOffset, duration, type, level) => {
|
|
66
|
+
const oscillator = audio.createOscillator();
|
|
67
|
+
const gain = audio.createGain();
|
|
68
|
+
const start = now + startOffset;
|
|
69
|
+
const end = start + duration;
|
|
70
|
+
oscillator.type = type;
|
|
71
|
+
oscillator.frequency.setValueAtTime(frequency, start);
|
|
72
|
+
oscillator.frequency.exponentialRampToValueAtTime(frequency * 1.015, end);
|
|
73
|
+
gain.gain.setValueAtTime(1e-4, start);
|
|
74
|
+
gain.gain.exponentialRampToValueAtTime(level, start + 0.012);
|
|
75
|
+
gain.gain.exponentialRampToValueAtTime(1e-4, end);
|
|
76
|
+
oscillator.connect(gain);
|
|
77
|
+
gain.connect(master);
|
|
78
|
+
oscillator.start(start);
|
|
79
|
+
oscillator.stop(end + 0.03);
|
|
80
|
+
};
|
|
81
|
+
const notes = [
|
|
82
|
+
{ frequency: 523.25, start: 0, duration: 0.09 },
|
|
83
|
+
{ frequency: 659.25, start: 0.08, duration: 0.1 },
|
|
84
|
+
{ frequency: 783.99, start: 0.16, duration: 0.12 },
|
|
85
|
+
{ frequency: 1046.5, start: 0.28, duration: 0.22 }
|
|
86
|
+
];
|
|
87
|
+
const sparkles = [
|
|
88
|
+
{ frequency: 1567.98, start: 0.2 },
|
|
89
|
+
{ frequency: 2093, start: 0.32 },
|
|
90
|
+
{ frequency: 2637.02, start: 0.42 }
|
|
91
|
+
];
|
|
92
|
+
master.gain.setValueAtTime(1e-4, now);
|
|
93
|
+
master.gain.exponentialRampToValueAtTime(safeVolume, now + 0.02);
|
|
94
|
+
master.gain.exponentialRampToValueAtTime(1e-4, now + 0.78);
|
|
95
|
+
master.connect(audio.destination);
|
|
96
|
+
notes.forEach((note) => {
|
|
97
|
+
tone(note.frequency, note.start, note.duration, "triangle", 0.9);
|
|
98
|
+
tone(note.frequency * 2, note.start + 0.01, note.duration * 0.55, "sine", 0.18);
|
|
99
|
+
});
|
|
100
|
+
sparkles.forEach((sparkle) => {
|
|
101
|
+
tone(sparkle.frequency, sparkle.start, 0.055, "sine", 0.22);
|
|
102
|
+
});
|
|
103
|
+
window.setTimeout(() => {
|
|
104
|
+
void audio.close();
|
|
105
|
+
}, 1e3);
|
|
106
|
+
}
|
|
107
|
+
function playSound(sound, volume = 0.35) {
|
|
108
|
+
if (typeof sound === "string") {
|
|
109
|
+
const audio = new Audio(sound);
|
|
110
|
+
audio.volume = Math.min(Math.max(volume, 0), 1);
|
|
111
|
+
void audio.play();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (sound === true) {
|
|
115
|
+
playCompletionSound(volume);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function confetti(options = {}) {
|
|
119
|
+
var _a;
|
|
120
|
+
if (shouldSkip(options)) return;
|
|
121
|
+
const duration = options.duration ?? 2e3;
|
|
122
|
+
const particleCount = options.particleCount ?? 150;
|
|
123
|
+
const colors = ((_a = options.colors) == null ? void 0 : _a.length) ? options.colors : googleColors;
|
|
124
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
125
|
+
const particles = Array.from({ length: particleCount }).map(() => ({
|
|
126
|
+
x: Math.random() * canvas.width,
|
|
127
|
+
y: Math.random() * canvas.height - canvas.height,
|
|
128
|
+
size: Math.random() * 6 + 4,
|
|
129
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
130
|
+
velocityX: Math.random() * 4 - 2,
|
|
131
|
+
velocityY: Math.random() * 6 + 4,
|
|
132
|
+
rotation: Math.random() * 360
|
|
133
|
+
}));
|
|
134
|
+
const start = performance.now();
|
|
135
|
+
function draw(now) {
|
|
136
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
137
|
+
for (const p of particles) {
|
|
138
|
+
p.x += p.velocityX;
|
|
139
|
+
p.y += p.velocityY;
|
|
140
|
+
p.rotation += 5;
|
|
141
|
+
ctx.save();
|
|
142
|
+
ctx.translate(p.x, p.y);
|
|
143
|
+
ctx.rotate(p.rotation * Math.PI / 180);
|
|
144
|
+
ctx.fillStyle = p.color;
|
|
145
|
+
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
|
|
146
|
+
ctx.restore();
|
|
147
|
+
}
|
|
148
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
149
|
+
}
|
|
150
|
+
requestAnimationFrame(draw);
|
|
151
|
+
}
|
|
152
|
+
function balloons(options = {}) {
|
|
153
|
+
var _a;
|
|
154
|
+
if (shouldSkip(options)) return;
|
|
155
|
+
const duration = options.duration ?? 3e3;
|
|
156
|
+
const count = options.particleCount ?? 20;
|
|
157
|
+
const colors = ((_a = options.colors) == null ? void 0 : _a.length) ? options.colors : googleColors;
|
|
158
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
159
|
+
const items = Array.from({ length: count }).map(() => {
|
|
160
|
+
const radius = Math.random() * 20 + 20;
|
|
161
|
+
return {
|
|
162
|
+
x: Math.random() * canvas.width,
|
|
163
|
+
y: canvas.height + radius + Math.random() * 120,
|
|
164
|
+
radius,
|
|
165
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
166
|
+
speed: Math.random() * 2 + 2.8,
|
|
167
|
+
sway: Math.random() * 2 + 1
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
const start = performance.now();
|
|
171
|
+
function draw(now) {
|
|
172
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
173
|
+
items.forEach((b, i) => {
|
|
174
|
+
b.y -= b.speed;
|
|
175
|
+
b.x += Math.sin(now / 300 + i) * b.sway;
|
|
176
|
+
ctx.fillStyle = b.color;
|
|
177
|
+
ctx.beginPath();
|
|
178
|
+
ctx.ellipse(b.x, b.y, b.radius * 0.8, b.radius, 0, 0, Math.PI * 2);
|
|
179
|
+
ctx.fill();
|
|
180
|
+
ctx.beginPath();
|
|
181
|
+
ctx.moveTo(b.x - b.radius * 0.18, b.y + b.radius * 0.78);
|
|
182
|
+
ctx.lineTo(b.x + b.radius * 0.18, b.y + b.radius * 0.78);
|
|
183
|
+
ctx.lineTo(b.x, b.y + b.radius * 1.08);
|
|
184
|
+
ctx.closePath();
|
|
185
|
+
ctx.fill();
|
|
186
|
+
ctx.beginPath();
|
|
187
|
+
ctx.moveTo(b.x, b.y + b.radius);
|
|
188
|
+
ctx.quadraticCurveTo(
|
|
189
|
+
b.x + Math.sin(now / 150 + i) * b.sway * 4,
|
|
190
|
+
b.y + b.radius + 17.5,
|
|
191
|
+
b.x,
|
|
192
|
+
b.y + b.radius + 35
|
|
193
|
+
);
|
|
194
|
+
ctx.strokeStyle = "#9AA0A6";
|
|
195
|
+
ctx.lineWidth = 1;
|
|
196
|
+
ctx.stroke();
|
|
197
|
+
});
|
|
198
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
199
|
+
}
|
|
200
|
+
requestAnimationFrame(draw);
|
|
201
|
+
}
|
|
202
|
+
function popper(options = {}) {
|
|
203
|
+
var _a;
|
|
204
|
+
if (shouldSkip(options)) return;
|
|
205
|
+
const duration = options.duration ?? 3200;
|
|
206
|
+
const particleCount = options.particleCount ?? 120;
|
|
207
|
+
const colors = ((_a = options.colors) == null ? void 0 : _a.length) ? options.colors : googleColors;
|
|
208
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
209
|
+
const particles = [];
|
|
210
|
+
for (const side of ["left", "right"]) {
|
|
211
|
+
for (let i = 0; i < particleCount / 2; i++) {
|
|
212
|
+
const direction = side === "left" ? 1 : -1;
|
|
213
|
+
const angle = Math.random() * 0.75 + 0.2;
|
|
214
|
+
const power = Math.random() * 8 + 10;
|
|
215
|
+
particles.push({
|
|
216
|
+
x: side === "left" ? canvas.width * 0.08 : canvas.width * 0.92,
|
|
217
|
+
y: canvas.height * 0.82,
|
|
218
|
+
size: Math.random() * 6 + 4,
|
|
219
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
220
|
+
vx: Math.cos(angle) * power * direction,
|
|
221
|
+
vy: -Math.sin(angle) * power - Math.random() * 6,
|
|
222
|
+
rotation: Math.random() * 360,
|
|
223
|
+
rotationSpeed: Math.random() * 12 - 6
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const start = performance.now();
|
|
228
|
+
function draw(now) {
|
|
229
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
230
|
+
particles.forEach((p) => {
|
|
231
|
+
p.vy += 0.32;
|
|
232
|
+
p.vx *= 0.992;
|
|
233
|
+
p.vy *= 0.997;
|
|
234
|
+
p.x += p.vx;
|
|
235
|
+
p.y += p.vy;
|
|
236
|
+
p.rotation += p.rotationSpeed;
|
|
237
|
+
ctx.save();
|
|
238
|
+
ctx.translate(p.x, p.y);
|
|
239
|
+
ctx.rotate(p.rotation * Math.PI / 180);
|
|
240
|
+
ctx.fillStyle = p.color;
|
|
241
|
+
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
|
|
242
|
+
ctx.restore();
|
|
243
|
+
});
|
|
244
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
245
|
+
}
|
|
246
|
+
requestAnimationFrame(draw);
|
|
247
|
+
}
|
|
248
|
+
function fireworks(options = {}) {
|
|
249
|
+
var _a;
|
|
250
|
+
if (shouldSkip(options)) return;
|
|
251
|
+
const duration = options.duration ?? 4e3;
|
|
252
|
+
const burstCount = options.count ?? 5;
|
|
253
|
+
const interval = options.interval ?? 500;
|
|
254
|
+
const colors = ((_a = options.colors) == null ? void 0 : _a.length) ? options.colors : googleColors;
|
|
255
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
256
|
+
let particles = [];
|
|
257
|
+
let bursts = 0;
|
|
258
|
+
let lastBurst = 0;
|
|
259
|
+
const start = performance.now();
|
|
260
|
+
function createBurst() {
|
|
261
|
+
const x = Math.random() * canvas.width;
|
|
262
|
+
const y = Math.random() * canvas.height * 0.5;
|
|
263
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
264
|
+
for (let i = 0; i < 60; i++) {
|
|
265
|
+
const angle = Math.PI * 2 * i / 60;
|
|
266
|
+
const speed = Math.random() * 4 + 2;
|
|
267
|
+
particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, alpha: 1, color });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function draw(now) {
|
|
271
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
272
|
+
if (bursts < burstCount && now - lastBurst > interval) {
|
|
273
|
+
createBurst();
|
|
274
|
+
bursts++;
|
|
275
|
+
lastBurst = now;
|
|
276
|
+
}
|
|
277
|
+
particles.forEach((p) => {
|
|
278
|
+
p.x += p.vx;
|
|
279
|
+
p.y += p.vy;
|
|
280
|
+
p.vy += 0.04;
|
|
281
|
+
p.alpha -= 0.015;
|
|
282
|
+
ctx.globalAlpha = Math.max(p.alpha, 0);
|
|
283
|
+
ctx.beginPath();
|
|
284
|
+
ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
|
|
285
|
+
ctx.fillStyle = p.color;
|
|
286
|
+
ctx.fill();
|
|
287
|
+
});
|
|
288
|
+
ctx.globalAlpha = 1;
|
|
289
|
+
particles = particles.filter((p) => p.alpha > 0);
|
|
290
|
+
now - start < duration || particles.length > 0 ? requestAnimationFrame(draw) : cleanup();
|
|
291
|
+
}
|
|
292
|
+
requestAnimationFrame(draw);
|
|
293
|
+
}
|
|
294
|
+
function bubbles(options = {}) {
|
|
295
|
+
if (shouldSkip(options)) return;
|
|
296
|
+
const duration = options.duration ?? 3e3;
|
|
297
|
+
const count = options.particleCount ?? 25;
|
|
298
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
299
|
+
const items = Array.from({ length: count }).map(() => ({
|
|
300
|
+
x: Math.random() * canvas.width,
|
|
301
|
+
y: canvas.height + Math.random() * 100,
|
|
302
|
+
radius: Math.random() * 15 + 10,
|
|
303
|
+
speed: Math.random() * 2.1 + 1.4,
|
|
304
|
+
drift: Math.random() * 1.5 - 0.75,
|
|
305
|
+
alpha: Math.random() * 0.4 + 0.4
|
|
306
|
+
}));
|
|
307
|
+
const start = performance.now();
|
|
308
|
+
function draw(now) {
|
|
309
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
310
|
+
items.forEach((b) => {
|
|
311
|
+
b.y -= b.speed;
|
|
312
|
+
b.x += Math.sin(now / 500) * b.drift;
|
|
313
|
+
if (b.y < canvas.height * 0.3) b.alpha = Math.max(0, b.alpha - 0.01);
|
|
314
|
+
ctx.beginPath();
|
|
315
|
+
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
|
|
316
|
+
ctx.fillStyle = `rgba(180, 220, 255, ${b.alpha * 0.4})`;
|
|
317
|
+
ctx.fill();
|
|
318
|
+
ctx.strokeStyle = `rgba(255,255,255,${b.alpha})`;
|
|
319
|
+
ctx.lineWidth = 2;
|
|
320
|
+
ctx.stroke();
|
|
321
|
+
});
|
|
322
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
323
|
+
}
|
|
324
|
+
requestAnimationFrame(draw);
|
|
325
|
+
}
|
|
326
|
+
function emojis(options = {}) {
|
|
327
|
+
if (shouldSkip(options)) return;
|
|
328
|
+
const duration = options.duration ?? 3e3;
|
|
329
|
+
const count = options.particleCount ?? 30;
|
|
330
|
+
const emojiSet = options.emojis ?? ["\u{1F389}", "\u2728", "\u{1F525}", "\u{1F4AF}", "\u{1F60D}"];
|
|
331
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
332
|
+
const items = Array.from({ length: count }).map(() => ({
|
|
333
|
+
x: Math.random() * canvas.width,
|
|
334
|
+
y: Math.random() * -canvas.height,
|
|
335
|
+
char: emojiSet[Math.floor(Math.random() * emojiSet.length)],
|
|
336
|
+
size: Math.random() * 20 + 20,
|
|
337
|
+
speed: Math.random() * 2.5 + 1.5,
|
|
338
|
+
drift: Math.random() * 1.5 - 0.75,
|
|
339
|
+
rotation: Math.random() * 360,
|
|
340
|
+
rotationSpeed: Math.random() * 4 - 2,
|
|
341
|
+
alpha: 1
|
|
342
|
+
}));
|
|
343
|
+
const start = performance.now();
|
|
344
|
+
function draw(now) {
|
|
345
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
346
|
+
items.forEach((e) => {
|
|
347
|
+
e.y += e.speed;
|
|
348
|
+
e.x += Math.sin(now / 400) * e.drift;
|
|
349
|
+
e.rotation += e.rotationSpeed;
|
|
350
|
+
if (e.y > canvas.height * 0.8) e.alpha = Math.max(0, e.alpha - 0.02);
|
|
351
|
+
ctx.save();
|
|
352
|
+
ctx.translate(e.x, e.y);
|
|
353
|
+
ctx.rotate(e.rotation * Math.PI / 180);
|
|
354
|
+
ctx.globalAlpha = e.alpha;
|
|
355
|
+
ctx.font = `${e.size}px serif`;
|
|
356
|
+
ctx.textAlign = "center";
|
|
357
|
+
ctx.textBaseline = "middle";
|
|
358
|
+
ctx.fillText(e.char, 0, 0);
|
|
359
|
+
ctx.restore();
|
|
360
|
+
});
|
|
361
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
362
|
+
}
|
|
363
|
+
requestAnimationFrame(draw);
|
|
364
|
+
}
|
|
365
|
+
function stars(options = {}) {
|
|
366
|
+
if (shouldSkip(options)) return;
|
|
367
|
+
const duration = options.duration ?? 3e3;
|
|
368
|
+
const count = options.count ?? 5;
|
|
369
|
+
const interval = options.interval ?? 500;
|
|
370
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
371
|
+
const items = [];
|
|
372
|
+
const start = performance.now();
|
|
373
|
+
let spawned = 0;
|
|
374
|
+
let lastSpawn = 0;
|
|
375
|
+
function spawnStar() {
|
|
376
|
+
const fromLeft = Math.random() > 0.5;
|
|
377
|
+
items.push({
|
|
378
|
+
x: fromLeft ? -100 : canvas.width + 100,
|
|
379
|
+
y: Math.random() * canvas.height * 0.4,
|
|
380
|
+
vx: fromLeft ? Math.random() * 12 + 8 : -(Math.random() * 12 + 8),
|
|
381
|
+
vy: Math.random() * 6 + 4,
|
|
382
|
+
alpha: 1
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function draw(now) {
|
|
386
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
387
|
+
if (spawned < count && now - lastSpawn > interval) {
|
|
388
|
+
spawnStar();
|
|
389
|
+
spawned++;
|
|
390
|
+
lastSpawn = now;
|
|
391
|
+
}
|
|
392
|
+
items.forEach((s, i) => {
|
|
393
|
+
s.x += s.vx;
|
|
394
|
+
s.y += s.vy;
|
|
395
|
+
s.alpha -= 0.015;
|
|
396
|
+
ctx.beginPath();
|
|
397
|
+
ctx.moveTo(s.x, s.y);
|
|
398
|
+
ctx.lineTo(s.x - s.vx * 4, s.y - s.vy * 4);
|
|
399
|
+
ctx.strokeStyle = `rgba(251,188,5,${s.alpha})`;
|
|
400
|
+
ctx.lineWidth = 2;
|
|
401
|
+
ctx.lineCap = "round";
|
|
402
|
+
ctx.stroke();
|
|
403
|
+
if (s.alpha <= 0) items.splice(i, 1);
|
|
404
|
+
});
|
|
405
|
+
now - start < duration || items.length > 0 ? requestAnimationFrame(draw) : cleanup();
|
|
406
|
+
}
|
|
407
|
+
requestAnimationFrame(draw);
|
|
408
|
+
}
|
|
409
|
+
function snowfall(options = {}) {
|
|
410
|
+
if (shouldSkip(options)) return;
|
|
411
|
+
const duration = options.duration ?? 5e3;
|
|
412
|
+
const count = options.particleCount ?? 60;
|
|
413
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
414
|
+
const flakes = Array.from({ length: count }).map(() => ({
|
|
415
|
+
x: Math.random() * canvas.width,
|
|
416
|
+
y: Math.random() * -canvas.height,
|
|
417
|
+
radius: Math.random() * 2.5 + 1,
|
|
418
|
+
speed: Math.random() * 0.6 + 0.3,
|
|
419
|
+
drift: Math.random() * 0.6 - 0.3,
|
|
420
|
+
alpha: Math.random() * 0.5 + 0.4
|
|
421
|
+
}));
|
|
422
|
+
const start = performance.now();
|
|
423
|
+
function draw(now) {
|
|
424
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
425
|
+
flakes.forEach((f) => {
|
|
426
|
+
f.y += f.speed;
|
|
427
|
+
f.x += Math.sin(now / 800) * f.drift;
|
|
428
|
+
if (f.y > canvas.height) {
|
|
429
|
+
f.y = -10;
|
|
430
|
+
f.x = Math.random() * canvas.width;
|
|
431
|
+
}
|
|
432
|
+
ctx.beginPath();
|
|
433
|
+
ctx.arc(f.x, f.y, f.radius, 0, Math.PI * 2);
|
|
434
|
+
ctx.fillStyle = `rgba(255,255,255,${f.alpha})`;
|
|
435
|
+
ctx.fill();
|
|
436
|
+
});
|
|
437
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
438
|
+
}
|
|
439
|
+
requestAnimationFrame(draw);
|
|
440
|
+
}
|
|
441
|
+
var celebrate = Object.assign((options = {}) => {
|
|
442
|
+
if (shouldSkip(options)) return;
|
|
443
|
+
if (options.sound) {
|
|
444
|
+
playSound(options.sound, options.soundVolume);
|
|
445
|
+
}
|
|
446
|
+
const effect = options.effect ?? "confetti";
|
|
447
|
+
switch (effect) {
|
|
448
|
+
case "confetti":
|
|
449
|
+
confetti(options);
|
|
450
|
+
break;
|
|
451
|
+
case "balloons":
|
|
452
|
+
balloons(options);
|
|
453
|
+
break;
|
|
454
|
+
case "popper":
|
|
455
|
+
popper(options);
|
|
456
|
+
break;
|
|
457
|
+
case "fireworks":
|
|
458
|
+
fireworks(options);
|
|
459
|
+
break;
|
|
460
|
+
case "bubbles":
|
|
461
|
+
bubbles(options);
|
|
462
|
+
break;
|
|
463
|
+
case "emojis":
|
|
464
|
+
emojis(options);
|
|
465
|
+
break;
|
|
466
|
+
case "stars":
|
|
467
|
+
stars(options);
|
|
468
|
+
break;
|
|
469
|
+
case "snowfall":
|
|
470
|
+
snowfall(options);
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}, {
|
|
474
|
+
confetti: (o) => celebrate({ ...o, effect: "confetti" }),
|
|
475
|
+
balloons: (o) => celebrate({ ...o, effect: "balloons" }),
|
|
476
|
+
popper: (o) => celebrate({ ...o, effect: "popper" }),
|
|
477
|
+
fireworks: (o) => celebrate({ ...o, effect: "fireworks" }),
|
|
478
|
+
bubbles: (o) => celebrate({ ...o, effect: "bubbles" }),
|
|
479
|
+
emojis: (o) => celebrate({ ...o, effect: "emojis" }),
|
|
480
|
+
stars: (o) => celebrate({ ...o, effect: "stars" }),
|
|
481
|
+
snowfall: (o) => celebrate({ ...o, effect: "snowfall" })
|
|
482
|
+
});
|
|
483
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
484
|
+
0 && (module.exports = {
|
|
485
|
+
celebrate
|
|
486
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type CelebrateEffect = "confetti" | "balloons" | "popper" | "fireworks" | "bubbles" | "emojis" | "stars" | "snowfall";
|
|
2
|
+
interface CelebrateOptions {
|
|
3
|
+
effect?: CelebrateEffect;
|
|
4
|
+
duration?: number;
|
|
5
|
+
particleCount?: number;
|
|
6
|
+
colors?: string[];
|
|
7
|
+
respectReducedMotion?: boolean;
|
|
8
|
+
sound?: boolean | string;
|
|
9
|
+
soundVolume?: number;
|
|
10
|
+
count?: number;
|
|
11
|
+
interval?: number;
|
|
12
|
+
emojis?: string[];
|
|
13
|
+
}
|
|
14
|
+
type CelebrateShortcut = (options?: CelebrateOptions) => void;
|
|
15
|
+
interface Celebrate {
|
|
16
|
+
(options?: CelebrateOptions): void;
|
|
17
|
+
confetti: CelebrateShortcut;
|
|
18
|
+
balloons: CelebrateShortcut;
|
|
19
|
+
popper: CelebrateShortcut;
|
|
20
|
+
fireworks: CelebrateShortcut;
|
|
21
|
+
bubbles: CelebrateShortcut;
|
|
22
|
+
emojis: CelebrateShortcut;
|
|
23
|
+
stars: CelebrateShortcut;
|
|
24
|
+
snowfall: CelebrateShortcut;
|
|
25
|
+
}
|
|
26
|
+
declare const celebrate: Celebrate;
|
|
27
|
+
|
|
28
|
+
export { type Celebrate, type CelebrateEffect, type CelebrateOptions, celebrate };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type CelebrateEffect = "confetti" | "balloons" | "popper" | "fireworks" | "bubbles" | "emojis" | "stars" | "snowfall";
|
|
2
|
+
interface CelebrateOptions {
|
|
3
|
+
effect?: CelebrateEffect;
|
|
4
|
+
duration?: number;
|
|
5
|
+
particleCount?: number;
|
|
6
|
+
colors?: string[];
|
|
7
|
+
respectReducedMotion?: boolean;
|
|
8
|
+
sound?: boolean | string;
|
|
9
|
+
soundVolume?: number;
|
|
10
|
+
count?: number;
|
|
11
|
+
interval?: number;
|
|
12
|
+
emojis?: string[];
|
|
13
|
+
}
|
|
14
|
+
type CelebrateShortcut = (options?: CelebrateOptions) => void;
|
|
15
|
+
interface Celebrate {
|
|
16
|
+
(options?: CelebrateOptions): void;
|
|
17
|
+
confetti: CelebrateShortcut;
|
|
18
|
+
balloons: CelebrateShortcut;
|
|
19
|
+
popper: CelebrateShortcut;
|
|
20
|
+
fireworks: CelebrateShortcut;
|
|
21
|
+
bubbles: CelebrateShortcut;
|
|
22
|
+
emojis: CelebrateShortcut;
|
|
23
|
+
stars: CelebrateShortcut;
|
|
24
|
+
snowfall: CelebrateShortcut;
|
|
25
|
+
}
|
|
26
|
+
declare const celebrate: Celebrate;
|
|
27
|
+
|
|
28
|
+
export { type Celebrate, type CelebrateEffect, type CelebrateOptions, celebrate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var googleColors = ["#4285F4", "#34A853", "#FBBC05", "#EA4335"];
|
|
3
|
+
function shouldSkip(options) {
|
|
4
|
+
return options.respectReducedMotion && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
5
|
+
}
|
|
6
|
+
function createCanvas() {
|
|
7
|
+
const canvas = document.createElement("canvas");
|
|
8
|
+
const ctx = canvas.getContext("2d");
|
|
9
|
+
if (!ctx) {
|
|
10
|
+
throw new Error("Canvas 2D context is not available.");
|
|
11
|
+
}
|
|
12
|
+
canvas.style.position = "fixed";
|
|
13
|
+
canvas.style.top = "0";
|
|
14
|
+
canvas.style.left = "0";
|
|
15
|
+
canvas.style.width = "100vw";
|
|
16
|
+
canvas.style.height = "100vh";
|
|
17
|
+
canvas.style.pointerEvents = "none";
|
|
18
|
+
canvas.style.zIndex = "9999";
|
|
19
|
+
document.body.appendChild(canvas);
|
|
20
|
+
const resize = () => {
|
|
21
|
+
canvas.width = window.innerWidth;
|
|
22
|
+
canvas.height = window.innerHeight;
|
|
23
|
+
};
|
|
24
|
+
resize();
|
|
25
|
+
window.addEventListener("resize", resize);
|
|
26
|
+
return {
|
|
27
|
+
canvas,
|
|
28
|
+
ctx,
|
|
29
|
+
cleanup: () => {
|
|
30
|
+
window.removeEventListener("resize", resize);
|
|
31
|
+
canvas.remove();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function playCompletionSound(volume = 0.35) {
|
|
36
|
+
const AudioContextClass = window.AudioContext ?? window.webkitAudioContext;
|
|
37
|
+
if (!AudioContextClass) return;
|
|
38
|
+
const audio = new AudioContextClass();
|
|
39
|
+
const master = audio.createGain();
|
|
40
|
+
const now = audio.currentTime;
|
|
41
|
+
const safeVolume = Math.min(Math.max(volume, 0), 1);
|
|
42
|
+
const tone = (frequency, startOffset, duration, type, level) => {
|
|
43
|
+
const oscillator = audio.createOscillator();
|
|
44
|
+
const gain = audio.createGain();
|
|
45
|
+
const start = now + startOffset;
|
|
46
|
+
const end = start + duration;
|
|
47
|
+
oscillator.type = type;
|
|
48
|
+
oscillator.frequency.setValueAtTime(frequency, start);
|
|
49
|
+
oscillator.frequency.exponentialRampToValueAtTime(frequency * 1.015, end);
|
|
50
|
+
gain.gain.setValueAtTime(1e-4, start);
|
|
51
|
+
gain.gain.exponentialRampToValueAtTime(level, start + 0.012);
|
|
52
|
+
gain.gain.exponentialRampToValueAtTime(1e-4, end);
|
|
53
|
+
oscillator.connect(gain);
|
|
54
|
+
gain.connect(master);
|
|
55
|
+
oscillator.start(start);
|
|
56
|
+
oscillator.stop(end + 0.03);
|
|
57
|
+
};
|
|
58
|
+
const notes = [
|
|
59
|
+
{ frequency: 523.25, start: 0, duration: 0.09 },
|
|
60
|
+
{ frequency: 659.25, start: 0.08, duration: 0.1 },
|
|
61
|
+
{ frequency: 783.99, start: 0.16, duration: 0.12 },
|
|
62
|
+
{ frequency: 1046.5, start: 0.28, duration: 0.22 }
|
|
63
|
+
];
|
|
64
|
+
const sparkles = [
|
|
65
|
+
{ frequency: 1567.98, start: 0.2 },
|
|
66
|
+
{ frequency: 2093, start: 0.32 },
|
|
67
|
+
{ frequency: 2637.02, start: 0.42 }
|
|
68
|
+
];
|
|
69
|
+
master.gain.setValueAtTime(1e-4, now);
|
|
70
|
+
master.gain.exponentialRampToValueAtTime(safeVolume, now + 0.02);
|
|
71
|
+
master.gain.exponentialRampToValueAtTime(1e-4, now + 0.78);
|
|
72
|
+
master.connect(audio.destination);
|
|
73
|
+
notes.forEach((note) => {
|
|
74
|
+
tone(note.frequency, note.start, note.duration, "triangle", 0.9);
|
|
75
|
+
tone(note.frequency * 2, note.start + 0.01, note.duration * 0.55, "sine", 0.18);
|
|
76
|
+
});
|
|
77
|
+
sparkles.forEach((sparkle) => {
|
|
78
|
+
tone(sparkle.frequency, sparkle.start, 0.055, "sine", 0.22);
|
|
79
|
+
});
|
|
80
|
+
window.setTimeout(() => {
|
|
81
|
+
void audio.close();
|
|
82
|
+
}, 1e3);
|
|
83
|
+
}
|
|
84
|
+
function playSound(sound, volume = 0.35) {
|
|
85
|
+
if (typeof sound === "string") {
|
|
86
|
+
const audio = new Audio(sound);
|
|
87
|
+
audio.volume = Math.min(Math.max(volume, 0), 1);
|
|
88
|
+
void audio.play();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (sound === true) {
|
|
92
|
+
playCompletionSound(volume);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function confetti(options = {}) {
|
|
96
|
+
var _a;
|
|
97
|
+
if (shouldSkip(options)) return;
|
|
98
|
+
const duration = options.duration ?? 2e3;
|
|
99
|
+
const particleCount = options.particleCount ?? 150;
|
|
100
|
+
const colors = ((_a = options.colors) == null ? void 0 : _a.length) ? options.colors : googleColors;
|
|
101
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
102
|
+
const particles = Array.from({ length: particleCount }).map(() => ({
|
|
103
|
+
x: Math.random() * canvas.width,
|
|
104
|
+
y: Math.random() * canvas.height - canvas.height,
|
|
105
|
+
size: Math.random() * 6 + 4,
|
|
106
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
107
|
+
velocityX: Math.random() * 4 - 2,
|
|
108
|
+
velocityY: Math.random() * 6 + 4,
|
|
109
|
+
rotation: Math.random() * 360
|
|
110
|
+
}));
|
|
111
|
+
const start = performance.now();
|
|
112
|
+
function draw(now) {
|
|
113
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
114
|
+
for (const p of particles) {
|
|
115
|
+
p.x += p.velocityX;
|
|
116
|
+
p.y += p.velocityY;
|
|
117
|
+
p.rotation += 5;
|
|
118
|
+
ctx.save();
|
|
119
|
+
ctx.translate(p.x, p.y);
|
|
120
|
+
ctx.rotate(p.rotation * Math.PI / 180);
|
|
121
|
+
ctx.fillStyle = p.color;
|
|
122
|
+
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
|
|
123
|
+
ctx.restore();
|
|
124
|
+
}
|
|
125
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
126
|
+
}
|
|
127
|
+
requestAnimationFrame(draw);
|
|
128
|
+
}
|
|
129
|
+
function balloons(options = {}) {
|
|
130
|
+
var _a;
|
|
131
|
+
if (shouldSkip(options)) return;
|
|
132
|
+
const duration = options.duration ?? 3e3;
|
|
133
|
+
const count = options.particleCount ?? 20;
|
|
134
|
+
const colors = ((_a = options.colors) == null ? void 0 : _a.length) ? options.colors : googleColors;
|
|
135
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
136
|
+
const items = Array.from({ length: count }).map(() => {
|
|
137
|
+
const radius = Math.random() * 20 + 20;
|
|
138
|
+
return {
|
|
139
|
+
x: Math.random() * canvas.width,
|
|
140
|
+
y: canvas.height + radius + Math.random() * 120,
|
|
141
|
+
radius,
|
|
142
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
143
|
+
speed: Math.random() * 2 + 2.8,
|
|
144
|
+
sway: Math.random() * 2 + 1
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
const start = performance.now();
|
|
148
|
+
function draw(now) {
|
|
149
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
150
|
+
items.forEach((b, i) => {
|
|
151
|
+
b.y -= b.speed;
|
|
152
|
+
b.x += Math.sin(now / 300 + i) * b.sway;
|
|
153
|
+
ctx.fillStyle = b.color;
|
|
154
|
+
ctx.beginPath();
|
|
155
|
+
ctx.ellipse(b.x, b.y, b.radius * 0.8, b.radius, 0, 0, Math.PI * 2);
|
|
156
|
+
ctx.fill();
|
|
157
|
+
ctx.beginPath();
|
|
158
|
+
ctx.moveTo(b.x - b.radius * 0.18, b.y + b.radius * 0.78);
|
|
159
|
+
ctx.lineTo(b.x + b.radius * 0.18, b.y + b.radius * 0.78);
|
|
160
|
+
ctx.lineTo(b.x, b.y + b.radius * 1.08);
|
|
161
|
+
ctx.closePath();
|
|
162
|
+
ctx.fill();
|
|
163
|
+
ctx.beginPath();
|
|
164
|
+
ctx.moveTo(b.x, b.y + b.radius);
|
|
165
|
+
ctx.quadraticCurveTo(
|
|
166
|
+
b.x + Math.sin(now / 150 + i) * b.sway * 4,
|
|
167
|
+
b.y + b.radius + 17.5,
|
|
168
|
+
b.x,
|
|
169
|
+
b.y + b.radius + 35
|
|
170
|
+
);
|
|
171
|
+
ctx.strokeStyle = "#9AA0A6";
|
|
172
|
+
ctx.lineWidth = 1;
|
|
173
|
+
ctx.stroke();
|
|
174
|
+
});
|
|
175
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
176
|
+
}
|
|
177
|
+
requestAnimationFrame(draw);
|
|
178
|
+
}
|
|
179
|
+
function popper(options = {}) {
|
|
180
|
+
var _a;
|
|
181
|
+
if (shouldSkip(options)) return;
|
|
182
|
+
const duration = options.duration ?? 3200;
|
|
183
|
+
const particleCount = options.particleCount ?? 120;
|
|
184
|
+
const colors = ((_a = options.colors) == null ? void 0 : _a.length) ? options.colors : googleColors;
|
|
185
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
186
|
+
const particles = [];
|
|
187
|
+
for (const side of ["left", "right"]) {
|
|
188
|
+
for (let i = 0; i < particleCount / 2; i++) {
|
|
189
|
+
const direction = side === "left" ? 1 : -1;
|
|
190
|
+
const angle = Math.random() * 0.75 + 0.2;
|
|
191
|
+
const power = Math.random() * 8 + 10;
|
|
192
|
+
particles.push({
|
|
193
|
+
x: side === "left" ? canvas.width * 0.08 : canvas.width * 0.92,
|
|
194
|
+
y: canvas.height * 0.82,
|
|
195
|
+
size: Math.random() * 6 + 4,
|
|
196
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
197
|
+
vx: Math.cos(angle) * power * direction,
|
|
198
|
+
vy: -Math.sin(angle) * power - Math.random() * 6,
|
|
199
|
+
rotation: Math.random() * 360,
|
|
200
|
+
rotationSpeed: Math.random() * 12 - 6
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const start = performance.now();
|
|
205
|
+
function draw(now) {
|
|
206
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
207
|
+
particles.forEach((p) => {
|
|
208
|
+
p.vy += 0.32;
|
|
209
|
+
p.vx *= 0.992;
|
|
210
|
+
p.vy *= 0.997;
|
|
211
|
+
p.x += p.vx;
|
|
212
|
+
p.y += p.vy;
|
|
213
|
+
p.rotation += p.rotationSpeed;
|
|
214
|
+
ctx.save();
|
|
215
|
+
ctx.translate(p.x, p.y);
|
|
216
|
+
ctx.rotate(p.rotation * Math.PI / 180);
|
|
217
|
+
ctx.fillStyle = p.color;
|
|
218
|
+
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
|
|
219
|
+
ctx.restore();
|
|
220
|
+
});
|
|
221
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
222
|
+
}
|
|
223
|
+
requestAnimationFrame(draw);
|
|
224
|
+
}
|
|
225
|
+
function fireworks(options = {}) {
|
|
226
|
+
var _a;
|
|
227
|
+
if (shouldSkip(options)) return;
|
|
228
|
+
const duration = options.duration ?? 4e3;
|
|
229
|
+
const burstCount = options.count ?? 5;
|
|
230
|
+
const interval = options.interval ?? 500;
|
|
231
|
+
const colors = ((_a = options.colors) == null ? void 0 : _a.length) ? options.colors : googleColors;
|
|
232
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
233
|
+
let particles = [];
|
|
234
|
+
let bursts = 0;
|
|
235
|
+
let lastBurst = 0;
|
|
236
|
+
const start = performance.now();
|
|
237
|
+
function createBurst() {
|
|
238
|
+
const x = Math.random() * canvas.width;
|
|
239
|
+
const y = Math.random() * canvas.height * 0.5;
|
|
240
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
241
|
+
for (let i = 0; i < 60; i++) {
|
|
242
|
+
const angle = Math.PI * 2 * i / 60;
|
|
243
|
+
const speed = Math.random() * 4 + 2;
|
|
244
|
+
particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, alpha: 1, color });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function draw(now) {
|
|
248
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
249
|
+
if (bursts < burstCount && now - lastBurst > interval) {
|
|
250
|
+
createBurst();
|
|
251
|
+
bursts++;
|
|
252
|
+
lastBurst = now;
|
|
253
|
+
}
|
|
254
|
+
particles.forEach((p) => {
|
|
255
|
+
p.x += p.vx;
|
|
256
|
+
p.y += p.vy;
|
|
257
|
+
p.vy += 0.04;
|
|
258
|
+
p.alpha -= 0.015;
|
|
259
|
+
ctx.globalAlpha = Math.max(p.alpha, 0);
|
|
260
|
+
ctx.beginPath();
|
|
261
|
+
ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
|
|
262
|
+
ctx.fillStyle = p.color;
|
|
263
|
+
ctx.fill();
|
|
264
|
+
});
|
|
265
|
+
ctx.globalAlpha = 1;
|
|
266
|
+
particles = particles.filter((p) => p.alpha > 0);
|
|
267
|
+
now - start < duration || particles.length > 0 ? requestAnimationFrame(draw) : cleanup();
|
|
268
|
+
}
|
|
269
|
+
requestAnimationFrame(draw);
|
|
270
|
+
}
|
|
271
|
+
function bubbles(options = {}) {
|
|
272
|
+
if (shouldSkip(options)) return;
|
|
273
|
+
const duration = options.duration ?? 3e3;
|
|
274
|
+
const count = options.particleCount ?? 25;
|
|
275
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
276
|
+
const items = Array.from({ length: count }).map(() => ({
|
|
277
|
+
x: Math.random() * canvas.width,
|
|
278
|
+
y: canvas.height + Math.random() * 100,
|
|
279
|
+
radius: Math.random() * 15 + 10,
|
|
280
|
+
speed: Math.random() * 2.1 + 1.4,
|
|
281
|
+
drift: Math.random() * 1.5 - 0.75,
|
|
282
|
+
alpha: Math.random() * 0.4 + 0.4
|
|
283
|
+
}));
|
|
284
|
+
const start = performance.now();
|
|
285
|
+
function draw(now) {
|
|
286
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
287
|
+
items.forEach((b) => {
|
|
288
|
+
b.y -= b.speed;
|
|
289
|
+
b.x += Math.sin(now / 500) * b.drift;
|
|
290
|
+
if (b.y < canvas.height * 0.3) b.alpha = Math.max(0, b.alpha - 0.01);
|
|
291
|
+
ctx.beginPath();
|
|
292
|
+
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
|
|
293
|
+
ctx.fillStyle = `rgba(180, 220, 255, ${b.alpha * 0.4})`;
|
|
294
|
+
ctx.fill();
|
|
295
|
+
ctx.strokeStyle = `rgba(255,255,255,${b.alpha})`;
|
|
296
|
+
ctx.lineWidth = 2;
|
|
297
|
+
ctx.stroke();
|
|
298
|
+
});
|
|
299
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
300
|
+
}
|
|
301
|
+
requestAnimationFrame(draw);
|
|
302
|
+
}
|
|
303
|
+
function emojis(options = {}) {
|
|
304
|
+
if (shouldSkip(options)) return;
|
|
305
|
+
const duration = options.duration ?? 3e3;
|
|
306
|
+
const count = options.particleCount ?? 30;
|
|
307
|
+
const emojiSet = options.emojis ?? ["\u{1F389}", "\u2728", "\u{1F525}", "\u{1F4AF}", "\u{1F60D}"];
|
|
308
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
309
|
+
const items = Array.from({ length: count }).map(() => ({
|
|
310
|
+
x: Math.random() * canvas.width,
|
|
311
|
+
y: Math.random() * -canvas.height,
|
|
312
|
+
char: emojiSet[Math.floor(Math.random() * emojiSet.length)],
|
|
313
|
+
size: Math.random() * 20 + 20,
|
|
314
|
+
speed: Math.random() * 2.5 + 1.5,
|
|
315
|
+
drift: Math.random() * 1.5 - 0.75,
|
|
316
|
+
rotation: Math.random() * 360,
|
|
317
|
+
rotationSpeed: Math.random() * 4 - 2,
|
|
318
|
+
alpha: 1
|
|
319
|
+
}));
|
|
320
|
+
const start = performance.now();
|
|
321
|
+
function draw(now) {
|
|
322
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
323
|
+
items.forEach((e) => {
|
|
324
|
+
e.y += e.speed;
|
|
325
|
+
e.x += Math.sin(now / 400) * e.drift;
|
|
326
|
+
e.rotation += e.rotationSpeed;
|
|
327
|
+
if (e.y > canvas.height * 0.8) e.alpha = Math.max(0, e.alpha - 0.02);
|
|
328
|
+
ctx.save();
|
|
329
|
+
ctx.translate(e.x, e.y);
|
|
330
|
+
ctx.rotate(e.rotation * Math.PI / 180);
|
|
331
|
+
ctx.globalAlpha = e.alpha;
|
|
332
|
+
ctx.font = `${e.size}px serif`;
|
|
333
|
+
ctx.textAlign = "center";
|
|
334
|
+
ctx.textBaseline = "middle";
|
|
335
|
+
ctx.fillText(e.char, 0, 0);
|
|
336
|
+
ctx.restore();
|
|
337
|
+
});
|
|
338
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
339
|
+
}
|
|
340
|
+
requestAnimationFrame(draw);
|
|
341
|
+
}
|
|
342
|
+
function stars(options = {}) {
|
|
343
|
+
if (shouldSkip(options)) return;
|
|
344
|
+
const duration = options.duration ?? 3e3;
|
|
345
|
+
const count = options.count ?? 5;
|
|
346
|
+
const interval = options.interval ?? 500;
|
|
347
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
348
|
+
const items = [];
|
|
349
|
+
const start = performance.now();
|
|
350
|
+
let spawned = 0;
|
|
351
|
+
let lastSpawn = 0;
|
|
352
|
+
function spawnStar() {
|
|
353
|
+
const fromLeft = Math.random() > 0.5;
|
|
354
|
+
items.push({
|
|
355
|
+
x: fromLeft ? -100 : canvas.width + 100,
|
|
356
|
+
y: Math.random() * canvas.height * 0.4,
|
|
357
|
+
vx: fromLeft ? Math.random() * 12 + 8 : -(Math.random() * 12 + 8),
|
|
358
|
+
vy: Math.random() * 6 + 4,
|
|
359
|
+
alpha: 1
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
function draw(now) {
|
|
363
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
364
|
+
if (spawned < count && now - lastSpawn > interval) {
|
|
365
|
+
spawnStar();
|
|
366
|
+
spawned++;
|
|
367
|
+
lastSpawn = now;
|
|
368
|
+
}
|
|
369
|
+
items.forEach((s, i) => {
|
|
370
|
+
s.x += s.vx;
|
|
371
|
+
s.y += s.vy;
|
|
372
|
+
s.alpha -= 0.015;
|
|
373
|
+
ctx.beginPath();
|
|
374
|
+
ctx.moveTo(s.x, s.y);
|
|
375
|
+
ctx.lineTo(s.x - s.vx * 4, s.y - s.vy * 4);
|
|
376
|
+
ctx.strokeStyle = `rgba(251,188,5,${s.alpha})`;
|
|
377
|
+
ctx.lineWidth = 2;
|
|
378
|
+
ctx.lineCap = "round";
|
|
379
|
+
ctx.stroke();
|
|
380
|
+
if (s.alpha <= 0) items.splice(i, 1);
|
|
381
|
+
});
|
|
382
|
+
now - start < duration || items.length > 0 ? requestAnimationFrame(draw) : cleanup();
|
|
383
|
+
}
|
|
384
|
+
requestAnimationFrame(draw);
|
|
385
|
+
}
|
|
386
|
+
function snowfall(options = {}) {
|
|
387
|
+
if (shouldSkip(options)) return;
|
|
388
|
+
const duration = options.duration ?? 5e3;
|
|
389
|
+
const count = options.particleCount ?? 60;
|
|
390
|
+
const { canvas, ctx, cleanup } = createCanvas();
|
|
391
|
+
const flakes = Array.from({ length: count }).map(() => ({
|
|
392
|
+
x: Math.random() * canvas.width,
|
|
393
|
+
y: Math.random() * -canvas.height,
|
|
394
|
+
radius: Math.random() * 2.5 + 1,
|
|
395
|
+
speed: Math.random() * 0.6 + 0.3,
|
|
396
|
+
drift: Math.random() * 0.6 - 0.3,
|
|
397
|
+
alpha: Math.random() * 0.5 + 0.4
|
|
398
|
+
}));
|
|
399
|
+
const start = performance.now();
|
|
400
|
+
function draw(now) {
|
|
401
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
402
|
+
flakes.forEach((f) => {
|
|
403
|
+
f.y += f.speed;
|
|
404
|
+
f.x += Math.sin(now / 800) * f.drift;
|
|
405
|
+
if (f.y > canvas.height) {
|
|
406
|
+
f.y = -10;
|
|
407
|
+
f.x = Math.random() * canvas.width;
|
|
408
|
+
}
|
|
409
|
+
ctx.beginPath();
|
|
410
|
+
ctx.arc(f.x, f.y, f.radius, 0, Math.PI * 2);
|
|
411
|
+
ctx.fillStyle = `rgba(255,255,255,${f.alpha})`;
|
|
412
|
+
ctx.fill();
|
|
413
|
+
});
|
|
414
|
+
now - start < duration ? requestAnimationFrame(draw) : cleanup();
|
|
415
|
+
}
|
|
416
|
+
requestAnimationFrame(draw);
|
|
417
|
+
}
|
|
418
|
+
var celebrate = Object.assign((options = {}) => {
|
|
419
|
+
if (shouldSkip(options)) return;
|
|
420
|
+
if (options.sound) {
|
|
421
|
+
playSound(options.sound, options.soundVolume);
|
|
422
|
+
}
|
|
423
|
+
const effect = options.effect ?? "confetti";
|
|
424
|
+
switch (effect) {
|
|
425
|
+
case "confetti":
|
|
426
|
+
confetti(options);
|
|
427
|
+
break;
|
|
428
|
+
case "balloons":
|
|
429
|
+
balloons(options);
|
|
430
|
+
break;
|
|
431
|
+
case "popper":
|
|
432
|
+
popper(options);
|
|
433
|
+
break;
|
|
434
|
+
case "fireworks":
|
|
435
|
+
fireworks(options);
|
|
436
|
+
break;
|
|
437
|
+
case "bubbles":
|
|
438
|
+
bubbles(options);
|
|
439
|
+
break;
|
|
440
|
+
case "emojis":
|
|
441
|
+
emojis(options);
|
|
442
|
+
break;
|
|
443
|
+
case "stars":
|
|
444
|
+
stars(options);
|
|
445
|
+
break;
|
|
446
|
+
case "snowfall":
|
|
447
|
+
snowfall(options);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
}, {
|
|
451
|
+
confetti: (o) => celebrate({ ...o, effect: "confetti" }),
|
|
452
|
+
balloons: (o) => celebrate({ ...o, effect: "balloons" }),
|
|
453
|
+
popper: (o) => celebrate({ ...o, effect: "popper" }),
|
|
454
|
+
fireworks: (o) => celebrate({ ...o, effect: "fireworks" }),
|
|
455
|
+
bubbles: (o) => celebrate({ ...o, effect: "bubbles" }),
|
|
456
|
+
emojis: (o) => celebrate({ ...o, effect: "emojis" }),
|
|
457
|
+
stars: (o) => celebrate({ ...o, effect: "stars" }),
|
|
458
|
+
snowfall: (o) => celebrate({ ...o, effect: "snowfall" })
|
|
459
|
+
});
|
|
460
|
+
export {
|
|
461
|
+
celebrate
|
|
462
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "celebrate-js",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Delightful UI celebration effects (confetti, balloons, fireworks, and more)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"celebration",
|
|
7
|
+
"confetti",
|
|
8
|
+
"balloons",
|
|
9
|
+
"animations",
|
|
10
|
+
"effects",
|
|
11
|
+
"ui",
|
|
12
|
+
"canvas",
|
|
13
|
+
"frontend",
|
|
14
|
+
"micro-interactions"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/akshaywritescode/celebrate-js",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/akshaywritescode/celebrate-js.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/akshaywritescode/celebrate-js/issues"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Akshay <debugwithakshay@gmail.com>",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.cjs",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js",
|
|
34
|
+
"require": "./dist/index.cjs"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"sideEffects": false,
|
|
41
|
+
"scripts": {
|
|
42
|
+
"dev": "vite --host 0.0.0.0",
|
|
43
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
44
|
+
"prepublishOnly": "npm run build"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"eslint": "^8.56.0",
|
|
48
|
+
"tsup": "^8.5.1",
|
|
49
|
+
"typescript": "^5.9.3",
|
|
50
|
+
"vite": "^7.3.0",
|
|
51
|
+
"vitest": "^1.2.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18"
|
|
55
|
+
}
|
|
56
|
+
}
|