import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x05040a);
const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 100);
camera.position.set(0, 0.6, 5.2);
const COUNT = 4000;
const seed = new Float32Array(COUNT * 4);
for (let i = 0; i < COUNT; i++) {
seed[i * 4 + 0] = (Math.random() - 0.5) * 1.4;
seed[i * 4 + 1] = Math.random() * 6.2831852;
seed[i * 4 + 2] = 0.6 + Math.random() * 1.2;
seed[i * 4 + 3] = Math.random();
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(COUNT * 3), 3));
geo.setAttribute('aSeed', new THREE.Float32BufferAttribute(seed, 4));
const vertexShader = [
'attribute vec4 aSeed;',
'uniform float uTime;',
'varying float vLife;',
'void main() {',
' float life = fract(aSeed.w + uTime * 0.12 * aSeed.z);',
' float angle = aSeed.y + life * 2.0;',
' float radius = aSeed.x + life * 0.6;',
' vec3 p = vec3(cos(angle) * radius, life * 3.2 - 1.4, sin(angle) * radius);',
' vLife = life;',
' vec4 mv = modelViewMatrix * vec4(p, 1.0);',
' gl_PointSize = (1.0 - life) * 22.0 * (1.0 / -mv.z);',
' gl_Position = projectionMatrix * mv;',
'}',
].join('\n');
const fragmentShader = [
'varying float vLife;',
'void main() {',
' vec2 uv = gl_PointCoord - 0.5;',
' float d = length(uv);',
' if (d > 0.5) discard;',
' float alpha = smoothstep(0.5, 0.0, d) * (1.0 - vLife);',
' vec3 hot = vec3(1.0, 0.85, 0.4);',
' vec3 cool = vec3(1.0, 0.35, 0.15);',
' vec3 color = mix(cool, hot, 1.0 - vLife);',
' gl_FragColor = vec4(color, alpha);',
'}',
].join('\n');
const mat = new THREE.ShaderMaterial({
uniforms: { uTime: { value: 0 } },
vertexShader,
fragmentShader,
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending,
});
const points = new THREE.Points(geo, mat);
points.frustumCulled = false;
scene.add(points);
function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
mat.uniforms.uTime.value = t * 0.001;
points.rotation.y = t * 0.00008;
renderer.render(scene, camera);
});