Spaces:
Running
Running
Update stars.js
Browse files
stars.js
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
// High-Contrast, High-Density Starfield – Script Only
|
2 |
-
|
3 |
(() => {
|
4 |
const canvas = document.getElementById("starfield");
|
5 |
const ctx = canvas.getContext("2d");
|
@@ -10,7 +8,7 @@
|
|
10 |
let comets = [];
|
11 |
let pulses = [];
|
12 |
|
13 |
-
//
|
14 |
const layerConfigs = [
|
15 |
{ count: 400, speed: 1.5, size: 2.5 },
|
16 |
{ count: 300, speed: 3.0, size: 4.0 },
|
@@ -20,56 +18,95 @@
|
|
20 |
|
21 |
const rand = (min, max) => min + Math.random() * (max - min);
|
22 |
|
23 |
-
// Handle resize
|
24 |
function resize() {
|
25 |
width = window.innerWidth;
|
26 |
height = window.innerHeight;
|
27 |
canvas.width = width;
|
28 |
canvas.height = height;
|
29 |
|
30 |
-
//
|
31 |
bgCanvas = document.createElement("canvas");
|
32 |
bgCanvas.width = width;
|
33 |
bgCanvas.height = height;
|
34 |
bgCtx = bgCanvas.getContext("2d");
|
|
|
|
|
35 |
const bgGrad = bgCtx.createRadialGradient(
|
36 |
width / 2, height / 2, 0,
|
37 |
-
width / 2, height / 2, width
|
38 |
);
|
39 |
bgGrad.addColorStop(0, "#111122");
|
40 |
-
bgGrad.addColorStop(1, "#
|
41 |
bgCtx.fillStyle = bgGrad;
|
42 |
bgCtx.fillRect(0, 0, width, height);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
}
|
44 |
|
45 |
-
// Pre-render star sprites with
|
46 |
function createStarSprites() {
|
47 |
starSprites = layerConfigs.map(cfg => {
|
48 |
const r = cfg.size;
|
49 |
-
const sz = r * 6;
|
50 |
const off = document.createElement("canvas");
|
51 |
off.width = off.height = sz;
|
52 |
const octx = off.getContext("2d");
|
|
|
|
|
53 |
octx.fillStyle = "#fff";
|
54 |
octx.shadowColor = "#fff";
|
55 |
-
octx.shadowBlur = r *
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
octx.beginPath();
|
57 |
-
octx.arc(sz
|
58 |
octx.fill();
|
|
|
59 |
return off;
|
60 |
});
|
61 |
}
|
62 |
|
63 |
-
// Initialize
|
64 |
function initStars() {
|
65 |
layers = layerConfigs.map((cfg, idx) => {
|
66 |
const stars = [];
|
67 |
for (let i = 0; i < cfg.count; i++) {
|
68 |
stars.push({
|
69 |
-
x: rand(-width
|
70 |
-
y: rand(-height
|
71 |
z: rand(1, width),
|
72 |
-
|
|
|
|
|
73 |
sprIndex: idx,
|
74 |
});
|
75 |
}
|
@@ -77,108 +114,158 @@
|
|
77 |
});
|
78 |
}
|
79 |
|
80 |
-
// Spawn comets
|
81 |
function maybeComet() {
|
82 |
if (Math.random() < 0.002) {
|
|
|
83 |
comets.push({
|
84 |
-
x: rand(
|
85 |
-
y: rand(
|
86 |
len: 0,
|
87 |
maxLen: rand(80, 200),
|
88 |
-
angle:
|
89 |
speed: rand(10, 16),
|
|
|
|
|
90 |
});
|
91 |
}
|
92 |
}
|
93 |
|
94 |
-
// Click
|
95 |
canvas.addEventListener("click", e => {
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
});
|
98 |
|
99 |
-
// Update
|
100 |
function update() {
|
101 |
const now = Date.now();
|
|
|
|
|
102 |
layers.forEach(layer => {
|
103 |
layer.stars.forEach(s => {
|
104 |
-
s.z -= layer.speed;
|
105 |
if (s.z <= 0) {
|
106 |
s.z = width;
|
107 |
-
s.x = rand(-width
|
108 |
-
s.y = rand(-height
|
109 |
-
s.twAngle = rand(0, Math.PI * 2);
|
110 |
}
|
111 |
-
//
|
112 |
-
s.
|
113 |
});
|
114 |
});
|
115 |
-
|
116 |
-
|
|
|
117 |
comets.forEach(c => {
|
118 |
c.len += c.speed;
|
119 |
-
c.x
|
120 |
-
c.y
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
});
|
122 |
-
|
123 |
-
|
|
|
124 |
pulses.forEach(p => {
|
125 |
-
p.r +=
|
126 |
-
p.alpha -= 0.
|
127 |
});
|
128 |
-
|
129 |
maybeComet();
|
130 |
}
|
131 |
|
132 |
-
// Draw the
|
133 |
function draw() {
|
134 |
-
//
|
135 |
ctx.clearRect(0, 0, width, height);
|
136 |
ctx.drawImage(bgCanvas, 0, 0);
|
137 |
-
|
138 |
-
//
|
139 |
-
ctx.fillStyle = "rgba(0,0,
|
140 |
ctx.fillRect(0, 0, width, height);
|
141 |
-
|
142 |
-
//
|
143 |
layers.forEach((layer, li) => {
|
144 |
const sprite = starSprites[li];
|
145 |
ctx.globalCompositeOperation = "lighter";
|
146 |
layer.stars.forEach(s => {
|
147 |
-
const k = (width
|
148 |
-
const x = s.x * k + width
|
149 |
-
const y = s.y * k + height
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
|
|
|
|
|
|
|
|
154 |
ctx.globalAlpha = alpha;
|
155 |
ctx.drawImage(sprite, x - ds, y - ds, ds * 2, ds * 2);
|
156 |
});
|
157 |
-
ctx.globalCompositeOperation = "source-over";
|
158 |
});
|
|
|
159 |
ctx.globalAlpha = 1;
|
160 |
-
|
161 |
-
//
|
162 |
comets.forEach(c => {
|
163 |
-
ctx.
|
164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
ctx.beginPath();
|
166 |
-
ctx.
|
167 |
-
ctx.
|
168 |
-
|
169 |
-
|
170 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
ctx.stroke();
|
172 |
});
|
173 |
-
|
174 |
-
// 5) Warp pulses
|
175 |
-
pulses.forEach(p => {
|
176 |
-
const grd = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r);
|
177 |
-
grd.addColorStop(0, `rgba(255,255,255,${p.alpha})`);
|
178 |
-
grd.addColorStop(1, "rgba(0,0,0,0)");
|
179 |
-
ctx.fillStyle = grd;
|
180 |
-
ctx.fillRect(0, 0, width, height);
|
181 |
-
});
|
182 |
}
|
183 |
|
184 |
// Animation loop
|
@@ -188,7 +275,7 @@
|
|
188 |
requestAnimationFrame(loop);
|
189 |
}
|
190 |
|
191 |
-
// Initialize
|
192 |
function init() {
|
193 |
resize();
|
194 |
createStarSprites();
|
@@ -201,4 +288,4 @@
|
|
201 |
}
|
202 |
|
203 |
init();
|
204 |
-
})();
|
|
|
|
|
|
|
1 |
(() => {
|
2 |
const canvas = document.getElementById("starfield");
|
3 |
const ctx = canvas.getContext("2d");
|
|
|
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 },
|
|
|
18 |
|
19 |
const rand = (min, max) => min + Math.random() * (max - min);
|
20 |
|
21 |
+
// Handle resize
|
22 |
function resize() {
|
23 |
width = window.innerWidth;
|
24 |
height = window.innerHeight;
|
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;
|
32 |
bgCtx = bgCanvas.getContext("2d");
|
33 |
+
|
34 |
+
// Base gradient
|
35 |
const bgGrad = bgCtx.createRadialGradient(
|
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 = [];
|
102 |
for (let i = 0; i < cfg.count; i++) {
|
103 |
stars.push({
|
104 |
+
x: rand(-width/2, width/2),
|
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 |
}
|
|
|
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";
|
211 |
layer.stars.forEach(s => {
|
212 |
+
const k = (width/2) / s.z;
|
213 |
+
const x = s.x * k + width/2;
|
214 |
+
const y = s.y * k + height/2;
|
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);
|
225 |
});
|
|
|
226 |
});
|
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++) {
|
234 |
+
const seg1 = c.segments[i-1];
|
235 |
+
const seg2 = c.segments[i];
|
236 |
+
|
237 |
+
const gradient = ctx.createLinearGradient(
|
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);
|
249 |
+
ctx.stroke();
|
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);
|
257 |
+
const gradient = ctx.createRadialGradient(
|
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 |
|
271 |
// Animation loop
|
|
|
275 |
requestAnimationFrame(loop);
|
276 |
}
|
277 |
|
278 |
+
// Initialize
|
279 |
function init() {
|
280 |
resize();
|
281 |
createStarSprites();
|
|
|
288 |
}
|
289 |
|
290 |
init();
|
291 |
+
})();
|