fireworks-engine 1.0.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/src/presets.js ADDED
@@ -0,0 +1,811 @@
1
+ import {
2
+ DEFAULT_BURST_CENTER_FLASH_ATTACK,
3
+ DEFAULT_BURST_CENTER_FLASH_COLOR,
4
+ DEFAULT_BURST_CENTER_FLASH_COLOR_MODE,
5
+ DEFAULT_BURST_CENTER_FLASH_ENABLED,
6
+ DEFAULT_BURST_CENTER_FLASH_END_SIZE,
7
+ DEFAULT_BURST_CENTER_FLASH_INNER_STOP,
8
+ DEFAULT_BURST_CENTER_FLASH_PEAK_ALPHA,
9
+ DEFAULT_BURST_CENTER_FLASH_FADE,
10
+ DEFAULT_BURST_CENTER_FLASH_START_SIZE,
11
+ DEFAULT_BURST_COLOR_MODE,
12
+ DEFAULT_BURST_FORMS_COUNT,
13
+ DEFAULT_BURST_FORMS_ENABLED,
14
+ DEFAULT_BURST_FORMS_GLYPH,
15
+ DEFAULT_BURST_FORMS_GRAVITY,
16
+ DEFAULT_BURST_FORMS_LIFE_MAX,
17
+ DEFAULT_BURST_FORMS_LIFE_MIN,
18
+ DEFAULT_BURST_FORMS_SPIN,
19
+ DEFAULT_BURST_FORMS_SPIN_DURATION,
20
+ DEFAULT_BURST_FORMS_SPIN_PAUSE,
21
+ DEFAULT_BURST_FORMS_HIGHLIGHT,
22
+ DEFAULT_BURST_FORMS_HIGHLIGHT_ANGLE_DEG,
23
+ DEFAULT_BURST_FORMS_HIGHLIGHT_WIDTH,
24
+ DEFAULT_BURST_FORMS_WOBBLE,
25
+ DEFAULT_BURST_FORMS_DIRECTION_DEG,
26
+ DEFAULT_BURST_FORMS_CONE_DEG,
27
+ DEFAULT_BURST_FORMS_SHAPE,
28
+ DEFAULT_BURST_FORMS_SIZE_MAX,
29
+ DEFAULT_BURST_FORMS_SIZE_MIN,
30
+ DEFAULT_BURST_FORMS_SPEED_MAX,
31
+ DEFAULT_BURST_FORMS_SPEED_MIN,
32
+ DEFAULT_BURST_PALETTE,
33
+ DEFAULT_BURST_SINGLE_COLOR,
34
+ DEFAULT_BURST_SPARK_COUNT_RATIO,
35
+ DEFAULT_BURST_SPARK_GRAVITY,
36
+ DEFAULT_BURST_SPARK_LENGTH,
37
+ DEFAULT_BURST_SPARK_DIRECTION_DEG,
38
+ DEFAULT_BURST_SPARK_CONE_DEG,
39
+ DEFAULT_BURST_SPARK_TAIL_SCALE,
40
+ DEFAULT_BURST_SPARK_SHAPE,
41
+ DEFAULT_BURST_SPARK_THICKNESS,
42
+ DEFAULT_BURST_SPARKS_ENABLED,
43
+ DEFAULT_BURST_TWINKLE_CLOUD_BRIGHTNESS,
44
+ DEFAULT_BURST_TWINKLE_CLOUD_COUNT,
45
+ DEFAULT_BURST_TWINKLE_CLOUD_DURATION,
46
+ DEFAULT_BURST_TWINKLE_CLOUD_ENABLED,
47
+ DEFAULT_BURST_TWINKLE_CLOUD_RADIUS,
48
+ DEFAULT_BURST_TWINKLE_CLOUD_SIZE,
49
+ DEFAULT_BURST_TWINKLE_CLOUD_SPAWN_SPREAD,
50
+ DEFAULT_BURST_TWINKLE_CLOUD_START_DELAY,
51
+ DEFAULT_BURST_TWINKLE_CLOUD_TWINKLE_LIFE,
52
+ DEFAULT_ROCKET_ENABLED,
53
+ DEFAULT_ROCKET_FINAL_STREAK_LENGTH,
54
+ DEFAULT_ROCKET_PALETTE,
55
+ DEFAULT_ROCKET_STREAK_LENGTH,
56
+ DEFAULT_ROCKET_SPEED,
57
+ DEFAULT_ROCKET_TAIL_EMBERS_BRIGHTNESS,
58
+ DEFAULT_ROCKET_TAIL_EMBERS_COUNT,
59
+ DEFAULT_ROCKET_TAIL_EMBERS_ENABLED,
60
+ DEFAULT_ROCKET_TAIL_EMBERS_FADE,
61
+ DEFAULT_ROCKET_TAIL_EMBERS_SIZE,
62
+ DEFAULT_ROCKET_TAIL_EMBERS_SPEED,
63
+ DEFAULT_ROCKET_TAIL_EMBERS_SPREAD_X,
64
+ DEFAULT_ROCKET_TAIL_EMBERS_SPREAD_Y,
65
+ DEFAULT_ROCKET_THICKNESS,
66
+ BURST_SPARK_COUNT_BASE,
67
+ BURST_SPARK_LIFE_MAX_BASE,
68
+ BURST_SPARK_LIFE_MIN_BASE,
69
+ BURST_SPARK_SPEED_MAX_BASE,
70
+ BURST_SPARK_SPEED_MIN_BASE,
71
+ } from './defaults.js';
72
+ import { randomRange } from './mathResolvers.js';
73
+ import { resolveScaledDelay, scheduleTimeout } from './runtimeLifecycle.js';
74
+ import { isPlainRecord } from './valueNormalizers.js';
75
+
76
+ function deepFreeze(value) {
77
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) {
78
+ return value;
79
+ }
80
+
81
+ Object.freeze(value);
82
+ Object.values(value).forEach((entry) => {
83
+ deepFreeze(entry);
84
+ });
85
+ return value;
86
+ }
87
+
88
+ function mergeLaunchRecipeOptions(baseOptions = {}, patch = {}) {
89
+ const next = {
90
+ ...baseOptions,
91
+ ...patch,
92
+ };
93
+
94
+ const keys = [
95
+ 'rocket',
96
+ 'rocketTailEmbers',
97
+ 'burstForms',
98
+ 'burstSparks',
99
+ 'burstCenterFlash',
100
+ 'burstTwinkleCloud',
101
+ ];
102
+
103
+ for (let index = 0; index < keys.length; index += 1) {
104
+ const key = keys[index];
105
+ const baseGroup = isPlainRecord(baseOptions[key]) ? baseOptions[key] : {};
106
+ const patchGroup = isPlainRecord(patch[key]) ? patch[key] : null;
107
+
108
+ if (patchGroup) {
109
+ next[key] = {
110
+ ...baseGroup,
111
+ ...patchGroup,
112
+ };
113
+ } else if (isPlainRecord(baseOptions[key])) {
114
+ next[key] = { ...baseGroup };
115
+ }
116
+ }
117
+
118
+ return next;
119
+ }
120
+
121
+ function createRocketCascade(overrides = {}) {
122
+ return {
123
+ bursts: 0,
124
+ delay: 160,
125
+ spreadX: 152,
126
+ spreadY: 151,
127
+ ...overrides,
128
+ };
129
+ }
130
+
131
+ function createRocketConfig(overrides = {}) {
132
+ const { cascade, ...rocketOverrides } = overrides;
133
+
134
+ return {
135
+ enabled: DEFAULT_ROCKET_ENABLED,
136
+ colorMode: DEFAULT_BURST_COLOR_MODE,
137
+ singleColor: DEFAULT_BURST_SINGLE_COLOR,
138
+ streakLength: DEFAULT_ROCKET_STREAK_LENGTH,
139
+ finalStreakLength: DEFAULT_ROCKET_FINAL_STREAK_LENGTH,
140
+ thickness: DEFAULT_ROCKET_THICKNESS,
141
+ speed: DEFAULT_ROCKET_SPEED,
142
+ ...rocketOverrides,
143
+ cascade: createRocketCascade(cascade),
144
+ };
145
+ }
146
+
147
+ function createRocketTailEmbersConfig(overrides = {}) {
148
+ return {
149
+ enabled: DEFAULT_ROCKET_TAIL_EMBERS_ENABLED,
150
+ count: DEFAULT_ROCKET_TAIL_EMBERS_COUNT,
151
+ brightness: DEFAULT_ROCKET_TAIL_EMBERS_BRIGHTNESS,
152
+ fade: DEFAULT_ROCKET_TAIL_EMBERS_FADE,
153
+ spreadX: DEFAULT_ROCKET_TAIL_EMBERS_SPREAD_X,
154
+ spreadY: DEFAULT_ROCKET_TAIL_EMBERS_SPREAD_Y,
155
+ speed: DEFAULT_ROCKET_TAIL_EMBERS_SPEED,
156
+ size: DEFAULT_ROCKET_TAIL_EMBERS_SIZE,
157
+ ...overrides,
158
+ };
159
+ }
160
+
161
+ function createBurstFormsConfig(overrides = {}) {
162
+ return {
163
+ enabled: DEFAULT_BURST_FORMS_ENABLED,
164
+ count: DEFAULT_BURST_FORMS_COUNT,
165
+ speedMin: DEFAULT_BURST_FORMS_SPEED_MIN,
166
+ speedMax: DEFAULT_BURST_FORMS_SPEED_MAX,
167
+ gravity: DEFAULT_BURST_FORMS_GRAVITY,
168
+ lifeMin: DEFAULT_BURST_FORMS_LIFE_MIN,
169
+ lifeMax: DEFAULT_BURST_FORMS_LIFE_MAX,
170
+ sizeMin: DEFAULT_BURST_FORMS_SIZE_MIN,
171
+ sizeMax: DEFAULT_BURST_FORMS_SIZE_MAX,
172
+ spin: DEFAULT_BURST_FORMS_SPIN,
173
+ spinDuration: DEFAULT_BURST_FORMS_SPIN_DURATION,
174
+ spinPause: DEFAULT_BURST_FORMS_SPIN_PAUSE,
175
+ highlight: DEFAULT_BURST_FORMS_HIGHLIGHT,
176
+ highlightAngleDeg: DEFAULT_BURST_FORMS_HIGHLIGHT_ANGLE_DEG,
177
+ highlightWidth: DEFAULT_BURST_FORMS_HIGHLIGHT_WIDTH,
178
+ wobble: DEFAULT_BURST_FORMS_WOBBLE,
179
+ directionDeg: DEFAULT_BURST_FORMS_DIRECTION_DEG,
180
+ coneDeg: DEFAULT_BURST_FORMS_CONE_DEG,
181
+ palette: DEFAULT_BURST_PALETTE,
182
+ shape: DEFAULT_BURST_FORMS_SHAPE,
183
+ glyph: DEFAULT_BURST_FORMS_GLYPH,
184
+ colorMode: DEFAULT_BURST_COLOR_MODE,
185
+ singleColor: DEFAULT_BURST_SINGLE_COLOR,
186
+ ...overrides,
187
+ };
188
+ }
189
+
190
+ function createBurstSparksConfig(overrides = {}) {
191
+ return {
192
+ enabled: DEFAULT_BURST_SPARKS_ENABLED,
193
+ count: BURST_SPARK_COUNT_BASE * DEFAULT_BURST_SPARK_COUNT_RATIO,
194
+ length: DEFAULT_BURST_SPARK_LENGTH,
195
+ tailScale: DEFAULT_BURST_SPARK_TAIL_SCALE,
196
+ thickness: DEFAULT_BURST_SPARK_THICKNESS,
197
+ speedMin: BURST_SPARK_SPEED_MIN_BASE,
198
+ speedMax: BURST_SPARK_SPEED_MAX_BASE,
199
+ gravity: DEFAULT_BURST_SPARK_GRAVITY,
200
+ lifeMin: BURST_SPARK_LIFE_MIN_BASE,
201
+ lifeMax: BURST_SPARK_LIFE_MAX_BASE,
202
+ directionDeg: DEFAULT_BURST_SPARK_DIRECTION_DEG,
203
+ coneDeg: DEFAULT_BURST_SPARK_CONE_DEG,
204
+ palette: DEFAULT_BURST_PALETTE,
205
+ shape: DEFAULT_BURST_SPARK_SHAPE,
206
+ colorMode: DEFAULT_BURST_COLOR_MODE,
207
+ singleColor: DEFAULT_BURST_SINGLE_COLOR,
208
+ ...overrides,
209
+ };
210
+ }
211
+
212
+ function createBurstCenterFlashConfig(overrides = {}) {
213
+ return {
214
+ enabled: DEFAULT_BURST_CENTER_FLASH_ENABLED,
215
+ startSize: DEFAULT_BURST_CENTER_FLASH_START_SIZE,
216
+ endSize: DEFAULT_BURST_CENTER_FLASH_END_SIZE,
217
+ peakAlpha: DEFAULT_BURST_CENTER_FLASH_PEAK_ALPHA,
218
+ attack: DEFAULT_BURST_CENTER_FLASH_ATTACK,
219
+ fade: DEFAULT_BURST_CENTER_FLASH_FADE,
220
+ colorMode: DEFAULT_BURST_CENTER_FLASH_COLOR_MODE,
221
+ color: DEFAULT_BURST_CENTER_FLASH_COLOR,
222
+ innerStop: DEFAULT_BURST_CENTER_FLASH_INNER_STOP,
223
+ ...overrides,
224
+ };
225
+ }
226
+
227
+ function createBurstTwinkleCloudConfig(overrides = {}) {
228
+ return {
229
+ enabled: DEFAULT_BURST_TWINKLE_CLOUD_ENABLED,
230
+ palette: DEFAULT_BURST_PALETTE,
231
+ count: DEFAULT_BURST_TWINKLE_CLOUD_COUNT,
232
+ radius: DEFAULT_BURST_TWINKLE_CLOUD_RADIUS,
233
+ startDelay: DEFAULT_BURST_TWINKLE_CLOUD_START_DELAY,
234
+ duration: DEFAULT_BURST_TWINKLE_CLOUD_DURATION,
235
+ twinkleLife: DEFAULT_BURST_TWINKLE_CLOUD_TWINKLE_LIFE,
236
+ brightness: DEFAULT_BURST_TWINKLE_CLOUD_BRIGHTNESS,
237
+ size: DEFAULT_BURST_TWINKLE_CLOUD_SIZE,
238
+ spawnSpread: DEFAULT_BURST_TWINKLE_CLOUD_SPAWN_SPREAD,
239
+ ...overrides,
240
+ };
241
+ }
242
+
243
+ function createBurstBundle({
244
+ palette = DEFAULT_BURST_PALETTE,
245
+ burstForms = {},
246
+ burstSparks = {},
247
+ burstCenterFlash = {},
248
+ burstTwinkleCloud = {},
249
+ } = {}) {
250
+ return {
251
+ burstForms: createBurstFormsConfig({ palette, ...burstForms }),
252
+ burstSparks: createBurstSparksConfig({ palette, ...burstSparks }),
253
+ burstCenterFlash: createBurstCenterFlashConfig(burstCenterFlash),
254
+ burstTwinkleCloud: createBurstTwinkleCloudConfig(burstTwinkleCloud),
255
+ };
256
+ }
257
+
258
+ export const FIREWORKS_ENGINE_PRESETS = deepFreeze({
259
+ base: {
260
+ timeScale: 1,
261
+ bursts: 1,
262
+ duration: 1200,
263
+ simple: false,
264
+ rocket: createRocketConfig(),
265
+ rocketTailEmbers: createRocketTailEmbersConfig(),
266
+ ...createBurstBundle(),
267
+ },
268
+ rocket: {
269
+ timeScale: 1,
270
+ mode: 'rocket',
271
+ rocketCount: 2,
272
+ rocketDelay: 240,
273
+ totalDuration: 2200,
274
+ rocket: createRocketConfig({
275
+ durationMin: 0.72,
276
+ durationMax: 0.95,
277
+ }),
278
+ rocketTailEmbers: createRocketTailEmbersConfig(),
279
+ ...createBurstBundle({ palette: DEFAULT_ROCKET_PALETTE }),
280
+ },
281
+ cascade: {
282
+ timeScale: 1,
283
+ mode: 'rocket',
284
+ rocketCount: 2,
285
+ rocketDelay: 200,
286
+ totalDuration: 2800,
287
+ rocket: createRocketConfig({
288
+ durationMin: 0.74,
289
+ durationMax: 1.02,
290
+ cascade: {
291
+ bursts: 3,
292
+ burst: createBurstBundle(),
293
+ },
294
+ }),
295
+ rocketTailEmbers: createRocketTailEmbersConfig(),
296
+ ...createBurstBundle(),
297
+ },
298
+ finale: {
299
+ timeScale: 1,
300
+ mode: 'rocket',
301
+ rocketCount: 20,
302
+ rocketDelay: 180,
303
+ totalDuration: 2600,
304
+ rocket: createRocketConfig({
305
+ durationMin: 0.74,
306
+ durationMax: 1.02,
307
+ }),
308
+ rocketTailEmbers: createRocketTailEmbersConfig(),
309
+ ...createBurstBundle(),
310
+ },
311
+ });
312
+
313
+ export function resolveFireworksEnginePreset(name) {
314
+ if (typeof name === 'string' && FIREWORKS_ENGINE_PRESETS[name]) {
315
+ return FIREWORKS_ENGINE_PRESETS[name];
316
+ }
317
+ return FIREWORKS_ENGINE_PRESETS.base;
318
+ }
319
+
320
+ function runDirectBurst(engine, targetX, targetY, options) {
321
+ return engine.burst(targetX, targetY, options);
322
+ }
323
+
324
+ function runRocketLaunch(engine, targetX, targetY, options) {
325
+ return engine.launchRocketAt(targetX, targetY, options);
326
+ }
327
+
328
+ function runAchievementUnlock(engine, targetX, targetY, options) {
329
+ const directions = [135, 90, 45];
330
+
331
+ directions.forEach((directionDeg, index) => {
332
+ scheduleTimeout(engine, () => {
333
+ engine.confetti(
334
+ targetX,
335
+ targetY,
336
+ mergeLaunchRecipeOptions(options, {
337
+ burstForms: {
338
+ directionDeg,
339
+ },
340
+ }),
341
+ );
342
+ }, resolveScaledDelay(engine, index * 50));
343
+ });
344
+
345
+ return true;
346
+ }
347
+
348
+ function runSnowfall(engine, _targetX, _targetY, options) {
349
+ const flakesPerWave = 6;
350
+ const waveDelayMs = 1500;
351
+
352
+ const launchWave = () => {
353
+ const stageWidth = engine.width;
354
+ for (let flake = 0; flake < flakesPerWave; flake += 1) {
355
+ const x = randomRange(0, stageWidth);
356
+ const y = -10 - randomRange(0, 40);
357
+ engine.burst(x, y, options);
358
+ }
359
+
360
+ scheduleTimeout(engine, () => {
361
+ if (!engine.running) {
362
+ return;
363
+ }
364
+
365
+ launchWave();
366
+ }, resolveScaledDelay(engine, waveDelayMs));
367
+ };
368
+
369
+ launchWave();
370
+
371
+ return true;
372
+ }
373
+
374
+ function runLeafFall(engine, _targetX, _targetY, options) {
375
+ const leavesPerWave = 5;
376
+ const waveDelayMs = 1200;
377
+
378
+ const launchWave = () => {
379
+ const stageWidth = engine.width;
380
+ for (let leaf = 0; leaf < leavesPerWave; leaf += 1) {
381
+ const x = randomRange(0, stageWidth);
382
+ const y = -16 - randomRange(0, 60);
383
+ engine.burst(x, y, options);
384
+ }
385
+
386
+ scheduleTimeout(engine, () => {
387
+ if (!engine.running) {
388
+ return;
389
+ }
390
+
391
+ launchWave();
392
+ }, resolveScaledDelay(engine, waveDelayMs));
393
+ };
394
+
395
+ launchWave();
396
+
397
+ return true;
398
+ }
399
+
400
+ function runAmbientSparkle(engine, targetX, targetY, options) {
401
+ const pulseDelayMs = 8000;
402
+
403
+ const launchPulse = () => {
404
+ const centerX = Number.isFinite(targetX) ? targetX : engine.width * 0.5;
405
+ const centerY = Number.isFinite(targetY) ? targetY : engine.height * 0.5;
406
+
407
+ engine.launchRocketAt(centerX, centerY, options);
408
+ engine.launchRocketAt(
409
+ centerX,
410
+ centerY,
411
+ mergeLaunchRecipeOptions(options, {
412
+ burstTwinkleCloud: {
413
+ size: 2,
414
+ },
415
+ }),
416
+ );
417
+
418
+ scheduleTimeout(engine, () => {
419
+ if (!engine.running) {
420
+ return;
421
+ }
422
+
423
+ launchPulse();
424
+ }, resolveScaledDelay(engine, pulseDelayMs));
425
+ };
426
+
427
+ launchPulse();
428
+
429
+ return true;
430
+ }
431
+
432
+ function runConfettiPop(engine, _targetX, _targetY, options) {
433
+ const stageWidth = engine.width;
434
+ const stageHeight = engine.height;
435
+ const cannons = [
436
+ { x: -0.02, y: 0.5, directionDeg: 45 },
437
+ { x: 1.01, y: 0.5, directionDeg: 135 },
438
+ ];
439
+
440
+ cannons.forEach(({ x, y, directionDeg }) => {
441
+ engine.confetti(
442
+ stageWidth * x,
443
+ stageHeight * y,
444
+ mergeLaunchRecipeOptions(options, {
445
+ burstForms: {
446
+ directionDeg,
447
+ },
448
+ }),
449
+ );
450
+ });
451
+
452
+ return true;
453
+ }
454
+
455
+ export const FIREWORKS_LAUNCH_PRESETS = deepFreeze({
456
+ 'button-click': {
457
+ options: {
458
+ timeScale: 1.7,
459
+ rocket: {
460
+ enabled: false,
461
+ },
462
+ rocketTailEmbers: {
463
+ enabled: false,
464
+ },
465
+ burstForms: {
466
+ enabled: false,
467
+ },
468
+ burstSparks: {
469
+ count: 30,
470
+ length: 34,
471
+ tailScale: 1.3,
472
+ thickness: 2.5,
473
+ speedMin: 50,
474
+ speedMax: 230,
475
+ gravity: 100,
476
+ lifeMin: 1,
477
+ lifeMax: 2,
478
+ },
479
+ burstCenterFlash: {
480
+ enabled: false,
481
+ },
482
+ burstTwinkleCloud: {
483
+ enabled: false,
484
+ },
485
+ },
486
+ run: runDirectBurst,
487
+ },
488
+ 'success-burst': {
489
+ options: {
490
+ timeScale: 1.7,
491
+ rocket: {
492
+ enabled: false,
493
+ },
494
+ rocketTailEmbers: {
495
+ enabled: false,
496
+ },
497
+ burstForms: {
498
+ shape: 'glyph',
499
+ count: 30,
500
+ speedMin: 50,
501
+ speedMax: 230,
502
+ directionDeg: 0,
503
+ gravity: 100,
504
+ lifeMin: 1,
505
+ lifeMax: 2,
506
+ sizeMax: 4,
507
+ spin: 0.4,
508
+ spinCoverage: 0.5,
509
+ wobble: 0.5,
510
+ },
511
+ burstSparks: {
512
+ count: 30,
513
+ length: 34,
514
+ tailScale: 1.3,
515
+ thickness: 2.5,
516
+ speedMin: 100,
517
+ speedMax: 230,
518
+ gravity: 100,
519
+ lifeMin: 1,
520
+ lifeMax: 2,
521
+ },
522
+ burstCenterFlash: {
523
+ startSize: 40,
524
+ endSize: 1,
525
+ peakAlpha: 0.4,
526
+ attack: 0.75,
527
+ colorMode: 'single',
528
+ color: '#f7ff80',
529
+ innerStop: 0.2,
530
+ },
531
+ burstTwinkleCloud: {
532
+ enabled: false,
533
+ },
534
+ },
535
+ run: runDirectBurst,
536
+ },
537
+ 'win-celebration': {
538
+ options: {
539
+ timeScale: 1,
540
+ rocket: {
541
+ cascade: {
542
+ mode: 'rocket',
543
+ bursts: 3,
544
+ childBursts: 1,
545
+ childSpeedMin: 400,
546
+ delay: 160,
547
+ spreadX: 232,
548
+ spreadY: 239,
549
+ },
550
+ },
551
+ burstForms: {
552
+ directionDeg: 0,
553
+ },
554
+ },
555
+ run: runRocketLaunch,
556
+ },
557
+ 'achievement-unlock': {
558
+ options: {
559
+ timeScale: 3,
560
+ rocket: {
561
+ enabled: false,
562
+ },
563
+ rocketTailEmbers: {
564
+ enabled: false,
565
+ },
566
+ burstForms: {
567
+ count: 90,
568
+ speedMin: 100,
569
+ speedMax: 700,
570
+ gravity: 100,
571
+ lifeMin: 4,
572
+ lifeMax: 7.4,
573
+ sizeMin: 3.1,
574
+ sizeMax: 4.2,
575
+ spin: 0.3,
576
+ wobble: 0.42,
577
+ coneDeg: 40,
578
+ shape: 'circle|square',
579
+ spinCoverage: 0.92,
580
+ highlight: 0.8,
581
+ highlightAngleDeg: 20,
582
+ highlightWidth: 0.3,
583
+ },
584
+ burstSparks: {
585
+ enabled: false,
586
+ },
587
+ burstCenterFlash: {
588
+ enabled: false,
589
+ },
590
+ burstTwinkleCloud: {
591
+ enabled: false,
592
+ },
593
+ },
594
+ run: runAchievementUnlock,
595
+ },
596
+ 'confetti-pop': {
597
+ options: {
598
+ timeScale: 3,
599
+ rocket: {
600
+ enabled: false,
601
+ },
602
+ rocketTailEmbers: {
603
+ enabled: false,
604
+ },
605
+ burstForms: {
606
+ count: 110,
607
+ speedMin: 100,
608
+ speedMax: 700,
609
+ gravity: 95,
610
+ lifeMin: 4,
611
+ lifeMax: 7.4,
612
+ sizeMin: 3.1,
613
+ sizeMax: 4.2,
614
+ spin: 0.3,
615
+ wobble: 0.42,
616
+ coneDeg: 40,
617
+ shape: 'circle|square',
618
+ spinCoverage: 0.92,
619
+ highlight: 0.8,
620
+ highlightAngleDeg: 20,
621
+ highlightWidth: 0.3,
622
+ },
623
+ burstSparks: {
624
+ enabled: false,
625
+ },
626
+ burstCenterFlash: {
627
+ enabled: false,
628
+ },
629
+ burstTwinkleCloud: {
630
+ enabled: false,
631
+ },
632
+ },
633
+ run: runConfettiPop,
634
+ },
635
+ 'purchase-success': {
636
+ options: {
637
+ timeScale: 1,
638
+ rocket: {
639
+ cascade: {
640
+ mode: 'rocket',
641
+ bursts: 16,
642
+ childBursts: 1,
643
+ childSpeedMin: 400,
644
+ delay: 160,
645
+ spreadX: 232,
646
+ spreadY: 239,
647
+ },
648
+ },
649
+ burstForms: {
650
+ enabled: false,
651
+ },
652
+ burstSparks: {
653
+ count: 54,
654
+ tailScale: 1,
655
+ thickness: 1,
656
+ speedMin: 108,
657
+ speedMax: 284,
658
+ directionDeg: 0,
659
+ gravity: 180,
660
+ lifeMin: 0.8,
661
+ lifeMax: 1.4,
662
+ },
663
+ burstTwinkleCloud: {
664
+ enabled: false,
665
+ },
666
+ },
667
+ run: runRocketLaunch,
668
+ },
669
+ 'victory-finale': {
670
+ options: {
671
+ timeScale: 1.3,
672
+ rocket: {
673
+ cascade: {
674
+ mode: 'rocket',
675
+ bursts: 6,
676
+ childBursts: 3,
677
+ delay: 200,
678
+ childSpeedMin: 100,
679
+ childSpeedMax: 150,
680
+ spreadX: 150,
681
+ spreadY: 300,
682
+ childDirectionDeg: 90,
683
+ childConeDeg: 360,
684
+ },
685
+ },
686
+ },
687
+ run: runRocketLaunch,
688
+ },
689
+ snowfall: {
690
+ options: {
691
+ rocket: {
692
+ enabled: false,
693
+ },
694
+ rocketTailEmbers: {
695
+ enabled: false,
696
+ },
697
+ burstForms: {
698
+ count: 1,
699
+ speedMin: 0,
700
+ speedMax: 0,
701
+ gravity: 60,
702
+ lifeMin: 12,
703
+ lifeMax: 140,
704
+ sizeMin: 1.6,
705
+ sizeMax: 4,
706
+ spin: 0.6,
707
+ wobble: 0.9,
708
+ coneDeg: 20,
709
+ palette: ['#ffffff', '#ffffff', '#ffffff', '#80dfff'],
710
+ shape: 'glyph',
711
+ glyph: '❄•',
712
+ spinCoverage: 0.25,
713
+ highlight: 0.22,
714
+ highlightWidth: 0.25,
715
+ },
716
+ burstSparks: {
717
+ enabled: false,
718
+ },
719
+ burstCenterFlash: {
720
+ enabled: false,
721
+ },
722
+ burstTwinkleCloud: {
723
+ enabled: false,
724
+ },
725
+ timeScale: 1,
726
+ sizeScale: 1,
727
+ },
728
+ run: runSnowfall,
729
+ },
730
+ 'leaf-fall': {
731
+ options: {
732
+ timeScale: 1,
733
+ sizeScale: 1,
734
+ rocket: {
735
+ enabled: false,
736
+ },
737
+ rocketTailEmbers: {
738
+ enabled: false,
739
+ },
740
+ burstForms: {
741
+ enabled: true,
742
+ shape: 'glyph',
743
+ glyph: '🍂🍁',
744
+ colorMode: 'palette',
745
+ palette: ['#f6c453', '#e98b2a', '#b85f1f', '#8f3f16', '#f6c453', '#e98b2a', '#b85f1f', '#8f3f16', '#ff4d4d', '#c1ff00'],
746
+ count: 1,
747
+ speedMin: 0,
748
+ speedMax: 0,
749
+ gravity: 75,
750
+ lifeMin: 20,
751
+ lifeMax: 150,
752
+ sizeMin: 3,
753
+ sizeMax: 6,
754
+ wobble: 0.9,
755
+ spin: 0.1,
756
+ spinCoverage: 0.6,
757
+ spinDuration: 6,
758
+ spinPause: 6,
759
+ highlight: 0,
760
+ },
761
+ burstSparks: {
762
+ enabled: false,
763
+ },
764
+ burstCenterFlash: {
765
+ enabled: false,
766
+ },
767
+ burstTwinkleCloud: {
768
+ enabled: false,
769
+ },
770
+ },
771
+ run: runLeafFall,
772
+ },
773
+ 'ambient-sparkle': {
774
+ options: {
775
+ timeScale: 1,
776
+ rocket: {
777
+ enabled: false,
778
+ },
779
+ rocketTailEmbers: {
780
+ enabled: false,
781
+ },
782
+ burstForms: {
783
+ enabled: false,
784
+ },
785
+ burstSparks: {
786
+ enabled: false,
787
+ },
788
+ burstCenterFlash: {
789
+ enabled: false,
790
+ },
791
+ burstTwinkleCloud: {
792
+ count: 3000,
793
+ radius: 1000,
794
+ startDelay: 0,
795
+ duration: 60,
796
+ twinkleLife: 2,
797
+ brightness: 3,
798
+ spawnSpread: 1,
799
+ },
800
+ },
801
+ run: runAmbientSparkle,
802
+ },
803
+ });
804
+
805
+ export function resolveFireworksLaunchPreset(name) {
806
+ if (typeof name !== 'string') {
807
+ return null;
808
+ }
809
+
810
+ return FIREWORKS_LAUNCH_PRESETS[name] || null;
811
+ }