Spaces:
Running
Running
Update stars.js
Browse files
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:
|
14 |
-
{ count:
|
15 |
-
{ count:
|
16 |
-
{ count:
|
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, "#
|
|
|
40 |
bgGrad.addColorStop(1, "#000011");
|
41 |
bgCtx.fillStyle = bgGrad;
|
42 |
bgCtx.fillRect(0, 0, width, height);
|
43 |
|
44 |
-
//
|
45 |
-
const nebulaCount = Math.floor(width * height /
|
46 |
-
bgCtx.globalCompositeOperation = "
|
47 |
for (let i = 0; i < nebulaCount; i++) {
|
48 |
const x = rand(0, width);
|
49 |
const y = rand(0, height);
|
50 |
-
const radius = rand(
|
51 |
const hue = rand(220, 280);
|
52 |
-
const alpha = rand(0.
|
53 |
|
54 |
const grd = bgCtx.createRadialGradient(
|
55 |
x, y, 0,
|
56 |
x, y, radius
|
57 |
);
|
58 |
-
grd.addColorStop(0, `hsla(${hue},
|
59 |
-
grd.addColorStop(
|
|
|
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 *
|
74 |
const off = document.createElement("canvas");
|
75 |
off.width = off.height = sz;
|
76 |
const octx = off.getContext("2d");
|
77 |
|
78 |
-
// Core glow
|
79 |
-
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
octx.beginPath();
|
83 |
-
octx.arc(sz/2, sz/2, r, 0, Math.PI * 2);
|
84 |
octx.fill();
|
85 |
|
86 |
-
// Bright core
|
87 |
-
octx.
|
88 |
-
octx.
|
|
|
89 |
octx.fillStyle = "#ffffff";
|
90 |
octx.beginPath();
|
91 |
-
octx.arc(sz/2, sz/2, r * 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
109 |
-
twRange: rand(0.
|
110 |
sprIndex: idx,
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
});
|
112 |
}
|
113 |
return { ...cfg, stars };
|
114 |
});
|
115 |
}
|
116 |
|
117 |
-
// Spawn comets with
|
118 |
function maybeComet() {
|
119 |
-
if (Math.random() < 0.
|
120 |
-
const angle = Math.PI * 0.25 + rand(-0.
|
121 |
comets.push({
|
122 |
-
x: rand(-
|
123 |
-
y: rand(-
|
124 |
len: 0,
|
125 |
-
maxLen: rand(
|
126 |
angle: angle,
|
127 |
-
speed: rand(
|
128 |
segments: [],
|
129 |
-
alpha: 1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
});
|
131 |
}
|
132 |
}
|
133 |
|
134 |
-
// Click creates warp pulse with
|
135 |
canvas.addEventListener("click", e => {
|
136 |
-
|
|
|
137 |
pulses.push({
|
138 |
x: e.clientX,
|
139 |
y: e.clientY,
|
140 |
-
r: i *
|
141 |
-
maxR:
|
142 |
-
thickness:
|
143 |
-
alpha: 0.
|
144 |
-
speed:
|
|
|
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.
|
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({
|
|
|
|
|
|
|
|
|
|
|
176 |
|
177 |
// Fade old segments
|
178 |
-
c.segments.forEach(seg => seg.alpha -= 0.
|
179 |
|
180 |
// Remove old segments
|
181 |
-
if (c.segments.length >
|
182 |
|
183 |
// Fade entire comet
|
184 |
-
c.alpha -= 0.
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
//
|
204 |
-
ctx.fillStyle = "rgba(
|
205 |
ctx.fillRect(0, 0, width, height);
|
206 |
|
207 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
221 |
-
const
|
|
|
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, `
|
242 |
-
gradient.addColorStop(
|
|
|
243 |
|
244 |
ctx.strokeStyle = gradient;
|
245 |
-
ctx.lineWidth = Math.max(1,
|
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
|
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, `
|
262 |
-
gradient.addColorStop(0.
|
263 |
-
gradient.addColorStop(
|
|
|
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 |
|