@basmilius/sparkle 2.0.0 → 2.1.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 (148) hide show
  1. package/dist/index.d.mts +1192 -14
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/index.mjs +4552 -370
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +2 -1
  6. package/src/aurora/consts.ts +3 -0
  7. package/src/aurora/index.ts +4 -0
  8. package/src/aurora/layer.ts +152 -0
  9. package/src/aurora/simulation.ts +19 -0
  10. package/src/aurora/types.ts +13 -0
  11. package/src/balloons/consts.ts +3 -0
  12. package/src/balloons/index.ts +6 -0
  13. package/src/balloons/layer.ts +138 -0
  14. package/src/balloons/particle.ts +110 -0
  15. package/src/balloons/simulation.ts +19 -0
  16. package/src/balloons/types.ts +14 -0
  17. package/src/bubbles/consts.ts +3 -0
  18. package/src/bubbles/index.ts +4 -0
  19. package/src/bubbles/layer.ts +233 -0
  20. package/src/bubbles/simulation.ts +20 -0
  21. package/src/bubbles/types.ts +21 -0
  22. package/src/canvas.ts +20 -1
  23. package/src/color.ts +10 -0
  24. package/src/confetti/consts.ts +13 -13
  25. package/src/confetti/index.ts +6 -0
  26. package/src/confetti/layer.ts +152 -0
  27. package/src/confetti/particle.ts +105 -0
  28. package/src/confetti/shapes.ts +104 -0
  29. package/src/confetti/simulation.ts +9 -203
  30. package/src/confetti/types.ts +4 -1
  31. package/src/distance.ts +1 -1
  32. package/src/donuts/consts.ts +19 -0
  33. package/src/donuts/donut.ts +12 -0
  34. package/src/donuts/index.ts +3 -0
  35. package/src/donuts/layer.ts +270 -0
  36. package/src/donuts/simulation.ts +25 -0
  37. package/src/fireflies/consts.ts +3 -0
  38. package/src/fireflies/index.ts +6 -0
  39. package/src/fireflies/layer.ts +152 -0
  40. package/src/fireflies/particle.ts +124 -0
  41. package/src/fireflies/simulation.ts +18 -0
  42. package/src/fireflies/types.ts +17 -0
  43. package/src/firepit/consts.ts +3 -0
  44. package/src/firepit/index.ts +4 -0
  45. package/src/firepit/layer.ts +174 -0
  46. package/src/firepit/simulation.ts +17 -0
  47. package/src/firepit/types.ts +20 -0
  48. package/src/fireworks/explosion.ts +8 -8
  49. package/src/fireworks/firework.ts +9 -8
  50. package/src/fireworks/index.ts +6 -2
  51. package/src/fireworks/layer.ts +452 -0
  52. package/src/fireworks/simulation.ts +9 -484
  53. package/src/fireworks/spark.ts +7 -7
  54. package/src/glitter/consts.ts +13 -0
  55. package/src/glitter/index.ts +4 -0
  56. package/src/glitter/layer.ts +173 -0
  57. package/src/glitter/simulation.ts +19 -0
  58. package/src/glitter/types.ts +23 -0
  59. package/src/index.ts +28 -0
  60. package/src/lanterns/consts.ts +13 -0
  61. package/src/lanterns/index.ts +4 -0
  62. package/src/lanterns/layer.ts +166 -0
  63. package/src/lanterns/simulation.ts +17 -0
  64. package/src/lanterns/types.ts +14 -0
  65. package/src/layer.ts +24 -0
  66. package/src/layered.ts +185 -0
  67. package/src/leaves/consts.ts +16 -0
  68. package/src/leaves/index.ts +4 -0
  69. package/src/leaves/layer.ts +251 -0
  70. package/src/leaves/simulation.ts +18 -0
  71. package/src/leaves/types.ts +16 -0
  72. package/src/lightning/consts.ts +3 -0
  73. package/src/lightning/index.ts +6 -0
  74. package/src/lightning/layer.ts +41 -0
  75. package/src/lightning/simulation.ts +17 -0
  76. package/src/lightning/system.ts +196 -0
  77. package/src/lightning/types.ts +12 -0
  78. package/src/matrix/consts.ts +5 -0
  79. package/src/matrix/index.ts +4 -0
  80. package/src/matrix/layer.ts +146 -0
  81. package/src/matrix/simulation.ts +18 -0
  82. package/src/matrix/types.ts +8 -0
  83. package/src/orbits/consts.ts +13 -0
  84. package/src/orbits/index.ts +4 -0
  85. package/src/orbits/layer.ts +183 -0
  86. package/src/orbits/simulation.ts +19 -0
  87. package/src/orbits/types.ts +16 -0
  88. package/src/particles/consts.ts +3 -0
  89. package/src/particles/index.ts +4 -0
  90. package/src/particles/layer.ts +317 -0
  91. package/src/particles/simulation.ts +26 -0
  92. package/src/particles/types.ts +10 -0
  93. package/src/petals/consts.ts +13 -0
  94. package/src/petals/index.ts +4 -0
  95. package/src/petals/layer.ts +158 -0
  96. package/src/petals/simulation.ts +18 -0
  97. package/src/petals/types.ts +15 -0
  98. package/src/plasma/consts.ts +3 -0
  99. package/src/plasma/index.ts +4 -0
  100. package/src/plasma/layer.ts +92 -0
  101. package/src/plasma/simulation.ts +17 -0
  102. package/src/plasma/types.ts +5 -0
  103. package/src/rain/consts.ts +3 -0
  104. package/src/rain/index.ts +6 -0
  105. package/src/rain/layer.ts +172 -0
  106. package/src/rain/particle.ts +132 -0
  107. package/src/rain/simulation.ts +21 -0
  108. package/src/rain/types.ts +22 -0
  109. package/src/sandstorm/consts.ts +3 -0
  110. package/src/sandstorm/index.ts +4 -0
  111. package/src/sandstorm/layer.ts +135 -0
  112. package/src/sandstorm/simulation.ts +18 -0
  113. package/src/sandstorm/types.ts +10 -0
  114. package/src/shooting-stars/index.ts +3 -0
  115. package/src/shooting-stars/system.ts +149 -0
  116. package/src/shooting-stars/types.ts +10 -0
  117. package/src/simulation-canvas.ts +47 -0
  118. package/src/snow/consts.ts +2 -2
  119. package/src/snow/index.ts +1 -0
  120. package/src/snow/layer.ts +263 -0
  121. package/src/snow/simulation.ts +4 -288
  122. package/src/sparklers/consts.ts +3 -0
  123. package/src/sparklers/index.ts +6 -0
  124. package/src/sparklers/layer.ts +174 -0
  125. package/src/sparklers/particle.ts +89 -0
  126. package/src/sparklers/simulation.ts +30 -0
  127. package/src/sparklers/types.ts +13 -0
  128. package/src/stars/consts.ts +3 -0
  129. package/src/stars/index.ts +4 -0
  130. package/src/stars/layer.ts +133 -0
  131. package/src/stars/simulation.ts +22 -0
  132. package/src/stars/types.ts +12 -0
  133. package/src/streamers/consts.ts +14 -0
  134. package/src/streamers/index.ts +4 -0
  135. package/src/streamers/layer.ts +211 -0
  136. package/src/streamers/simulation.ts +16 -0
  137. package/src/streamers/types.ts +14 -0
  138. package/src/trail.ts +140 -0
  139. package/src/waves/consts.ts +3 -0
  140. package/src/waves/index.ts +4 -0
  141. package/src/waves/layer.ts +167 -0
  142. package/src/waves/simulation.ts +18 -0
  143. package/src/waves/types.ts +9 -0
  144. package/src/wormhole/consts.ts +3 -0
  145. package/src/wormhole/index.ts +4 -0
  146. package/src/wormhole/layer.ts +181 -0
  147. package/src/wormhole/simulation.ts +17 -0
  148. package/src/wormhole/types.ts +10 -0
@@ -0,0 +1,133 @@
1
+ import { hexToRGB } from '@basmilius/utils';
2
+ import { SimulationLayer } from '../layer';
3
+ import { ShootingStarSystem } from '../shooting-stars';
4
+ import { MULBERRY } from './consts';
5
+ import type { StarSimulationConfig } from './simulation';
6
+ import type { Star, StarMode } from './types';
7
+
8
+ export class StarLayer extends SimulationLayer {
9
+ readonly #mode: StarMode;
10
+ readonly #twinkleSpeed: number;
11
+ readonly #colorRGB: [number, number, number];
12
+ readonly #scale: number;
13
+ readonly #verticalFade: [number, number] | null;
14
+ readonly #shootingStarSystem: ShootingStarSystem | null;
15
+ #starCount: number;
16
+ #time: number = 0;
17
+ #stars: Star[] = [];
18
+
19
+ constructor(config: StarSimulationConfig = {}) {
20
+ super();
21
+
22
+ this.#mode = config.mode ?? 'both';
23
+ this.#starCount = config.starCount ?? 150;
24
+ this.#twinkleSpeed = config.twinkleSpeed ?? 1;
25
+ this.#scale = config.scale ?? 1;
26
+ this.#verticalFade = config.verticalFade ?? null;
27
+
28
+ this.#colorRGB = hexToRGB(config.color ?? '#ffffff');
29
+
30
+ const shootingColorRGB = hexToRGB(config.shootingColor ?? '#ffffff');
31
+ const enableShooting = this.#mode === 'shooting' || this.#mode === 'both';
32
+
33
+ this.#shootingStarSystem = enableShooting
34
+ ? new ShootingStarSystem(
35
+ {
36
+ interval: config.shootingInterval ?? [120, 360],
37
+ color: shootingColorRGB,
38
+ trailLength: config.trailLength ?? 15,
39
+ trailAlphaFactor: 0.6,
40
+ speed: config.shootingSpeed ?? 1,
41
+ scale: this.#scale,
42
+ alphaMin: 0.8,
43
+ alphaRange: 0.2,
44
+ decayMin: 0.01,
45
+ decayRange: 0.015,
46
+ verticalFade: this.#verticalFade ?? undefined
47
+ },
48
+ () => MULBERRY.next()
49
+ )
50
+ : null;
51
+
52
+ if (this.#mode === 'sky' || this.#mode === 'both') {
53
+ for (let i = 0; i < this.#starCount; ++i) {
54
+ this.#stars.push(this.#createStar());
55
+ }
56
+ }
57
+ }
58
+
59
+ tick(dt: number, width: number, height: number): void {
60
+ this.#time += 0.02 * dt;
61
+ this.#shootingStarSystem?.tick(dt, width, height);
62
+ }
63
+
64
+ draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
65
+ const [sr, sg, sb] = this.#colorRGB;
66
+
67
+ // Background stars
68
+ if (this.#mode === 'sky' || this.#mode === 'both') {
69
+ ctx.globalCompositeOperation = 'source-over';
70
+
71
+ for (const star of this.#stars) {
72
+ const px = star.x * width;
73
+ 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
+
86
+ const size = star.size * this.#scale;
87
+
88
+ ctx.globalAlpha = alpha;
89
+
90
+ // Star dot
91
+ ctx.beginPath();
92
+ ctx.arc(px, py, size, 0, Math.PI * 2);
93
+ ctx.fillStyle = `rgb(${sr}, ${sg}, ${sb})`;
94
+ ctx.fill();
95
+
96
+ // Cross sparkle for larger stars
97
+ if (star.size > 1.5) {
98
+ const sparkleLength = size * 3;
99
+ const sparkleAlpha = alpha * 0.4;
100
+ ctx.globalAlpha = sparkleAlpha;
101
+ ctx.strokeStyle = `rgb(${sr}, ${sg}, ${sb})`;
102
+ ctx.lineWidth = 0.5;
103
+
104
+ ctx.beginPath();
105
+ ctx.moveTo(px - sparkleLength, py);
106
+ ctx.lineTo(px + sparkleLength, py);
107
+ ctx.stroke();
108
+
109
+ ctx.beginPath();
110
+ ctx.moveTo(px, py - sparkleLength);
111
+ ctx.lineTo(px, py + sparkleLength);
112
+ ctx.stroke();
113
+ }
114
+ }
115
+ }
116
+
117
+ // Shooting stars
118
+ this.#shootingStarSystem?.draw(ctx);
119
+
120
+ ctx.globalAlpha = 1;
121
+ }
122
+
123
+ #createStar(): Star {
124
+ return {
125
+ x: MULBERRY.next(),
126
+ y: MULBERRY.next(),
127
+ size: 0.5 + MULBERRY.next() * 2,
128
+ twinklePhase: MULBERRY.next() * Math.PI * 2,
129
+ twinkleSpeed: 0.5 + MULBERRY.next() * 2,
130
+ brightness: 0.3 + MULBERRY.next() * 0.7
131
+ };
132
+ }
133
+ }
@@ -0,0 +1,22 @@
1
+ import { SimulationCanvas } from '../simulation-canvas';
2
+ import { StarLayer } from './layer';
3
+
4
+ export interface StarSimulationConfig {
5
+ readonly mode?: import('./types').StarMode;
6
+ readonly starCount?: number;
7
+ readonly shootingInterval?: [number, number];
8
+ readonly shootingSpeed?: number;
9
+ readonly twinkleSpeed?: number;
10
+ readonly color?: string;
11
+ readonly shootingColor?: string;
12
+ readonly trailLength?: number;
13
+ readonly scale?: number;
14
+ readonly verticalFade?: [number, number];
15
+ readonly canvasOptions?: CanvasRenderingContext2DSettings;
16
+ }
17
+
18
+ export class StarSimulation extends SimulationCanvas {
19
+ constructor(canvas: HTMLCanvasElement, config: StarSimulationConfig = {}) {
20
+ super(canvas, new StarLayer(config), 60, config.canvasOptions ?? {colorSpace: 'display-p3'});
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ export type { ShootingStar } from '../shooting-stars/types';
2
+
3
+ export type StarMode = 'sky' | 'shooting' | 'both';
4
+
5
+ export type Star = {
6
+ x: number;
7
+ y: number;
8
+ size: number;
9
+ twinklePhase: number;
10
+ twinkleSpeed: number;
11
+ brightness: number;
12
+ };
@@ -0,0 +1,14 @@
1
+ import { type Mulberry32, mulberry32 } from '@basmilius/utils';
2
+
3
+ export const MULBERRY: Mulberry32 = mulberry32(13);
4
+
5
+ export const STREAMER_COLORS: string[] = [
6
+ '#ff6b6b', // red
7
+ '#ffd93d', // yellow
8
+ '#6bcb77', // green
9
+ '#4d96ff', // blue
10
+ '#ff6bb5', // pink
11
+ '#845ec2', // purple
12
+ '#ff9671', // coral
13
+ '#00c9a7' // teal
14
+ ];
@@ -0,0 +1,4 @@
1
+ export { StreamerLayer } from './layer';
2
+ export { StreamerSimulation } from './simulation';
3
+ export type { StreamerSimulationConfig } from './simulation';
4
+ export type { Streamer } from './types';
@@ -0,0 +1,211 @@
1
+ import { SimulationLayer } from '../layer';
2
+ import { MULBERRY, STREAMER_COLORS } from './consts';
3
+ import type { StreamerSimulationConfig } from './simulation';
4
+ import type { Streamer } from './types';
5
+
6
+ export class StreamerLayer extends SimulationLayer {
7
+ readonly #colors: string[];
8
+ readonly #scale: number;
9
+ readonly #speed: number;
10
+ #count: number;
11
+ #streamers: Streamer[] = [];
12
+ #width: number = 960;
13
+ #height: number = 540;
14
+ #initialized: boolean = false;
15
+
16
+ constructor(config: StreamerSimulationConfig = {}) {
17
+ super();
18
+
19
+ this.#colors = config.colors ?? STREAMER_COLORS;
20
+ this.#scale = config.scale ?? 1;
21
+ this.#speed = config.speed ?? 1;
22
+ this.#count = config.count ?? 20;
23
+
24
+ if (innerWidth < 991) {
25
+ this.#count = Math.floor(this.#count / 2);
26
+ }
27
+ }
28
+
29
+ onResize(width: number, height: number): void {
30
+ this.#width = width;
31
+ this.#height = height;
32
+
33
+ if (!this.#initialized) {
34
+ this.#initialized = true;
35
+ this.#streamers = [];
36
+
37
+ for (let i = 0; i < this.#count; i++) {
38
+ this.#streamers.push(this.#createStreamer(true));
39
+ }
40
+ }
41
+ }
42
+
43
+ tick(dt: number, width: number, height: number): void {
44
+ this.#width = width;
45
+ this.#height = height;
46
+
47
+ let alive = 0;
48
+
49
+ for (let i = 0; i < this.#streamers.length; i++) {
50
+ const streamer = this.#streamers[i];
51
+
52
+ streamer.y += streamer.fallSpeed * this.#speed * dt;
53
+ streamer.swayPhase += streamer.swaySpeed * dt;
54
+
55
+ const swayOffset = Math.sin(streamer.swayPhase) * streamer.swayAmplitude;
56
+ streamer.x += swayOffset * dt * 0.3;
57
+
58
+ this.#updateSegments(streamer, dt);
59
+
60
+ const tail = streamer.segments[streamer.segments.length - 1];
61
+ const tailY = tail ? tail.y : streamer.y;
62
+
63
+ if (tailY > height + 50) {
64
+ this.#streamers[alive++] = this.#createStreamer(false);
65
+ } else {
66
+ this.#streamers[alive++] = streamer;
67
+ }
68
+ }
69
+
70
+ this.#streamers.length = alive;
71
+
72
+ while (this.#streamers.length < this.#count) {
73
+ this.#streamers.push(this.#createStreamer(false));
74
+ }
75
+ }
76
+
77
+ draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
78
+
79
+ for (const streamer of this.#streamers) {
80
+ this.#drawStreamer(ctx, streamer);
81
+ }
82
+ }
83
+
84
+ #createStreamer(initialSpread: boolean): Streamer {
85
+ const scale = this.#scale;
86
+ const segmentCount = 12 + Math.floor(MULBERRY.next() * 8);
87
+ const length = (80 + MULBERRY.next() * 120) * scale;
88
+ const width = (3 + MULBERRY.next() * 5) * scale;
89
+ const startX = MULBERRY.next() * this.#width;
90
+ const startY = initialSpread
91
+ ? MULBERRY.next() * this.#height
92
+ : -(MULBERRY.next() * 100 + length);
93
+ const depth = 0.4 + MULBERRY.next() * 0.6;
94
+ const curl = (0.3 + MULBERRY.next() * 0.7) * scale;
95
+ const fallSpeed = (1.5 + MULBERRY.next() * 2.5) * depth * scale;
96
+ const swayAmplitude = (0.3 + MULBERRY.next() * 0.6) * scale;
97
+ const swaySpeed = 0.02 + MULBERRY.next() * 0.04;
98
+ const swayPhase = MULBERRY.next() * Math.PI * 2;
99
+ const color = this.#colors[Math.floor(MULBERRY.next() * this.#colors.length)];
100
+
101
+ const segments: {x: number; y: number}[] = [];
102
+ const segmentLength = length / segmentCount;
103
+
104
+ for (let i = 0; i < segmentCount; i++) {
105
+ segments.push({
106
+ x: startX + Math.sin(i * curl * 0.5) * curl * 8,
107
+ y: startY - i * segmentLength
108
+ });
109
+ }
110
+
111
+ return {
112
+ x: startX,
113
+ y: startY,
114
+ length,
115
+ width,
116
+ segments,
117
+ fallSpeed,
118
+ swayPhase,
119
+ swaySpeed,
120
+ swayAmplitude,
121
+ color,
122
+ curl,
123
+ depth
124
+ };
125
+ }
126
+
127
+ #updateSegments(streamer: Streamer, dt: number): void {
128
+ const segments = streamer.segments;
129
+
130
+ if (segments.length === 0) {
131
+ return;
132
+ }
133
+
134
+ segments[0].x = streamer.x;
135
+ segments[0].y = streamer.y;
136
+
137
+ const segmentLength = streamer.length / segments.length;
138
+
139
+ for (let i = 1; i < segments.length; i++) {
140
+ const prev = segments[i - 1];
141
+ const curr = segments[i];
142
+
143
+ const curlOffset = Math.sin(streamer.swayPhase + i * streamer.curl * 0.8) * streamer.curl * 6;
144
+
145
+ const targetX = prev.x + curlOffset;
146
+ const targetY = prev.y - segmentLength;
147
+
148
+ const follow = 0.08 * dt;
149
+ curr.x += (targetX - curr.x) * follow;
150
+ curr.y += (targetY - curr.y) * follow;
151
+
152
+ const dx = curr.x - prev.x;
153
+ const dy = curr.y - prev.y;
154
+ const dist = Math.sqrt(dx * dx + dy * dy);
155
+
156
+ if (dist > segmentLength * 1.5) {
157
+ const nx = dx / dist;
158
+ const ny = dy / dist;
159
+ curr.x = prev.x + nx * segmentLength * 1.5;
160
+ curr.y = prev.y + ny * segmentLength * 1.5;
161
+ }
162
+ }
163
+ }
164
+
165
+ #drawStreamer(ctx: CanvasRenderingContext2D, streamer: Streamer): void {
166
+ const segments = streamer.segments;
167
+
168
+ if (segments.length < 2) {
169
+ return;
170
+ }
171
+
172
+ const alpha = 0.6 + streamer.depth * 0.4;
173
+ const maxWidth = streamer.width * streamer.depth;
174
+ const last = segments[segments.length - 1];
175
+
176
+ ctx.lineCap = 'round';
177
+ ctx.lineJoin = 'round';
178
+
179
+ ctx.beginPath();
180
+ ctx.moveTo(segments[0].x, segments[0].y);
181
+
182
+ for (let i = 1; i < segments.length - 1; i++) {
183
+ const curr = segments[i];
184
+ const next = segments[i + 1];
185
+ const midX = (curr.x + next.x) / 2;
186
+ const midY = (curr.y + next.y) / 2;
187
+
188
+ ctx.quadraticCurveTo(curr.x, curr.y, midX, midY);
189
+ }
190
+
191
+ ctx.lineTo(last.x, last.y);
192
+
193
+ const gradient = ctx.createLinearGradient(
194
+ segments[0].x, segments[0].y,
195
+ last.x, last.y
196
+ );
197
+ gradient.addColorStop(0, this.#adjustAlpha(streamer.color, alpha * 0.3));
198
+ gradient.addColorStop(0.3, this.#adjustAlpha(streamer.color, alpha));
199
+ gradient.addColorStop(0.7, this.#adjustAlpha(streamer.color, alpha));
200
+ gradient.addColorStop(1, this.#adjustAlpha(streamer.color, alpha * 0.1));
201
+
202
+ ctx.strokeStyle = gradient;
203
+ ctx.lineWidth = maxWidth;
204
+ ctx.stroke();
205
+ }
206
+
207
+ #adjustAlpha(color: string, alpha: number): string {
208
+ const clampedAlpha = Math.max(0, Math.min(1, alpha));
209
+ return color + Math.round(clampedAlpha * 255).toString(16).padStart(2, '0');
210
+ }
211
+ }
@@ -0,0 +1,16 @@
1
+ import { SimulationCanvas } from '../simulation-canvas';
2
+ import { StreamerLayer } from './layer';
3
+
4
+ export interface StreamerSimulationConfig {
5
+ readonly count?: number;
6
+ readonly colors?: string[];
7
+ readonly speed?: number;
8
+ readonly scale?: number;
9
+ readonly canvasOptions?: CanvasRenderingContext2DSettings;
10
+ }
11
+
12
+ export class StreamerSimulation extends SimulationCanvas {
13
+ constructor(canvas: HTMLCanvasElement, config: StreamerSimulationConfig = {}) {
14
+ super(canvas, new StreamerLayer(config), 60, config.canvasOptions ?? {colorSpace: 'display-p3'});
15
+ }
16
+ }
@@ -0,0 +1,14 @@
1
+ export type Streamer = {
2
+ x: number;
3
+ y: number;
4
+ length: number;
5
+ width: number;
6
+ segments: {x: number; y: number}[];
7
+ fallSpeed: number;
8
+ swayPhase: number;
9
+ swaySpeed: number;
10
+ swayAmplitude: number;
11
+ color: string;
12
+ curl: number;
13
+ depth: number;
14
+ };
package/src/trail.ts ADDED
@@ -0,0 +1,140 @@
1
+ import { distance } from './distance';
2
+ import { Spark } from './fireworks/spark';
3
+ import type { Point } from './point';
4
+
5
+ export interface TrailConfig {
6
+ readonly acceleration?: number;
7
+ readonly brightness?: number;
8
+ readonly glow?: number;
9
+ readonly hue?: number;
10
+ readonly length?: number;
11
+ readonly speed?: number;
12
+ readonly width?: number;
13
+ }
14
+
15
+ export class Trail {
16
+ readonly #startPosition: Point;
17
+ readonly #position: Point;
18
+ readonly #angle: number;
19
+ readonly #totalDistance: number;
20
+ readonly #trail: Point[];
21
+ readonly #acceleration: number;
22
+ readonly #brightness: number;
23
+ readonly #glow: number;
24
+ readonly #hue: number;
25
+ readonly #width: number;
26
+ #speed: number;
27
+ #distanceTraveled: number = 0;
28
+ #isDone: boolean = false;
29
+ #sparkTimer: number = 0;
30
+ #pendingSparks: Spark[] = [];
31
+
32
+ get hue(): number {
33
+ return this.#hue;
34
+ }
35
+
36
+ get isDone(): boolean {
37
+ return this.#isDone;
38
+ }
39
+
40
+ get position(): Point {
41
+ return {...this.#position};
42
+ }
43
+
44
+ collectSparks(): Spark[] {
45
+ const sparks = this.#pendingSparks;
46
+ this.#pendingSparks = [];
47
+ return sparks;
48
+ }
49
+
50
+ constructor(start: Point, end: Point, config: TrailConfig = {}) {
51
+ this.#startPosition = {...start};
52
+ this.#position = {...start};
53
+ this.#angle = Math.atan2(end.y - start.y, end.x - start.x);
54
+ this.#totalDistance = distance(start, end);
55
+ this.#acceleration = config.acceleration ?? 1.05;
56
+ this.#brightness = config.brightness ?? 65;
57
+ this.#glow = config.glow ?? 10;
58
+ this.#hue = config.hue ?? Math.random() * 360;
59
+ this.#speed = config.speed ?? 1;
60
+ this.#width = config.width ?? 2;
61
+
62
+ const length = config.length ?? 6;
63
+ this.#trail = Array.from({length}, () => ({...start}));
64
+ }
65
+
66
+ draw(ctx: CanvasRenderingContext2D): void {
67
+ if (this.#isDone) {
68
+ return;
69
+ }
70
+
71
+ ctx.save();
72
+ ctx.lineCap = 'round';
73
+
74
+ for (let i = this.#trail.length - 1; i > 0; i--) {
75
+ const progress = i / this.#trail.length;
76
+ const alpha = (1 - progress) * 0.8;
77
+ const width = this.#width * (1 - progress * 0.5);
78
+
79
+ ctx.beginPath();
80
+ ctx.moveTo(this.#trail[i].x, this.#trail[i].y);
81
+ ctx.lineTo(this.#trail[i - 1].x, this.#trail[i - 1].y);
82
+ ctx.lineWidth = width;
83
+ ctx.strokeStyle = `hsla(${this.#hue}, 100%, ${this.#brightness}%, ${alpha})`;
84
+ ctx.stroke();
85
+ }
86
+
87
+ ctx.shadowBlur = this.#glow;
88
+ ctx.shadowColor = `hsl(${this.#hue}, 100%, 60%)`;
89
+
90
+ ctx.beginPath();
91
+ ctx.moveTo(this.#trail[0].x, this.#trail[0].y);
92
+ ctx.lineTo(this.#position.x, this.#position.y);
93
+ ctx.lineWidth = this.#width;
94
+ ctx.strokeStyle = `hsl(${this.#hue}, 100%, ${this.#brightness}%)`;
95
+ ctx.stroke();
96
+
97
+ ctx.shadowBlur = this.#glow * 1.5;
98
+ ctx.shadowColor = `hsl(${this.#hue}, 80%, 80%)`;
99
+ ctx.beginPath();
100
+ ctx.arc(this.#position.x, this.#position.y, this.#width * 0.6, 0, Math.PI * 2);
101
+ ctx.fillStyle = `hsl(${this.#hue}, 20%, 92%)`;
102
+ ctx.fill();
103
+
104
+ ctx.restore();
105
+ }
106
+
107
+ tick(dt: number = 1): void {
108
+ if (this.#isDone) {
109
+ return;
110
+ }
111
+
112
+ this.#trail.pop();
113
+ this.#trail.unshift({...this.#position});
114
+
115
+ this.#speed *= Math.pow(this.#acceleration, dt);
116
+
117
+ const vx = Math.cos(this.#angle) * this.#speed;
118
+ const vy = Math.sin(this.#angle) * this.#speed;
119
+
120
+ this.#distanceTraveled = distance(this.#startPosition, {
121
+ x: this.#position.x + vx * dt,
122
+ y: this.#position.y + vy * dt
123
+ });
124
+
125
+ if (this.#distanceTraveled >= this.#totalDistance) {
126
+ this.#isDone = true;
127
+ return;
128
+ }
129
+
130
+ this.#position.x += vx * dt;
131
+ this.#position.y += vy * dt;
132
+
133
+ this.#sparkTimer += dt;
134
+
135
+ if (this.#sparkTimer >= 3) {
136
+ this.#sparkTimer -= 3;
137
+ this.#pendingSparks.push(new Spark(this.#position, this.#hue, -vx * 0.1, -vy * 0.1));
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,3 @@
1
+ import { type Mulberry32, mulberry32 } from '@basmilius/utils';
2
+
3
+ export const MULBERRY: Mulberry32 = mulberry32(13);
@@ -0,0 +1,4 @@
1
+ export { WaveLayer } from './layer';
2
+ export { WaveSimulation } from './simulation';
3
+ export type { WaveSimulationConfig } from './simulation';
4
+ export type { Wave } from './types';