@basmilius/sparkle 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/index.d.mts +317 -459
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/index.mjs +1258 -949
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +6 -2
  6. package/src/aurora/index.ts +9 -3
  7. package/src/aurora/layer.ts +57 -29
  8. package/src/balloons/index.ts +9 -3
  9. package/src/balloons/layer.ts +50 -19
  10. package/src/bubbles/index.ts +9 -3
  11. package/src/bubbles/layer.ts +30 -17
  12. package/src/canvas.ts +92 -2
  13. package/src/color.ts +11 -2
  14. package/src/confetti/index.ts +15 -3
  15. package/src/confetti/layer.ts +8 -5
  16. package/src/confetti/particle.ts +12 -11
  17. package/src/confetti/shapes.ts +84 -97
  18. package/src/donuts/consts.ts +2 -2
  19. package/src/donuts/index.ts +9 -3
  20. package/src/donuts/layer.ts +43 -12
  21. package/src/effect.ts +107 -0
  22. package/src/fade.ts +87 -0
  23. package/src/fireflies/index.ts +9 -3
  24. package/src/fireflies/layer.ts +26 -9
  25. package/src/fireflies/particle.ts +2 -2
  26. package/src/firepit/index.ts +9 -3
  27. package/src/firepit/layer.ts +26 -7
  28. package/src/fireworks/create-explosion.ts +237 -0
  29. package/src/fireworks/explosion.ts +1 -1
  30. package/src/fireworks/index.ts +15 -3
  31. package/src/fireworks/layer.ts +55 -304
  32. package/src/fireworks/spark.ts +2 -2
  33. package/src/fireworks/types.ts +2 -2
  34. package/src/glitter/index.ts +9 -4
  35. package/src/glitter/layer.ts +15 -7
  36. package/src/glitter/types.ts +10 -0
  37. package/src/index.ts +3 -4
  38. package/src/lanterns/index.ts +9 -4
  39. package/src/lanterns/layer.ts +22 -10
  40. package/src/lanterns/types.ts +8 -0
  41. package/src/layer.ts +13 -11
  42. package/src/leaves/index.ts +9 -4
  43. package/src/leaves/layer.ts +21 -14
  44. package/src/leaves/types.ts +9 -0
  45. package/src/lightning/index.ts +9 -4
  46. package/src/lightning/layer.ts +4 -4
  47. package/src/lightning/system.ts +3 -3
  48. package/src/lightning/types.ts +10 -2
  49. package/src/matrix/index.ts +9 -4
  50. package/src/matrix/layer.ts +15 -7
  51. package/src/matrix/types.ts +9 -0
  52. package/src/orbits/index.ts +9 -4
  53. package/src/orbits/layer.ts +51 -21
  54. package/src/orbits/types.ts +12 -1
  55. package/src/particles/index.ts +9 -3
  56. package/src/particles/layer.ts +55 -12
  57. package/src/petals/index.ts +9 -3
  58. package/src/petals/layer.ts +29 -13
  59. package/src/plasma/index.ts +9 -3
  60. package/src/plasma/layer.ts +21 -6
  61. package/src/rain/index.ts +9 -3
  62. package/src/rain/layer.ts +30 -8
  63. package/src/sandstorm/index.ts +9 -3
  64. package/src/sandstorm/layer.ts +26 -9
  65. package/src/scene.ts +204 -0
  66. package/src/shooting-stars/system.ts +26 -24
  67. package/src/shooting-stars/types.ts +2 -1
  68. package/src/simulation-canvas.ts +45 -6
  69. package/src/snow/index.ts +9 -3
  70. package/src/snow/layer.ts +24 -11
  71. package/src/sparklers/index.ts +13 -3
  72. package/src/sparklers/layer.ts +61 -15
  73. package/src/stars/index.ts +9 -3
  74. package/src/stars/layer.ts +28 -22
  75. package/src/streamers/index.ts +9 -3
  76. package/src/streamers/layer.ts +18 -6
  77. package/src/streamers/types.ts +1 -1
  78. package/src/waves/index.ts +9 -3
  79. package/src/waves/layer.ts +42 -45
  80. package/src/waves/types.ts +1 -0
  81. package/src/wormhole/index.ts +9 -3
  82. package/src/wormhole/layer.ts +22 -6
  83. package/src/aurora/simulation.ts +0 -19
  84. package/src/balloons/simulation.ts +0 -19
  85. package/src/bubbles/simulation.ts +0 -20
  86. package/src/confetti/simulation.ts +0 -27
  87. package/src/donuts/simulation.ts +0 -25
  88. package/src/fireflies/simulation.ts +0 -18
  89. package/src/firepit/simulation.ts +0 -17
  90. package/src/fireworks/simulation.ts +0 -18
  91. package/src/glitter/simulation.ts +0 -19
  92. package/src/lanterns/simulation.ts +0 -17
  93. package/src/layered.ts +0 -185
  94. package/src/leaves/simulation.ts +0 -18
  95. package/src/lightning/simulation.ts +0 -17
  96. package/src/matrix/simulation.ts +0 -18
  97. package/src/orbits/simulation.ts +0 -19
  98. package/src/particles/simulation.ts +0 -26
  99. package/src/petals/simulation.ts +0 -18
  100. package/src/plasma/simulation.ts +0 -17
  101. package/src/rain/simulation.ts +0 -21
  102. package/src/sandstorm/simulation.ts +0 -18
  103. package/src/snow/simulation.ts +0 -17
  104. package/src/sparklers/simulation.ts +0 -30
  105. package/src/stars/simulation.ts +0 -22
  106. package/src/streamers/simulation.ts +0 -16
  107. package/src/waves/simulation.ts +0 -18
  108. package/src/wormhole/simulation.ts +0 -17
@@ -1,12 +1,17 @@
1
1
  import { LimitedFrameRateCanvas } from './canvas';
2
- import type { SimulationLayer } from './layer';
2
+ import { applyEdgeFade } from './fade';
3
+ import type { EdgeFade, SimulationLayer } from './layer';
3
4
 
4
5
  export class SimulationCanvas extends LimitedFrameRateCanvas {
5
6
  readonly #simulation: SimulationLayer;
7
+ readonly #contextOptions: CanvasRenderingContext2DSettings;
8
+ #offscreen: HTMLCanvasElement | null = null;
9
+ #offscreenCtx: CanvasRenderingContext2D | null = null;
6
10
 
7
11
  constructor(canvas: HTMLCanvasElement, simulation: SimulationLayer, frameRate: number = 60, options: CanvasRenderingContext2DSettings = {colorSpace: 'display-p3'}) {
8
12
  super(canvas, frameRate, options);
9
13
  this.#simulation = simulation;
14
+ this.#contextOptions = options;
10
15
 
11
16
  canvas.style.position = 'absolute';
12
17
  canvas.style.top = '0';
@@ -15,6 +20,11 @@ export class SimulationCanvas extends LimitedFrameRateCanvas {
15
20
  canvas.style.width = '100%';
16
21
  }
17
22
 
23
+ withFade(fade: EdgeFade): this {
24
+ this.#simulation.fade = fade;
25
+ return this;
26
+ }
27
+
18
28
  start(): void {
19
29
  this.#simulation.onMount(this.canvas);
20
30
  super.start();
@@ -26,13 +36,25 @@ export class SimulationCanvas extends LimitedFrameRateCanvas {
26
36
  }
27
37
 
28
38
  draw(): void {
29
- this.canvas.height = this.height;
30
- this.canvas.width = this.width;
39
+ const dpr = this.dpr;
40
+ this.canvas.height = this.height * dpr;
41
+ this.canvas.width = this.width * dpr;
31
42
 
32
43
  const ctx = this.context;
33
- ctx.save();
34
- this.#simulation.draw(ctx, this.width, this.height);
35
- ctx.restore();
44
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
45
+
46
+ if (this.#simulation.fade) {
47
+ const offCtx = this.#getOffscreenCtx(this.width * dpr, this.height * dpr);
48
+ offCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
49
+ offCtx.clearRect(0, 0, this.width, this.height);
50
+ this.#simulation.draw(offCtx, this.width, this.height);
51
+ applyEdgeFade(offCtx, this.width, this.height, this.#simulation.fade);
52
+ ctx.drawImage(this.#offscreen!, 0, 0, this.width, this.height);
53
+ } else {
54
+ ctx.save();
55
+ this.#simulation.draw(ctx, this.width, this.height);
56
+ ctx.restore();
57
+ }
36
58
  }
37
59
 
38
60
  tick(): void {
@@ -42,6 +64,23 @@ export class SimulationCanvas extends LimitedFrameRateCanvas {
42
64
 
43
65
  onResize(): void {
44
66
  super.onResize();
67
+
68
+ if (this.#offscreen) {
69
+ this.#offscreen.width = this.width * this.dpr;
70
+ this.#offscreen.height = this.height * this.dpr;
71
+ }
72
+
45
73
  this.#simulation.onResize(this.width, this.height);
46
74
  }
75
+
76
+ #getOffscreenCtx(width: number, height: number): CanvasRenderingContext2D {
77
+ if (!this.#offscreen) {
78
+ this.#offscreen = document.createElement('canvas');
79
+ this.#offscreen.width = width;
80
+ this.#offscreen.height = height;
81
+ this.#offscreenCtx = this.#offscreen.getContext('2d', this.#contextOptions)!;
82
+ }
83
+
84
+ return this.#offscreenCtx!;
85
+ }
47
86
  }
package/src/snow/index.ts CHANGED
@@ -1,3 +1,9 @@
1
- export { SnowLayer } from './layer';
2
- export { SnowSimulation } from './simulation';
3
- export type { SnowSimulationConfig } from './simulation';
1
+ import { Snow } from './layer';
2
+ import type { SnowConfig } from './layer';
3
+ import type { Effect } from '../effect';
4
+
5
+ export function createSnow(config?: SnowConfig): Effect<SnowConfig> {
6
+ return new Snow(config);
7
+ }
8
+
9
+ export type { SnowConfig };
package/src/snow/layer.ts CHANGED
@@ -1,17 +1,24 @@
1
1
  import { parseColor } from '../color';
2
- import { SimulationLayer } from '../layer';
2
+ import { Effect } from '../effect';
3
3
  import { MULBERRY } from './consts';
4
- import type { SnowSimulationConfig } from './simulation';
5
4
  import type { Snowflake } from './snowflake';
6
5
 
6
+ export interface SnowConfig {
7
+ readonly fillStyle?: string;
8
+ readonly particles?: number;
9
+ readonly scale?: number;
10
+ readonly size?: number;
11
+ readonly speed?: number;
12
+ }
13
+
7
14
  const SPRITE_SIZE = 64;
8
15
  const SPRITE_CENTER = SPRITE_SIZE / 2;
9
16
  const SPRITE_RADIUS = SPRITE_SIZE / 2;
10
17
 
11
- export class SnowLayer extends SimulationLayer {
18
+ export class Snow extends Effect<SnowConfig> {
12
19
  readonly #scale: number;
13
20
  readonly #size: number;
14
- readonly #speed: number;
21
+ #speed: number;
15
22
  readonly #baseOpacity: number;
16
23
  #maxParticles: number;
17
24
  #time: number = 0;
@@ -20,7 +27,7 @@ export class SnowLayer extends SimulationLayer {
20
27
  #sprites: HTMLCanvasElement[] = [];
21
28
  #height: number = 540;
22
29
 
23
- constructor(config: SnowSimulationConfig = {}) {
30
+ constructor(config: SnowConfig = {}) {
24
31
  super();
25
32
 
26
33
  this.#scale = config.scale ?? 1;
@@ -42,6 +49,12 @@ export class SnowLayer extends SimulationLayer {
42
49
  }
43
50
  }
44
51
 
52
+ configure(config: Partial<SnowConfig>): void {
53
+ if (config.speed !== undefined) {
54
+ this.#speed = config.speed;
55
+ }
56
+ }
57
+
45
58
  onResize(_width: number, height: number): void {
46
59
  this.#height = height;
47
60
  }
@@ -54,8 +67,8 @@ export class SnowLayer extends SimulationLayer {
54
67
  this.#time += 0.015 * speedFactor * dt;
55
68
 
56
69
  const wind = Math.sin(this.#time * 0.7) * 0.5
57
- + Math.sin(this.#time * 1.9 + 3) * 0.25
58
- + Math.sin(this.#time * 4.3 + 1) * 0.1;
70
+ + Math.sin(this.#time * 1.9 + 3) * 0.25
71
+ + Math.sin(this.#time * 4.3 + 1) * 0.1;
59
72
 
60
73
  for (let index = 0; index < this.#snowflakes.length; index++) {
61
74
  const snowflake = this.#snowflakes[index];
@@ -104,9 +117,9 @@ export class SnowLayer extends SimulationLayer {
104
117
  ctx.globalAlpha = this.#baseOpacity * (0.15 + snowflake.depth * 0.85);
105
118
 
106
119
  if (snowflake.spriteIndex === 3) {
107
- ctx.save();
108
- ctx.translate(px, py);
109
- ctx.rotate(snowflake.rotation);
120
+ const cos = Math.cos(snowflake.rotation);
121
+ const sin = Math.sin(snowflake.rotation);
122
+ ctx.setTransform(cos, sin, -sin, cos, px, py);
110
123
  ctx.drawImage(
111
124
  this.#sprites[snowflake.spriteIndex],
112
125
  -displayRadius,
@@ -114,7 +127,7 @@ export class SnowLayer extends SimulationLayer {
114
127
  displaySize,
115
128
  displaySize
116
129
  );
117
- ctx.restore();
130
+ ctx.resetTransform();
118
131
  } else {
119
132
  ctx.drawImage(
120
133
  this.#sprites[snowflake.spriteIndex],
@@ -1,6 +1,16 @@
1
- export { SparklerLayer } from './layer';
1
+ import { Sparklers } from './layer';
2
+ import type { SparklersConfig } from './layer';
3
+ import type { Effect } from '../effect';
4
+
5
+ export interface SparklersInstance extends Effect<SparklersConfig> {
6
+ moveTo(x: number, y: number): void;
7
+ }
8
+
9
+ export function createSparklers(config?: SparklersConfig): SparklersInstance {
10
+ return new Sparklers(config) as SparklersInstance;
11
+ }
12
+
2
13
  export { SparklerParticle } from './particle';
3
- export { SparklerSimulation } from './simulation';
14
+ export type { SparklersConfig };
4
15
  export type { SparklerParticleConfig } from './particle';
5
- export type { SparklerSimulationConfig } from './simulation';
6
16
  export type { SparklerSpark } from './types';
@@ -1,30 +1,44 @@
1
1
  import { hexToRGB } from '@basmilius/utils';
2
- import { SimulationLayer } from '../layer';
2
+ import { Effect } from '../effect';
3
3
  import { MULBERRY } from './consts';
4
- import type { SparklerSimulationConfig } from './simulation';
5
4
  import type { SparklerSpark } from './types';
6
5
 
6
+ export interface SparklersConfig {
7
+ readonly emitRate?: number;
8
+ readonly maxSparks?: number;
9
+ readonly colors?: string[];
10
+ readonly speed?: [number, number];
11
+ readonly friction?: number;
12
+ readonly gravity?: number;
13
+ readonly decay?: [number, number];
14
+ readonly trailLength?: number;
15
+ readonly hoverMode?: boolean;
16
+ readonly scale?: number;
17
+ }
18
+
7
19
  const DEFAULT_COLORS = ['#ffcc33', '#ff9900', '#ffffff', '#ffee88'];
8
20
 
9
- export class SparklerLayer extends SimulationLayer {
10
- readonly #scale: number;
11
- readonly #emitRate: number;
21
+ export class Sparklers extends Effect<SparklersConfig> {
22
+ #scale: number;
23
+ #emitRate: number;
12
24
  readonly #maxSparks: number;
13
25
  readonly #colorRGBs: [number, number, number][];
14
26
  readonly #speedRange: [number, number];
15
- readonly #friction: number;
16
- readonly #gravity: number;
27
+ #friction: number;
28
+ #gravity: number;
17
29
  readonly #decayRange: [number, number];
18
- readonly #trailLength: number;
19
- readonly #hoverMode: boolean;
30
+ #trailLength: number;
31
+ #hoverMode: boolean;
20
32
  readonly #onMouseMoveBound: (evt: MouseEvent) => void;
21
33
  readonly #onMouseLeaveBound: () => void;
22
34
  #emitX: number = 0.5;
23
35
  #emitY: number = 0.5;
24
36
  #mouseOnCanvas: boolean = false;
25
37
  #sparks: SparklerSpark[] = [];
38
+ #mountedCanvas: HTMLCanvasElement | null = null;
39
+ #cachedRect: DOMRect | null = null;
26
40
 
27
- constructor(config: SparklerSimulationConfig = {}) {
41
+ constructor(config: SparklersConfig = {}) {
28
42
  super();
29
43
 
30
44
  this.#scale = config.scale ?? 1;
@@ -44,12 +58,15 @@ export class SparklerLayer extends SimulationLayer {
44
58
  this.#onMouseLeaveBound = this.#onMouseLeave.bind(this);
45
59
  }
46
60
 
47
- setPosition(x: number, y: number): void {
61
+ moveTo(x: number, y: number): void {
48
62
  this.#emitX = x;
49
63
  this.#emitY = y;
50
64
  }
51
65
 
52
66
  onMount(canvas: HTMLCanvasElement): void {
67
+ this.#mountedCanvas = canvas;
68
+ this.#cachedRect = canvas.getBoundingClientRect();
69
+
53
70
  if (this.#hoverMode) {
54
71
  canvas.addEventListener('mousemove', this.#onMouseMoveBound, {passive: true});
55
72
  canvas.addEventListener('mouseleave', this.#onMouseLeaveBound, {passive: true});
@@ -59,6 +76,35 @@ export class SparklerLayer extends SimulationLayer {
59
76
  onUnmount(canvas: HTMLCanvasElement): void {
60
77
  canvas.removeEventListener('mousemove', this.#onMouseMoveBound);
61
78
  canvas.removeEventListener('mouseleave', this.#onMouseLeaveBound);
79
+ this.#mountedCanvas = null;
80
+ this.#cachedRect = null;
81
+ }
82
+
83
+ onResize(): void {
84
+ if (this.#mountedCanvas) {
85
+ this.#cachedRect = this.#mountedCanvas.getBoundingClientRect();
86
+ }
87
+ }
88
+
89
+ configure(config: Partial<SparklersConfig>): void {
90
+ if (config.scale !== undefined) {
91
+ this.#scale = config.scale;
92
+ }
93
+ if (config.emitRate !== undefined) {
94
+ this.#emitRate = config.emitRate;
95
+ }
96
+ if (config.friction !== undefined) {
97
+ this.#friction = config.friction;
98
+ }
99
+ if (config.gravity !== undefined) {
100
+ this.#gravity = config.gravity;
101
+ }
102
+ if (config.trailLength !== undefined) {
103
+ this.#trailLength = config.trailLength;
104
+ }
105
+ if (config.hoverMode !== undefined) {
106
+ this.#hoverMode = config.hoverMode;
107
+ }
62
108
  }
63
109
 
64
110
  tick(dt: number, width: number, height: number): void {
@@ -70,6 +116,7 @@ export class SparklerLayer extends SimulationLayer {
70
116
  }
71
117
  }
72
118
 
119
+ const frictionFactor = Math.pow(this.#friction, dt);
73
120
  let alive = 0;
74
121
 
75
122
  for (let i = 0; i < this.#sparks.length; i++) {
@@ -81,8 +128,8 @@ export class SparklerLayer extends SimulationLayer {
81
128
  spark.trail.shift();
82
129
  }
83
130
 
84
- spark.vx *= Math.pow(this.#friction, dt);
85
- spark.vy *= Math.pow(this.#friction, dt);
131
+ spark.vx *= frictionFactor;
132
+ spark.vy *= frictionFactor;
86
133
  spark.vy += this.#gravity * this.#scale * dt;
87
134
 
88
135
  spark.x += spark.vx * dt;
@@ -143,8 +190,7 @@ export class SparklerLayer extends SimulationLayer {
143
190
  }
144
191
 
145
192
  #onMouseMove(evt: MouseEvent): void {
146
- const target = evt.currentTarget as HTMLCanvasElement;
147
- const rect = target.getBoundingClientRect();
193
+ const rect = this.#cachedRect ?? (evt.currentTarget as HTMLCanvasElement).getBoundingClientRect();
148
194
  this.#emitX = (evt.clientX - rect.left) / rect.width;
149
195
  this.#emitY = (evt.clientY - rect.top) / rect.height;
150
196
  this.#mouseOnCanvas = true;
@@ -1,4 +1,10 @@
1
- export { StarLayer } from './layer';
2
- export { StarSimulation } from './simulation';
3
- export type { StarSimulationConfig } from './simulation';
1
+ import { Stars } from './layer';
2
+ import type { StarsConfig } from './layer';
3
+ import type { Effect } from '../effect';
4
+
5
+ export function createStars(config?: StarsConfig): Effect<StarsConfig> {
6
+ return new Stars(config);
7
+ }
8
+
9
+ export type { StarsConfig };
4
10
  export type { Star, StarMode, ShootingStar } from './types';
@@ -1,29 +1,38 @@
1
1
  import { hexToRGB } from '@basmilius/utils';
2
- import { SimulationLayer } from '../layer';
2
+ import { Effect } from '../effect';
3
3
  import { ShootingStarSystem } from '../shooting-stars';
4
4
  import { MULBERRY } from './consts';
5
- import type { StarSimulationConfig } from './simulation';
6
5
  import type { Star, StarMode } from './types';
7
6
 
8
- export class StarLayer extends SimulationLayer {
7
+ export interface StarsConfig {
8
+ readonly mode?: StarMode;
9
+ readonly starCount?: number;
10
+ readonly shootingInterval?: [number, number];
11
+ readonly shootingSpeed?: number;
12
+ readonly twinkleSpeed?: number;
13
+ readonly color?: string;
14
+ readonly shootingColor?: string;
15
+ readonly trailLength?: number;
16
+ readonly scale?: number;
17
+ }
18
+
19
+ export class Stars extends Effect<StarsConfig> {
9
20
  readonly #mode: StarMode;
10
- readonly #twinkleSpeed: number;
21
+ #twinkleSpeed: number;
11
22
  readonly #colorRGB: [number, number, number];
12
- readonly #scale: number;
13
- readonly #verticalFade: [number, number] | null;
23
+ #scale: number;
14
24
  readonly #shootingStarSystem: ShootingStarSystem | null;
15
25
  #starCount: number;
16
26
  #time: number = 0;
17
27
  #stars: Star[] = [];
18
28
 
19
- constructor(config: StarSimulationConfig = {}) {
29
+ constructor(config: StarsConfig = {}) {
20
30
  super();
21
31
 
22
32
  this.#mode = config.mode ?? 'both';
23
33
  this.#starCount = config.starCount ?? 150;
24
34
  this.#twinkleSpeed = config.twinkleSpeed ?? 1;
25
35
  this.#scale = config.scale ?? 1;
26
- this.#verticalFade = config.verticalFade ?? null;
27
36
 
28
37
  this.#colorRGB = hexToRGB(config.color ?? '#ffffff');
29
38
 
@@ -42,8 +51,7 @@ export class StarLayer extends SimulationLayer {
42
51
  alphaMin: 0.8,
43
52
  alphaRange: 0.2,
44
53
  decayMin: 0.01,
45
- decayRange: 0.015,
46
- verticalFade: this.#verticalFade ?? undefined
54
+ decayRange: 0.015
47
55
  },
48
56
  () => MULBERRY.next()
49
57
  )
@@ -56,6 +64,15 @@ export class StarLayer extends SimulationLayer {
56
64
  }
57
65
  }
58
66
 
67
+ configure(config: Partial<StarsConfig>): void {
68
+ if (config.twinkleSpeed !== undefined) {
69
+ this.#twinkleSpeed = config.twinkleSpeed;
70
+ }
71
+ if (config.scale !== undefined) {
72
+ this.#scale = config.scale;
73
+ }
74
+ }
75
+
59
76
  tick(dt: number, width: number, height: number): void {
60
77
  this.#time += 0.02 * dt;
61
78
  this.#shootingStarSystem?.tick(dt, width, height);
@@ -71,18 +88,7 @@ export class StarLayer extends SimulationLayer {
71
88
  for (const star of this.#stars) {
72
89
  const px = star.x * width;
73
90
  const py = star.y * height;
74
- let alpha = star.brightness * (0.3 + 0.7 * (0.5 + 0.5 * Math.sin(this.#time * star.twinkleSpeed * this.#twinkleSpeed + star.twinklePhase)));
75
-
76
- if (this.#verticalFade) {
77
- const [fadeStart, fadeEnd] = this.#verticalFade;
78
- const fadeFactor = 1 - Math.max(0, Math.min(1, (star.y - fadeStart) / (fadeEnd - fadeStart)));
79
- alpha *= fadeFactor;
80
-
81
- if (alpha <= 0) {
82
- continue;
83
- }
84
- }
85
-
91
+ const alpha = star.brightness * (0.3 + 0.7 * (0.5 + 0.5 * Math.sin(this.#time * star.twinkleSpeed * this.#twinkleSpeed + star.twinklePhase)));
86
92
  const size = star.size * this.#scale;
87
93
 
88
94
  ctx.globalAlpha = alpha;
@@ -1,4 +1,10 @@
1
- export { StreamerLayer } from './layer';
2
- export { StreamerSimulation } from './simulation';
3
- export type { StreamerSimulationConfig } from './simulation';
1
+ import { Streamers } from './layer';
2
+ import type { StreamersConfig } from './layer';
3
+ import type { Effect } from '../effect';
4
+
5
+ export function createStreamers(config?: StreamersConfig): Effect<StreamersConfig> {
6
+ return new Streamers(config);
7
+ }
8
+
9
+ export type { StreamersConfig };
4
10
  export type { Streamer } from './types';
@@ -1,19 +1,25 @@
1
- import { SimulationLayer } from '../layer';
1
+ import { Effect } from '../effect';
2
2
  import { MULBERRY, STREAMER_COLORS } from './consts';
3
- import type { StreamerSimulationConfig } from './simulation';
4
3
  import type { Streamer } from './types';
5
4
 
6
- export class StreamerLayer extends SimulationLayer {
5
+ export interface StreamersConfig {
6
+ readonly count?: number;
7
+ readonly colors?: string[];
8
+ readonly speed?: number;
9
+ readonly scale?: number;
10
+ }
11
+
12
+ export class Streamers extends Effect<StreamersConfig> {
7
13
  readonly #colors: string[];
8
14
  readonly #scale: number;
9
- readonly #speed: number;
15
+ #speed: number;
10
16
  #count: number;
11
17
  #streamers: Streamer[] = [];
12
18
  #width: number = 960;
13
19
  #height: number = 540;
14
20
  #initialized: boolean = false;
15
21
 
16
- constructor(config: StreamerSimulationConfig = {}) {
22
+ constructor(config: StreamersConfig = {}) {
17
23
  super();
18
24
 
19
25
  this.#colors = config.colors ?? STREAMER_COLORS;
@@ -40,6 +46,12 @@ export class StreamerLayer extends SimulationLayer {
40
46
  }
41
47
  }
42
48
 
49
+ configure(config: Partial<StreamersConfig>): void {
50
+ if (config.speed !== undefined) {
51
+ this.#speed = config.speed;
52
+ }
53
+ }
54
+
43
55
  tick(dt: number, width: number, height: number): void {
44
56
  this.#width = width;
45
57
  this.#height = height;
@@ -98,7 +110,7 @@ export class StreamerLayer extends SimulationLayer {
98
110
  const swayPhase = MULBERRY.next() * Math.PI * 2;
99
111
  const color = this.#colors[Math.floor(MULBERRY.next() * this.#colors.length)];
100
112
 
101
- const segments: {x: number; y: number}[] = [];
113
+ const segments: { x: number; y: number }[] = [];
102
114
  const segmentLength = length / segmentCount;
103
115
 
104
116
  for (let i = 0; i < segmentCount; i++) {
@@ -3,7 +3,7 @@ export type Streamer = {
3
3
  y: number;
4
4
  length: number;
5
5
  width: number;
6
- segments: {x: number; y: number}[];
6
+ segments: { x: number; y: number }[];
7
7
  fallSpeed: number;
8
8
  swayPhase: number;
9
9
  swaySpeed: number;
@@ -1,4 +1,10 @@
1
- export { WaveLayer } from './layer';
2
- export { WaveSimulation } from './simulation';
3
- export type { WaveSimulationConfig } from './simulation';
1
+ import { Waves } from './layer';
2
+ import type { WavesConfig } from './layer';
3
+ import type { Effect } from '../effect';
4
+
5
+ export function createWaves(config?: WavesConfig): Effect<WavesConfig> {
6
+ return new Waves(config);
7
+ }
8
+
9
+ export type { WavesConfig };
4
10
  export type { Wave } from './types';