asigalov61 commited on
Commit
5d364bc
·
verified ·
1 Parent(s): b780be5

Update stars.js

Browse files
Files changed (1) hide show
  1. stars.js +217 -66
stars.js CHANGED
@@ -7,13 +7,15 @@
7
  let layers = [];
8
  let comets = [];
9
  let pulses = [];
 
 
10
 
11
  // Layer configurations: count, base speed, draw size
12
  const layerConfigs = [
13
- { count: 400, speed: 1.5, size: 2.5 },
14
- { count: 300, speed: 3.0, size: 4.0 },
15
- { count: 200, speed: 6.0, size: 5.5 },
16
- { count: 100, speed: 10.0, size: 8.0 },
17
  ];
18
 
19
  const rand = (min, max) => min + Math.random() * (max - min);
@@ -25,7 +27,7 @@
25
  canvas.width = width;
26
  canvas.height = height;
27
 
28
- // Create background with nebula effect
29
  bgCanvas = document.createElement("canvas");
30
  bgCanvas.width = width;
31
  bgCanvas.height = height;
@@ -36,66 +38,104 @@
36
  width / 2, height / 2, 0,
37
  width / 2, height / 2, width * 0.8
38
  );
39
- bgGrad.addColorStop(0, "#111122");
 
40
  bgGrad.addColorStop(1, "#000011");
41
  bgCtx.fillStyle = bgGrad;
42
  bgCtx.fillRect(0, 0, width, height);
43
 
44
- // Nebula effect
45
- const nebulaCount = Math.floor(width * height / 40000);
46
- bgCtx.globalCompositeOperation = "lighter";
47
  for (let i = 0; i < nebulaCount; i++) {
48
  const x = rand(0, width);
49
  const y = rand(0, height);
50
- const radius = rand(50, 300);
51
  const hue = rand(220, 280);
52
- const alpha = rand(0.01, 0.03);
53
 
54
  const grd = bgCtx.createRadialGradient(
55
  x, y, 0,
56
  x, y, radius
57
  );
58
- grd.addColorStop(0, `hsla(${hue}, 70%, 60%, ${alpha})`);
59
- grd.addColorStop(1, `hsla(${hue}, 80%, 30%, 0)`);
 
60
 
61
  bgCtx.beginPath();
62
  bgCtx.fillStyle = grd;
63
  bgCtx.arc(x, y, radius, 0, Math.PI * 2);
64
  bgCtx.fill();
65
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  bgCtx.globalCompositeOperation = "source-over";
67
  }
68
 
69
- // Pre-render star sprites with glow
70
  function createStarSprites() {
71
  starSprites = layerConfigs.map(cfg => {
72
  const r = cfg.size;
73
- const sz = Math.ceil(r * 6);
74
  const off = document.createElement("canvas");
75
  off.width = off.height = sz;
76
  const octx = off.getContext("2d");
77
 
78
- // Core glow
79
- octx.fillStyle = "#fff";
80
- octx.shadowColor = "#fff";
81
- octx.shadowBlur = r * 4;
 
 
 
 
 
 
82
  octx.beginPath();
83
- octx.arc(sz/2, sz/2, r, 0, Math.PI * 2);
84
  octx.fill();
85
 
86
- // Bright core
87
- octx.shadowBlur = 0;
88
- octx.globalCompositeOperation = "source-over";
 
89
  octx.fillStyle = "#ffffff";
90
  octx.beginPath();
91
- octx.arc(sz/2, sz/2, r * 0.6, 0, Math.PI * 2);
 
 
 
 
 
 
 
 
92
  octx.fill();
93
 
94
  return off;
95
  });
96
  }
97
 
98
- // Initialize star layers with twinkle parameters
99
  function initStars() {
100
  layers = layerConfigs.map((cfg, idx) => {
101
  const stars = [];
@@ -105,106 +145,191 @@
105
  y: rand(-height/2, height/2),
106
  z: rand(1, width),
107
  twPhase: rand(0, Math.PI * 2),
108
- twFreq: rand(0.01, 0.03),
109
- twRange: rand(0.3, 0.7),
110
  sprIndex: idx,
 
 
 
 
 
 
111
  });
112
  }
113
  return { ...cfg, stars };
114
  });
115
  }
116
 
117
- // Spawn comets with natural motion
118
  function maybeComet() {
119
- if (Math.random() < 0.002) {
120
- const angle = Math.PI * 0.25 + rand(-0.3, 0.3);
121
  comets.push({
122
- x: rand(-50, width + 50),
123
- y: rand(-50, height * 0.3),
124
  len: 0,
125
- maxLen: rand(80, 200),
126
  angle: angle,
127
- speed: rand(10, 16),
128
  segments: [],
129
- alpha: 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  });
131
  }
132
  }
133
 
134
- // Click creates warp pulse with multiple rings
135
  canvas.addEventListener("click", e => {
136
- for (let i = 0; i < 3; i++) {
 
137
  pulses.push({
138
  x: e.clientX,
139
  y: e.clientY,
140
- r: i * 20,
141
- maxR: 150 + i * 80,
142
- thickness: 15 - i * 3,
143
- alpha: 0.7 - i * 0.2,
144
- speed: 8 - i * 2
 
145
  });
146
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  });
148
 
149
  // Update positions and effects
150
  function update() {
151
  const now = Date.now();
152
 
153
- // Update stars
154
  layers.forEach(layer => {
155
  layer.stars.forEach(s => {
156
  s.z -= layer.speed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  if (s.z <= 0) {
158
  s.z = width;
159
  s.x = rand(-width/2, width/2);
160
  s.y = rand(-height/2, height/2);
161
  }
162
- // Update twinkle
163
  s.twPhase += s.twFreq;
 
164
  });
165
  });
166
 
167
  // Update comets
168
- comets = comets.filter(c => c.alpha > 0.1);
169
  comets.forEach(c => {
170
  c.len += c.speed;
171
  const headX = c.x + Math.cos(c.angle) * c.len;
172
  const headY = c.y + Math.sin(c.angle) * c.len;
173
 
174
  // Add new segment
175
- c.segments.push({x: headX, y: headY, alpha: 1.0});
 
 
 
 
 
176
 
177
  // Fade old segments
178
- c.segments.forEach(seg => seg.alpha -= 0.02);
179
 
180
  // Remove old segments
181
- if (c.segments.length > 20) c.segments.shift();
182
 
183
  // Fade entire comet
184
- c.alpha -= 0.003;
 
 
 
 
 
185
  });
186
 
187
  // Update pulses
188
  pulses = pulses.filter(p => p.r < p.maxR);
189
  pulses.forEach(p => {
190
  p.r += p.speed;
191
- p.alpha -= 0.008;
 
 
 
 
 
 
 
 
 
 
 
192
  });
193
 
194
  maybeComet();
195
  }
196
 
197
- // Draw the scene
198
  function draw() {
199
- // Background
200
  ctx.clearRect(0, 0, width, height);
201
  ctx.drawImage(bgCanvas, 0, 0);
202
 
203
- // Motion trails
204
- ctx.fillStyle = "rgba(0,0,10,0.1)";
205
  ctx.fillRect(0, 0, width, height);
206
 
207
- // Stars
 
 
 
 
 
 
 
 
 
208
  layers.forEach((layer, li) => {
209
  const sprite = starSprites[li];
210
  ctx.globalCompositeOperation = "lighter";
@@ -215,10 +340,11 @@
215
 
216
  if (x < -100 || x > width+100 || y < -100 || y > height+100) return;
217
 
218
- // Calculate twinkle
219
  const twinkle = 0.5 + 0.5 * Math.sin(s.twPhase);
220
- const alpha = s.twRange + (1 - s.twRange) * twinkle;
221
- const ds = (1 - s.z/width) * layer.size * 1.5;
 
222
 
223
  ctx.globalAlpha = alpha;
224
  ctx.drawImage(sprite, x - ds, y - ds, ds * 2, ds * 2);
@@ -227,7 +353,7 @@
227
  ctx.globalCompositeOperation = "source-over";
228
  ctx.globalAlpha = 1;
229
 
230
- // Comets with particle trails
231
  comets.forEach(c => {
232
  ctx.lineCap = "round";
233
  for (let i = 1; i < c.segments.length; i++) {
@@ -238,11 +364,12 @@
238
  seg1.x, seg1.y, seg2.x, seg2.y
239
  );
240
  const segAlpha = Math.min(seg1.alpha, seg2.alpha) * c.alpha;
241
- gradient.addColorStop(0, `rgba(200,220,255,${segAlpha})`);
242
- gradient.addColorStop(1, `rgba(100,150,255,${segAlpha * 0.2})`);
 
243
 
244
  ctx.strokeStyle = gradient;
245
- ctx.lineWidth = Math.max(1, 3 * (i/c.segments.length));
246
  ctx.beginPath();
247
  ctx.moveTo(seg1.x, seg1.y);
248
  ctx.lineTo(seg2.x, seg2.y);
@@ -250,7 +377,7 @@
250
  }
251
  });
252
 
253
- // Warp pulses with multiple rings
254
  pulses.forEach(p => {
255
  ctx.beginPath();
256
  ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
@@ -258,13 +385,28 @@
258
  p.x, p.y, p.r - p.thickness/2,
259
  p.x, p.y, p.r + p.thickness/2
260
  );
261
- gradient.addColorStop(0, `rgba(180,220,255,${p.alpha})`);
262
- gradient.addColorStop(0.5, `rgba(100,180,255,${p.alpha * 0.8})`);
263
- gradient.addColorStop(1, `rgba(50,100,255,0)`);
 
264
 
265
  ctx.strokeStyle = gradient;
266
  ctx.lineWidth = p.thickness;
267
  ctx.stroke();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  });
269
  }
270
 
@@ -280,10 +422,19 @@
280
  resize();
281
  createStarSprites();
282
  initStars();
 
283
  window.addEventListener("resize", () => {
284
  resize();
285
  initStars();
286
  });
 
 
 
 
 
 
 
 
287
  loop();
288
  }
289
 
 
7
  let layers = [];
8
  let comets = [];
9
  let pulses = [];
10
+ let particles = [];
11
+ let mouse = { x: 0, y: 0, active: false };
12
 
13
  // Layer configurations: count, base speed, draw size
14
  const layerConfigs = [
15
+ { count: 500, speed: 1.5, size: 2.5 },
16
+ { count: 400, speed: 3.0, size: 4.0 },
17
+ { count: 300, speed: 6.0, size: 5.5 },
18
+ { count: 200, speed: 10.0, size: 8.0 },
19
  ];
20
 
21
  const rand = (min, max) => min + Math.random() * (max - min);
 
27
  canvas.width = width;
28
  canvas.height = height;
29
 
30
+ // Create background with enhanced nebula effect
31
  bgCanvas = document.createElement("canvas");
32
  bgCanvas.width = width;
33
  bgCanvas.height = height;
 
38
  width / 2, height / 2, 0,
39
  width / 2, height / 2, width * 0.8
40
  );
41
+ bgGrad.addColorStop(0, "#0a0a1a");
42
+ bgGrad.addColorStop(0.5, "#111122");
43
  bgGrad.addColorStop(1, "#000011");
44
  bgCtx.fillStyle = bgGrad;
45
  bgCtx.fillRect(0, 0, width, height);
46
 
47
+ // Enhanced nebula effect with multiple layers
48
+ const nebulaCount = Math.floor(width * height / 30000);
49
+ bgCtx.globalCompositeOperation = "screen";
50
  for (let i = 0; i < nebulaCount; i++) {
51
  const x = rand(0, width);
52
  const y = rand(0, height);
53
+ const radius = rand(100, 400);
54
  const hue = rand(220, 280);
55
+ const alpha = rand(0.02, 0.05);
56
 
57
  const grd = bgCtx.createRadialGradient(
58
  x, y, 0,
59
  x, y, radius
60
  );
61
+ grd.addColorStop(0, `hsla(${hue}, 80%, 70%, ${alpha})`);
62
+ grd.addColorStop(0.3, `hsla(${hue + 20}, 70%, 50%, ${alpha * 0.7})`);
63
+ grd.addColorStop(1, `hsla(${hue - 20}, 90%, 30%, 0)`);
64
 
65
  bgCtx.beginPath();
66
  bgCtx.fillStyle = grd;
67
  bgCtx.arc(x, y, radius, 0, Math.PI * 2);
68
  bgCtx.fill();
69
  }
70
+
71
+ // Add subtle star clusters
72
+ bgCtx.globalCompositeOperation = "lighter";
73
+ for (let i = 0; i < nebulaCount / 3; i++) {
74
+ const x = rand(0, width);
75
+ const y = rand(0, height);
76
+ const clusterSize = rand(20, 60);
77
+
78
+ const clusterGrad = bgCtx.createRadialGradient(
79
+ x, y, 0,
80
+ x, y, clusterSize
81
+ );
82
+ clusterGrad.addColorStop(0, `rgba(255, 255, 255, 0.1)`);
83
+ clusterGrad.addColorStop(1, `rgba(200, 220, 255, 0)`);
84
+
85
+ bgCtx.beginPath();
86
+ bgCtx.fillStyle = clusterGrad;
87
+ bgCtx.arc(x, y, clusterSize, 0, Math.PI * 2);
88
+ bgCtx.fill();
89
+ }
90
+
91
  bgCtx.globalCompositeOperation = "source-over";
92
  }
93
 
94
+ // Pre-render enhanced star sprites with glow
95
  function createStarSprites() {
96
  starSprites = layerConfigs.map(cfg => {
97
  const r = cfg.size;
98
+ const sz = Math.ceil(r * 8);
99
  const off = document.createElement("canvas");
100
  off.width = off.height = sz;
101
  const octx = off.getContext("2d");
102
 
103
+ // Core glow with gradient
104
+ const gradient = octx.createRadialGradient(
105
+ sz/2, sz/2, 0,
106
+ sz/2, sz/2, r * 3
107
+ );
108
+ gradient.addColorStop(0, "#ffffff");
109
+ gradient.addColorStop(0.3, "#a0c0ff");
110
+ gradient.addColorStop(1, "rgba(50, 100, 255, 0)");
111
+
112
+ octx.fillStyle = gradient;
113
  octx.beginPath();
114
+ octx.arc(sz/2, sz/2, r * 3, 0, Math.PI * 2);
115
  octx.fill();
116
 
117
+ // Bright core with corona
118
+ octx.shadowColor = "#88ccff";
119
+ octx.shadowBlur = r * 2;
120
+ octx.globalCompositeOperation = "lighter";
121
  octx.fillStyle = "#ffffff";
122
  octx.beginPath();
123
+ octx.arc(sz/2, sz/2, r * 0.8, 0, Math.PI * 2);
124
+ octx.fill();
125
+
126
+ // Secondary corona
127
+ octx.shadowColor = "#5599ff";
128
+ octx.shadowBlur = r * 4;
129
+ octx.fillStyle = "rgba(150, 200, 255, 0.7)";
130
+ octx.beginPath();
131
+ octx.arc(sz/2, sz/2, r * 0.5, 0, Math.PI * 2);
132
  octx.fill();
133
 
134
  return off;
135
  });
136
  }
137
 
138
+ // Initialize star layers with enhanced twinkle parameters
139
  function initStars() {
140
  layers = layerConfigs.map((cfg, idx) => {
141
  const stars = [];
 
145
  y: rand(-height/2, height/2),
146
  z: rand(1, width),
147
  twPhase: rand(0, Math.PI * 2),
148
+ twFreq: rand(0.01, 0.05),
149
+ twRange: rand(0.2, 0.8),
150
  sprIndex: idx,
151
+ hue: rand(180, 220), // Blue-ish stars
152
+ saturation: rand(70, 100),
153
+ brightness: rand(80, 100),
154
+ pulsePhase: rand(0, Math.PI * 2),
155
+ pulseSpeed: rand(0.02, 0.08),
156
+ originalSize: cfg.size
157
  });
158
  }
159
  return { ...cfg, stars };
160
  });
161
  }
162
 
163
+ // Spawn comets with enhanced effects
164
  function maybeComet() {
165
+ if (Math.random() < 0.003) {
166
+ const angle = Math.PI * 0.25 + rand(-0.5, 0.5);
167
  comets.push({
168
+ x: rand(-100, width + 100),
169
+ y: rand(-100, height * 0.4),
170
  len: 0,
171
+ maxLen: rand(100, 250),
172
  angle: angle,
173
+ speed: rand(12, 20),
174
  segments: [],
175
+ alpha: 1.0,
176
+ hue: rand(180, 220),
177
+ size: rand(2, 5)
178
+ });
179
+ }
180
+ }
181
+
182
+ // Create particle explosion
183
+ function createParticles(x, y, count, color) {
184
+ for (let i = 0; i < count; i++) {
185
+ particles.push({
186
+ x: x,
187
+ y: y,
188
+ vx: rand(-3, 3),
189
+ vy: rand(-3, 3),
190
+ life: 1.0,
191
+ decay: rand(0.01, 0.03),
192
+ size: rand(1, 3),
193
+ color: color || `hsl(${rand(180, 220)}, 100%, ${rand(70, 90)}%)`
194
  });
195
  }
196
  }
197
 
198
+ // Click creates enhanced warp pulse with particles
199
  canvas.addEventListener("click", e => {
200
+ // Create main pulses
201
+ for (let i = 0; i < 5; i++) {
202
  pulses.push({
203
  x: e.clientX,
204
  y: e.clientY,
205
+ r: i * 15,
206
+ maxR: 200 + i * 60,
207
+ thickness: 20 - i * 2,
208
+ alpha: 0.8 - i * 0.15,
209
+ speed: 10 - i * 1.5,
210
+ hue: rand(180, 220)
211
  });
212
  }
213
+
214
+ // Create particle explosion
215
+ createParticles(e.clientX, e.clientY, 50, `hsl(${rand(180, 220)}, 100%, 80%)`);
216
+ });
217
+
218
+ // Mouse move for star attraction
219
+ canvas.addEventListener("mousemove", e => {
220
+ mouse.x = e.clientX;
221
+ mouse.y = e.clientY;
222
+ mouse.active = true;
223
+ });
224
+
225
+ canvas.addEventListener("mouseleave", () => {
226
+ mouse.active = false;
227
  });
228
 
229
  // Update positions and effects
230
  function update() {
231
  const now = Date.now();
232
 
233
+ // Update stars with mouse attraction
234
  layers.forEach(layer => {
235
  layer.stars.forEach(s => {
236
  s.z -= layer.speed;
237
+
238
+ // Mouse attraction effect
239
+ if (mouse.active) {
240
+ const dx = (mouse.x - width/2) - s.x;
241
+ const dy = (mouse.y - height/2) - s.y;
242
+ const dist = Math.sqrt(dx*dx + dy*dy);
243
+
244
+ if (dist < 300) {
245
+ const force = (300 - dist) / 300 * 0.5;
246
+ s.x += dx * force * (s.z / width);
247
+ s.y += dy * force * (s.z / width);
248
+ }
249
+ }
250
+
251
  if (s.z <= 0) {
252
  s.z = width;
253
  s.x = rand(-width/2, width/2);
254
  s.y = rand(-height/2, height/2);
255
  }
256
+ // Update twinkle and pulsing
257
  s.twPhase += s.twFreq;
258
+ s.pulsePhase += s.pulseSpeed;
259
  });
260
  });
261
 
262
  // Update comets
263
+ comets = comets.filter(c => c.alpha > 0.05);
264
  comets.forEach(c => {
265
  c.len += c.speed;
266
  const headX = c.x + Math.cos(c.angle) * c.len;
267
  const headY = c.y + Math.sin(c.angle) * c.len;
268
 
269
  // Add new segment
270
+ c.segments.push({
271
+ x: headX,
272
+ y: headY,
273
+ alpha: 1.0,
274
+ size: c.size * (0.5 + 0.5 * Math.random())
275
+ });
276
 
277
  // Fade old segments
278
+ c.segments.forEach(seg => seg.alpha -= 0.015);
279
 
280
  // Remove old segments
281
+ if (c.segments.length > 30) c.segments.shift();
282
 
283
  // Fade entire comet
284
+ c.alpha -= 0.002;
285
+
286
+ // Occasionally create particles
287
+ if (Math.random() < 0.3) {
288
+ createParticles(headX, headY, 1, `hsl(${c.hue}, 100%, 70%)`);
289
+ }
290
  });
291
 
292
  // Update pulses
293
  pulses = pulses.filter(p => p.r < p.maxR);
294
  pulses.forEach(p => {
295
  p.r += p.speed;
296
+ p.alpha -= 0.006;
297
+ });
298
+
299
+ // Update particles
300
+ particles = particles.filter(p => p.life > 0);
301
+ particles.forEach(p => {
302
+ p.x += p.vx;
303
+ p.y += p.vy;
304
+ p.vy += 0.05; // Gravity
305
+ p.life -= p.decay;
306
+ p.vx *= 0.98; // Friction
307
+ p.vy *= 0.98;
308
  });
309
 
310
  maybeComet();
311
  }
312
 
313
+ // Draw the enhanced scene
314
  function draw() {
315
+ // Background with subtle movement
316
  ctx.clearRect(0, 0, width, height);
317
  ctx.drawImage(bgCanvas, 0, 0);
318
 
319
+ // Subtle motion blur effect
320
+ ctx.fillStyle = "rgba(5, 5, 15, 0.05)";
321
  ctx.fillRect(0, 0, width, height);
322
 
323
+ // Draw particles
324
+ particles.forEach(p => {
325
+ ctx.globalAlpha = p.life;
326
+ ctx.fillStyle = p.color;
327
+ ctx.beginPath();
328
+ ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
329
+ ctx.fill();
330
+ });
331
+
332
+ // Stars with enhanced effects
333
  layers.forEach((layer, li) => {
334
  const sprite = starSprites[li];
335
  ctx.globalCompositeOperation = "lighter";
 
340
 
341
  if (x < -100 || x > width+100 || y < -100 || y > height+100) return;
342
 
343
+ // Calculate twinkle with pulsing
344
  const twinkle = 0.5 + 0.5 * Math.sin(s.twPhase);
345
+ const pulse = 0.8 + 0.2 * Math.sin(s.pulsePhase);
346
+ const alpha = (s.twRange + (1 - s.twRange) * twinkle) * pulse;
347
+ const ds = (1 - s.z/width) * s.originalSize * 2 * pulse;
348
 
349
  ctx.globalAlpha = alpha;
350
  ctx.drawImage(sprite, x - ds, y - ds, ds * 2, ds * 2);
 
353
  ctx.globalCompositeOperation = "source-over";
354
  ctx.globalAlpha = 1;
355
 
356
+ // Comets with enhanced particle trails
357
  comets.forEach(c => {
358
  ctx.lineCap = "round";
359
  for (let i = 1; i < c.segments.length; i++) {
 
364
  seg1.x, seg1.y, seg2.x, seg2.y
365
  );
366
  const segAlpha = Math.min(seg1.alpha, seg2.alpha) * c.alpha;
367
+ gradient.addColorStop(0, `hsla(${c.hue}, 100%, 90%, ${segAlpha})`);
368
+ gradient.addColorStop(0.5, `hsla(${c.hue - 20}, 100%, 70%, ${segAlpha * 0.7})`);
369
+ gradient.addColorStop(1, `hsla(${c.hue - 40}, 100%, 50%, ${segAlpha * 0.2})`);
370
 
371
  ctx.strokeStyle = gradient;
372
+ ctx.lineWidth = Math.max(1, seg2.size * (i/c.segments.length) * c.alpha);
373
  ctx.beginPath();
374
  ctx.moveTo(seg1.x, seg1.y);
375
  ctx.lineTo(seg2.x, seg2.y);
 
377
  }
378
  });
379
 
380
+ // Warp pulses with enhanced rings
381
  pulses.forEach(p => {
382
  ctx.beginPath();
383
  ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
 
385
  p.x, p.y, p.r - p.thickness/2,
386
  p.x, p.y, p.r + p.thickness/2
387
  );
388
+ gradient.addColorStop(0, `hsla(${p.hue}, 100%, 90%, ${p.alpha})`);
389
+ gradient.addColorStop(0.3, `hsla(${p.hue - 10}, 100%, 70%, ${p.alpha * 0.8})`);
390
+ gradient.addColorStop(0.7, `hsla(${p.hue - 20}, 100%, 50%, ${p.alpha * 0.4})`);
391
+ gradient.addColorStop(1, `hsla(${p.hue - 30}, 100%, 30%, 0)`);
392
 
393
  ctx.strokeStyle = gradient;
394
  ctx.lineWidth = p.thickness;
395
  ctx.stroke();
396
+
397
+ // Add inner glow
398
+ if (p.alpha > 0.3) {
399
+ ctx.beginPath();
400
+ ctx.arc(p.x, p.y, p.r * 0.3, 0, Math.PI * 2);
401
+ const innerGradient = ctx.createRadialGradient(
402
+ p.x, p.y, 0,
403
+ p.x, p.y, p.r * 0.3
404
+ );
405
+ innerGradient.addColorStop(0, `hsla(${p.hue + 20}, 100%, 100%, ${p.alpha * 0.5})`);
406
+ innerGradient.addColorStop(1, `hsla(${p.hue}, 100%, 70%, 0)`);
407
+ ctx.fillStyle = innerGradient;
408
+ ctx.fill();
409
+ }
410
  });
411
  }
412
 
 
422
  resize();
423
  createStarSprites();
424
  initStars();
425
+
426
  window.addEventListener("resize", () => {
427
  resize();
428
  initStars();
429
  });
430
+
431
+ // Add some initial particles for visual interest
432
+ for (let i = 0; i < 100; i++) {
433
+ setTimeout(() => {
434
+ createParticles(rand(0, width), rand(0, height), 3, `hsl(${rand(180, 220)}, 100%, ${rand(60, 80)}%)`);
435
+ }, i * 100);
436
+ }
437
+
438
  loop();
439
  }
440