@sarthak03dot/romantic-animations 1.2.0 → 1.2.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.
@@ -1,85 +1,85 @@
1
- import { mergeOptions } from '../core/engine.js';
2
-
3
- const DEFAULTS = {
4
- minSize: 6,
5
- maxSize: 16,
6
- decay: 0.025,
7
- colors: ['#ff6b8a', '#ff4d6d', '#ff85a1', '#ffc2d1', '#c9184a'],
8
- glow: true,
9
- };
10
-
11
- function drawHeart(ctx, cx, cy, r, color, alpha, glow) {
12
- ctx.save();
13
- ctx.globalAlpha = Math.max(0, alpha);
14
- if (glow) {
15
- ctx.shadowColor = color;
16
- ctx.shadowBlur = r * 2;
17
- }
18
- ctx.fillStyle = color;
19
- ctx.beginPath();
20
- ctx.moveTo(cx, cy + r * 0.3);
21
- ctx.bezierCurveTo(cx - r * 1.1, cy - r * 0.5, cx - r * 1.6, cy + r * 0.5, cx, cy + r * 1.4);
22
- ctx.bezierCurveTo(cx + r * 1.6, cy + r * 0.5, cx + r * 1.1, cy - r * 0.5, cx, cy + r * 0.3);
23
- ctx.fill();
24
- ctx.restore();
25
- }
26
-
27
- export function heartTrail(canvas, userOptions = {}) {
28
- const opts = mergeOptions(DEFAULTS, userOptions);
29
- const ctx = canvas.getContext('2d');
30
- const hearts = [];
31
- let running = true;
32
-
33
- function addHeart(x, y) {
34
- hearts.push({
35
- x,
36
- y,
37
- size: opts.minSize + Math.random() * (opts.maxSize - opts.minSize),
38
- alpha: 0.9 + Math.random() * 0.1,
39
- decay: opts.decay * (0.8 + Math.random() * 0.4),
40
- color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
41
- vy: -(0.3 + Math.random() * 0.6), // drift upward
42
- });
43
- }
44
-
45
- // Mouse support
46
- const onMouseMove = (e) => {
47
- const rect = canvas.getBoundingClientRect();
48
- addHeart(e.clientX - rect.left, e.clientY - rect.top);
49
- };
50
-
51
- // Touch support
52
- const onTouchMove = (e) => {
53
- const rect = canvas.getBoundingClientRect();
54
- Array.from(e.touches).forEach((t) => {
55
- addHeart(t.clientX - rect.left, t.clientY - rect.top);
56
- });
57
- };
58
-
59
- window.addEventListener('mousemove', onMouseMove);
60
- window.addEventListener('touchmove', onTouchMove, { passive: true });
61
-
62
- function animate() {
63
- if (!running) return;
64
- ctx.clearRect(0, 0, canvas.width, canvas.height);
65
-
66
- for (let i = hearts.length - 1; i >= 0; i--) {
67
- const h = hearts[i];
68
- h.y += h.vy;
69
- drawHeart(ctx, h.x, h.y, h.size, h.color, h.alpha, opts.glow);
70
- h.alpha -= h.decay;
71
- if (h.alpha <= 0) hearts.splice(i, 1);
72
- }
73
-
74
- requestAnimationFrame(animate);
75
- }
76
-
77
- animate();
78
-
79
- return function stop() {
80
- running = false;
81
- window.removeEventListener('mousemove', onMouseMove);
82
- window.removeEventListener('touchmove', onTouchMove);
83
- ctx.clearRect(0, 0, canvas.width, canvas.height);
84
- };
85
- }
1
+ import { mergeOptions } from '../core/engine.js';
2
+
3
+ const DEFAULTS = {
4
+ minSize: 6,
5
+ maxSize: 16,
6
+ decay: 0.025,
7
+ colors: ['#ff6b8a', '#ff4d6d', '#ff85a1', '#ffc2d1', '#c9184a'],
8
+ glow: true,
9
+ };
10
+
11
+ function drawHeart(ctx, cx, cy, r, color, alpha, glow) {
12
+ ctx.save();
13
+ ctx.globalAlpha = Math.max(0, alpha);
14
+ if (glow) {
15
+ ctx.shadowColor = color;
16
+ ctx.shadowBlur = r * 2;
17
+ }
18
+ ctx.fillStyle = color;
19
+ ctx.beginPath();
20
+ ctx.moveTo(cx, cy + r * 0.3);
21
+ ctx.bezierCurveTo(cx - r * 1.1, cy - r * 0.5, cx - r * 1.6, cy + r * 0.5, cx, cy + r * 1.4);
22
+ ctx.bezierCurveTo(cx + r * 1.6, cy + r * 0.5, cx + r * 1.1, cy - r * 0.5, cx, cy + r * 0.3);
23
+ ctx.fill();
24
+ ctx.restore();
25
+ }
26
+
27
+ export function heartTrail(canvas, userOptions = {}) {
28
+ const opts = mergeOptions(DEFAULTS, userOptions);
29
+ const ctx = canvas.getContext('2d');
30
+ const hearts = [];
31
+ let running = true;
32
+
33
+ function addHeart(x, y) {
34
+ hearts.push({
35
+ x,
36
+ y,
37
+ size: opts.minSize + Math.random() * (opts.maxSize - opts.minSize),
38
+ alpha: 0.9 + Math.random() * 0.1,
39
+ decay: opts.decay * (0.8 + Math.random() * 0.4),
40
+ color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
41
+ vy: -(0.3 + Math.random() * 0.6), // drift upward
42
+ });
43
+ }
44
+
45
+ // Mouse support
46
+ const onMouseMove = (e) => {
47
+ const rect = canvas.getBoundingClientRect();
48
+ addHeart(e.clientX - rect.left, e.clientY - rect.top);
49
+ };
50
+
51
+ // Touch support
52
+ const onTouchMove = (e) => {
53
+ const rect = canvas.getBoundingClientRect();
54
+ Array.from(e.touches).forEach((t) => {
55
+ addHeart(t.clientX - rect.left, t.clientY - rect.top);
56
+ });
57
+ };
58
+
59
+ window.addEventListener('mousemove', onMouseMove);
60
+ window.addEventListener('touchmove', onTouchMove, { passive: true });
61
+
62
+ function animate() {
63
+ if (!running) return;
64
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
65
+
66
+ for (let i = hearts.length - 1; i >= 0; i--) {
67
+ const h = hearts[i];
68
+ h.y += h.vy;
69
+ drawHeart(ctx, h.x, h.y, h.size, h.color, h.alpha, opts.glow);
70
+ h.alpha -= h.decay;
71
+ if (h.alpha <= 0) hearts.splice(i, 1);
72
+ }
73
+
74
+ requestAnimationFrame(animate);
75
+ }
76
+
77
+ animate();
78
+
79
+ return function stop() {
80
+ running = false;
81
+ window.removeEventListener('mousemove', onMouseMove);
82
+ window.removeEventListener('touchmove', onTouchMove);
83
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
84
+ };
85
+ }
@@ -1,71 +1,71 @@
1
- import { mergeOptions } from '../core/engine.js';
2
-
3
- const DEFAULTS = {
4
- density: 0.15, // probability of a new drop per frame
5
- symbols: ['❤', '💕', '✨', '💖', '💗', '⭐', '×'],
6
- minSize: 12,
7
- maxSize: 28,
8
- minSpeed: 1,
9
- maxSpeed: 3.5,
10
- colors: ['#ff6b8a', '#ff4d6d', '#ffc2d1', '#ff85a1', '#ff0a54', '#a2d2ff'],
11
- opacity: 0.85,
12
- glow: true,
13
- };
14
-
15
- export function loveRain(canvas, userOptions = {}) {
16
- const opts = mergeOptions(DEFAULTS, userOptions);
17
- const ctx = canvas.getContext('2d');
18
- const drops = [];
19
- let running = true;
20
-
21
- function createDrop() {
22
- const size = opts.minSize + Math.random() * (opts.maxSize - opts.minSize);
23
- return {
24
- x: Math.random() * canvas.width,
25
- y: -size * 2,
26
- size,
27
- speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
28
- symbol: opts.symbols[Math.floor(Math.random() * opts.symbols.length)],
29
- color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
30
- alpha: 0.4 + Math.random() * 0.6,
31
- angle: (Math.random() - 0.5) * 0.4, // slight tilt
32
- wobble: Math.random() * Math.PI * 2,
33
- wobbleSpeed: 0.02 + Math.random() * 0.02,
34
- };
35
- }
36
-
37
- function animate() {
38
- if (!running) return;
39
- ctx.clearRect(0, 0, canvas.width, canvas.height);
40
-
41
- if (Math.random() < opts.density) drops.push(createDrop());
42
-
43
- for (let i = drops.length - 1; i >= 0; i--) {
44
- const d = drops[i];
45
- d.y += d.speed;
46
- d.wobble += d.wobbleSpeed;
47
- const xOff = Math.sin(d.wobble) * d.size * 0.3;
48
-
49
- ctx.save();
50
- ctx.globalAlpha = d.alpha * opts.opacity;
51
- ctx.font = `${d.size}px serif`;
52
- ctx.fillStyle = d.color;
53
- if (opts.glow) { ctx.shadowColor = d.color; ctx.shadowBlur = d.size * 0.8; }
54
- ctx.translate(d.x + xOff, d.y);
55
- ctx.rotate(d.angle);
56
- ctx.fillText(d.symbol, 0, 0);
57
- ctx.restore();
58
-
59
- if (d.y > canvas.height + d.size * 2) drops.splice(i, 1);
60
- }
61
-
62
- requestAnimationFrame(animate);
63
- }
64
-
65
- animate();
66
-
67
- return function stop() {
68
- running = false;
69
- ctx.clearRect(0, 0, canvas.width, canvas.height);
70
- };
71
- }
1
+ import { mergeOptions } from '../core/engine.js';
2
+
3
+ const DEFAULTS = {
4
+ density: 0.15, // probability of a new drop per frame
5
+ symbols: ['❤', '💕', '✨', '💖', '💗', '⭐', '×'],
6
+ minSize: 12,
7
+ maxSize: 28,
8
+ minSpeed: 1,
9
+ maxSpeed: 3.5,
10
+ colors: ['#ff6b8a', '#ff4d6d', '#ffc2d1', '#ff85a1', '#ff0a54', '#a2d2ff'],
11
+ opacity: 0.85,
12
+ glow: true,
13
+ };
14
+
15
+ export function loveRain(canvas, userOptions = {}) {
16
+ const opts = mergeOptions(DEFAULTS, userOptions);
17
+ const ctx = canvas.getContext('2d');
18
+ const drops = [];
19
+ let running = true;
20
+
21
+ function createDrop() {
22
+ const size = opts.minSize + Math.random() * (opts.maxSize - opts.minSize);
23
+ return {
24
+ x: Math.random() * canvas.width,
25
+ y: -size * 2,
26
+ size,
27
+ speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
28
+ symbol: opts.symbols[Math.floor(Math.random() * opts.symbols.length)],
29
+ color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
30
+ alpha: 0.4 + Math.random() * 0.6,
31
+ angle: (Math.random() - 0.5) * 0.4, // slight tilt
32
+ wobble: Math.random() * Math.PI * 2,
33
+ wobbleSpeed: 0.02 + Math.random() * 0.02,
34
+ };
35
+ }
36
+
37
+ function animate() {
38
+ if (!running) return;
39
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
40
+
41
+ if (Math.random() < opts.density) drops.push(createDrop());
42
+
43
+ for (let i = drops.length - 1; i >= 0; i--) {
44
+ const d = drops[i];
45
+ d.y += d.speed;
46
+ d.wobble += d.wobbleSpeed;
47
+ const xOff = Math.sin(d.wobble) * d.size * 0.3;
48
+
49
+ ctx.save();
50
+ ctx.globalAlpha = d.alpha * opts.opacity;
51
+ ctx.font = `${d.size}px serif`;
52
+ ctx.fillStyle = d.color;
53
+ if (opts.glow) { ctx.shadowColor = d.color; ctx.shadowBlur = d.size * 0.8; }
54
+ ctx.translate(d.x + xOff, d.y);
55
+ ctx.rotate(d.angle);
56
+ ctx.fillText(d.symbol, 0, 0);
57
+ ctx.restore();
58
+
59
+ if (d.y > canvas.height + d.size * 2) drops.splice(i, 1);
60
+ }
61
+
62
+ requestAnimationFrame(animate);
63
+ }
64
+
65
+ animate();
66
+
67
+ return function stop() {
68
+ running = false;
69
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
70
+ };
71
+ }
@@ -1,87 +1,87 @@
1
- import { mergeOptions } from '../core/engine.js';
2
-
3
- const DEFAULTS = {
4
- particleCount: 150,
5
- minSize: 1,
6
- maxSize: 4,
7
- colors: ['#ffd6ff', '#e7c6ff', '#c77dff', '#ffb3c1', '#ffffff'],
8
- speed: 0.8,
9
- glow: true,
10
- };
11
-
12
- export function magicDust(canvas, userOptions = {}) {
13
- const opts = mergeOptions(DEFAULTS, userOptions);
14
- const ctx = canvas.getContext('2d');
15
- const dusts = [];
16
- let running = true;
17
- let time = 0;
18
-
19
- function createDust() {
20
- return {
21
- x: Math.random() * canvas.width,
22
- y: Math.random() * canvas.height,
23
- size: opts.minSize + Math.random() * (opts.maxSize - opts.minSize),
24
- color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
25
- angle: Math.random() * Math.PI * 2,
26
- orbitRadius: 20 + Math.random() * 80,
27
- orbitSpeed: (0.01 + Math.random() * 0.03) * (Math.random() > 0.5 ? 1 : -1),
28
- centerX: Math.random() * canvas.width,
29
- centerY: canvas.height + 50, // Start below and move up
30
- upwardSpeed: opts.speed + Math.random() * 1.5,
31
- alpha: 0,
32
- };
33
- }
34
-
35
- for (let i = 0; i < opts.particleCount; i++) {
36
- dusts.push(createDust());
37
- // Scatter initial positions so they aren't all at the bottom
38
- dusts[i].centerY = Math.random() * canvas.height;
39
- dusts[i].alpha = Math.random();
40
- }
41
-
42
- function animate() {
43
- if (!running) return;
44
- time++;
45
- ctx.clearRect(0, 0, canvas.width, canvas.height);
46
-
47
- for (let i = 0; i < dusts.length; i++) {
48
- const d = dusts[i];
49
- d.angle += d.orbitSpeed;
50
- d.centerY -= d.upwardSpeed;
51
-
52
- // Swirling calculation
53
- d.x = d.centerX + Math.cos(d.angle) * d.orbitRadius + Math.sin(time * 0.01 + d.angle) * 30;
54
- d.y = d.centerY + Math.sin(d.angle) * (d.orbitRadius * 0.5);
55
-
56
- if (d.alpha < 1 && d.centerY > 0) d.alpha += 0.01;
57
-
58
- // Reset when they reach top
59
- if (d.y < -50) {
60
- Object.assign(d, createDust());
61
- d.centerY = canvas.height + 50;
62
- d.centerX = Math.random() * canvas.width;
63
- }
64
-
65
- ctx.save();
66
- ctx.globalAlpha = d.alpha;
67
- if (opts.glow) {
68
- ctx.shadowColor = d.color;
69
- ctx.shadowBlur = d.size * 3;
70
- }
71
- ctx.fillStyle = d.color;
72
- ctx.beginPath();
73
- ctx.arc(d.x, d.y, d.size, 0, Math.PI * 2);
74
- ctx.fill();
75
- ctx.restore();
76
- }
77
-
78
- requestAnimationFrame(animate);
79
- }
80
-
81
- animate();
82
-
83
- return function stop() {
84
- running = false;
85
- ctx.clearRect(0, 0, canvas.width, canvas.height);
86
- };
87
- }
1
+ import { mergeOptions } from '../core/engine.js';
2
+
3
+ const DEFAULTS = {
4
+ particleCount: 150,
5
+ minSize: 1,
6
+ maxSize: 4,
7
+ colors: ['#ffd6ff', '#e7c6ff', '#c77dff', '#ffb3c1', '#ffffff'],
8
+ speed: 0.8,
9
+ glow: true,
10
+ };
11
+
12
+ export function magicDust(canvas, userOptions = {}) {
13
+ const opts = mergeOptions(DEFAULTS, userOptions);
14
+ const ctx = canvas.getContext('2d');
15
+ const dusts = [];
16
+ let running = true;
17
+ let time = 0;
18
+
19
+ function createDust() {
20
+ return {
21
+ x: Math.random() * canvas.width,
22
+ y: Math.random() * canvas.height,
23
+ size: opts.minSize + Math.random() * (opts.maxSize - opts.minSize),
24
+ color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
25
+ angle: Math.random() * Math.PI * 2,
26
+ orbitRadius: 20 + Math.random() * 80,
27
+ orbitSpeed: (0.01 + Math.random() * 0.03) * (Math.random() > 0.5 ? 1 : -1),
28
+ centerX: Math.random() * canvas.width,
29
+ centerY: canvas.height + 50, // Start below and move up
30
+ upwardSpeed: opts.speed + Math.random() * 1.5,
31
+ alpha: 0,
32
+ };
33
+ }
34
+
35
+ for (let i = 0; i < opts.particleCount; i++) {
36
+ dusts.push(createDust());
37
+ // Scatter initial positions so they aren't all at the bottom
38
+ dusts[i].centerY = Math.random() * canvas.height;
39
+ dusts[i].alpha = Math.random();
40
+ }
41
+
42
+ function animate() {
43
+ if (!running) return;
44
+ time++;
45
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
46
+
47
+ for (let i = 0; i < dusts.length; i++) {
48
+ const d = dusts[i];
49
+ d.angle += d.orbitSpeed;
50
+ d.centerY -= d.upwardSpeed;
51
+
52
+ // Swirling calculation
53
+ d.x = d.centerX + Math.cos(d.angle) * d.orbitRadius + Math.sin(time * 0.01 + d.angle) * 30;
54
+ d.y = d.centerY + Math.sin(d.angle) * (d.orbitRadius * 0.5);
55
+
56
+ if (d.alpha < 1 && d.centerY > 0) d.alpha += 0.01;
57
+
58
+ // Reset when they reach top
59
+ if (d.y < -50) {
60
+ Object.assign(d, createDust());
61
+ d.centerY = canvas.height + 50;
62
+ d.centerX = Math.random() * canvas.width;
63
+ }
64
+
65
+ ctx.save();
66
+ ctx.globalAlpha = d.alpha;
67
+ if (opts.glow) {
68
+ ctx.shadowColor = d.color;
69
+ ctx.shadowBlur = d.size * 3;
70
+ }
71
+ ctx.fillStyle = d.color;
72
+ ctx.beginPath();
73
+ ctx.arc(d.x, d.y, d.size, 0, Math.PI * 2);
74
+ ctx.fill();
75
+ ctx.restore();
76
+ }
77
+
78
+ requestAnimationFrame(animate);
79
+ }
80
+
81
+ animate();
82
+
83
+ return function stop() {
84
+ running = false;
85
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
86
+ };
87
+ }
@@ -1,82 +1,82 @@
1
- import { mergeOptions } from '../core/engine.js';
2
-
3
- const DEFAULTS = {
4
- density: 0.02,
5
- minSpeed: 10,
6
- maxSpeed: 25,
7
- colors: ['#ffffff', '#e7c6ff', '#48cae4', '#ffe66d'],
8
- glow: true,
9
- };
10
-
11
- export function shootingStars(canvas, userOptions = {}) {
12
- const opts = mergeOptions(DEFAULTS, userOptions);
13
- const ctx = canvas.getContext('2d');
14
- const stars = [];
15
- let running = true;
16
-
17
- function createStar() {
18
- return {
19
- x: Math.random() * canvas.width * 1.5,
20
- y: -50,
21
- length: 50 + Math.random() * 100,
22
- thickness: 1 + Math.random() * 2,
23
- speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
24
- color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
25
- angle: (Math.PI / 4) + (Math.random() * 0.2 - 0.1), // Roughly 45 degrees
26
- opacity: 1,
27
- };
28
- }
29
-
30
- function animate() {
31
- if (!running) return;
32
- ctx.clearRect(0, 0, canvas.width, canvas.height);
33
-
34
- if (Math.random() < opts.density) {
35
- stars.push(createStar());
36
- }
37
-
38
- for (let i = stars.length - 1; i >= 0; i--) {
39
- const s = stars[i];
40
- const vx = -Math.cos(s.angle) * s.speed;
41
- const vy = Math.sin(s.angle) * s.speed;
42
-
43
- s.x += vx;
44
- s.y += vy;
45
- s.opacity -= 0.01;
46
-
47
- ctx.save();
48
- ctx.globalAlpha = Math.max(0, s.opacity);
49
- if (opts.glow) {
50
- ctx.shadowColor = s.color;
51
- ctx.shadowBlur = s.thickness * 4;
52
- }
53
-
54
- const grad = ctx.createLinearGradient(s.x, s.y, s.x - vx * (s.length / s.speed), s.y - vy * (s.length / s.speed));
55
- grad.addColorStop(0, s.color);
56
- grad.addColorStop(1, 'rgba(255,255,255,0)');
57
-
58
- ctx.strokeStyle = grad;
59
- ctx.lineWidth = s.thickness;
60
- ctx.lineCap = 'round';
61
-
62
- ctx.beginPath();
63
- ctx.moveTo(s.x, s.y);
64
- ctx.lineTo(s.x - vx * (s.length / s.speed), s.y - vy * (s.length / s.speed));
65
- ctx.stroke();
66
- ctx.restore();
67
-
68
- if (s.opacity <= 0 || s.x < -100 || s.y > canvas.height + 100) {
69
- stars.splice(i, 1);
70
- }
71
- }
72
-
73
- requestAnimationFrame(animate);
74
- }
75
-
76
- animate();
77
-
78
- return function stop() {
79
- running = false;
80
- ctx.clearRect(0, 0, canvas.width, canvas.height);
81
- };
82
- }
1
+ import { mergeOptions } from '../core/engine.js';
2
+
3
+ const DEFAULTS = {
4
+ density: 0.02,
5
+ minSpeed: 10,
6
+ maxSpeed: 25,
7
+ colors: ['#ffffff', '#e7c6ff', '#48cae4', '#ffe66d'],
8
+ glow: true,
9
+ };
10
+
11
+ export function shootingStars(canvas, userOptions = {}) {
12
+ const opts = mergeOptions(DEFAULTS, userOptions);
13
+ const ctx = canvas.getContext('2d');
14
+ const stars = [];
15
+ let running = true;
16
+
17
+ function createStar() {
18
+ return {
19
+ x: Math.random() * canvas.width * 1.5,
20
+ y: -50,
21
+ length: 50 + Math.random() * 100,
22
+ thickness: 1 + Math.random() * 2,
23
+ speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
24
+ color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
25
+ angle: (Math.PI / 4) + (Math.random() * 0.2 - 0.1), // Roughly 45 degrees
26
+ opacity: 1,
27
+ };
28
+ }
29
+
30
+ function animate() {
31
+ if (!running) return;
32
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
33
+
34
+ if (Math.random() < opts.density) {
35
+ stars.push(createStar());
36
+ }
37
+
38
+ for (let i = stars.length - 1; i >= 0; i--) {
39
+ const s = stars[i];
40
+ const vx = -Math.cos(s.angle) * s.speed;
41
+ const vy = Math.sin(s.angle) * s.speed;
42
+
43
+ s.x += vx;
44
+ s.y += vy;
45
+ s.opacity -= 0.01;
46
+
47
+ ctx.save();
48
+ ctx.globalAlpha = Math.max(0, s.opacity);
49
+ if (opts.glow) {
50
+ ctx.shadowColor = s.color;
51
+ ctx.shadowBlur = s.thickness * 4;
52
+ }
53
+
54
+ const grad = ctx.createLinearGradient(s.x, s.y, s.x - vx * (s.length / s.speed), s.y - vy * (s.length / s.speed));
55
+ grad.addColorStop(0, s.color);
56
+ grad.addColorStop(1, 'rgba(255,255,255,0)');
57
+
58
+ ctx.strokeStyle = grad;
59
+ ctx.lineWidth = s.thickness;
60
+ ctx.lineCap = 'round';
61
+
62
+ ctx.beginPath();
63
+ ctx.moveTo(s.x, s.y);
64
+ ctx.lineTo(s.x - vx * (s.length / s.speed), s.y - vy * (s.length / s.speed));
65
+ ctx.stroke();
66
+ ctx.restore();
67
+
68
+ if (s.opacity <= 0 || s.x < -100 || s.y > canvas.height + 100) {
69
+ stars.splice(i, 1);
70
+ }
71
+ }
72
+
73
+ requestAnimationFrame(animate);
74
+ }
75
+
76
+ animate();
77
+
78
+ return function stop() {
79
+ running = false;
80
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
81
+ };
82
+ }