Spaces:
Running
Running
File size: 4,365 Bytes
b30b7c5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | // Galaxia de partículas WebGL para el splash — sin librerías, autocontenida.
// Técnica: GL_POINTS + blending aditivo + rotación diferencial calculada por
// completo en el vertex shader desde una semilla (la CPU no toca nada por
// frame). ~24k partículas en espiral rosa→violeta con motas doradas, parallax
// de ratón y explosión al pulsar Entrar.
export function startGalaxy(container) {
const canvas = document.createElement('canvas');
canvas.className = 'gl';
container.prepend(canvas);
const gl = canvas.getContext('webgl', { alpha: true, antialias: false, depth: false });
if (!gl) { canvas.remove(); return null; } // fallback: se queda el gradiente CSS
const N = 24000;
const seeds = new Float32Array(N * 2);
for (let i = 0; i < seeds.length; i++) seeds[i] = Math.random();
const VS = `
attribute vec2 seed;
uniform float t, aspect, burst, dpr;
uniform vec2 par;
varying vec3 vColor;
varying float vGlow;
float hash(float n){ return fract(sin(n) * 43758.5453); }
void main(){
float r = 0.08 + 1.35 * pow(seed.y, 0.65);
float arm = floor(hash(seed.x * 7.0 + seed.y) * 3.0);
float ang = seed.x * 0.9 + arm * 2.0944 + r * 2.6 + t * (0.12 + 0.25 / (r + 0.2));
float wob = (hash(seed.x * 13.1) - 0.5) * 0.22 * r;
vec2 p = vec2(cos(ang), sin(ang)) * (r + wob);
p.y *= 0.62;
p.y += (hash(seed.y * 17.7) - 0.5) * 0.14;
p += par * (0.03 + r * 0.06);
p *= 1.0 + burst * (0.4 + seed.y * 2.4);
gl_Position = vec4(p.x / aspect, p.y, 0.0, 1.0);
float tw = 0.55 + 0.45 * sin(t * (1.5 + seed.x * 3.0) + seed.y * 40.0);
vGlow = tw * (1.0 - burst * 0.55);
float gold = step(0.93, hash(seed.x * 31.3 + seed.y * 7.7));
vec3 pink = vec3(1.0, 0.30, 0.55);
vec3 violet = vec3(0.49, 0.36, 1.0);
vec3 goldc = vec3(1.0, 0.84, 0.42);
vColor = mix(mix(pink, violet, smoothstep(0.1, 1.3, r)), goldc, gold);
gl_PointSize = (1.3 + 2.8 * tw * max(1.2 - r * 0.5, 0.2)) * dpr;
}`;
const FS = `
precision mediump float;
varying vec3 vColor;
varying float vGlow;
void main(){
float m = smoothstep(0.5, 0.0, length(gl_PointCoord - 0.5));
gl_FragColor = vec4(vColor * vGlow, m * vGlow);
}`;
const sh = (type, src) => {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
return s;
};
const prog = gl.createProgram();
gl.attachShader(prog, sh(gl.VERTEX_SHADER, VS));
gl.attachShader(prog, sh(gl.FRAGMENT_SHADER, FS));
gl.linkProgram(prog);
gl.useProgram(prog);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, seeds, gl.STATIC_DRAW); // una vez; la GPU manda
const loc = gl.getAttribLocation(prog, 'seed');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
const U = n => gl.getUniformLocation(prog, n);
const uT = U('t'), uAspect = U('aspect'), uBurst = U('burst'), uPar = U('par'), uDpr = U('dpr');
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE); // aditivo = brillo
gl.clearColor(0, 0, 0, 0);
let burstAt = 0, alive = true;
const mouse = { x: 0, y: 0, tx: 0, ty: 0 };
const onMove = e => {
mouse.tx = (e.clientX / innerWidth - 0.5) * 2;
mouse.ty = -(e.clientY / innerHeight - 0.5) * 2;
};
addEventListener('pointermove', onMove);
const dpr = Math.min(devicePixelRatio || 1, 2);
function frame(now) {
if (!alive) return;
const w = container.clientWidth, h = container.clientHeight;
if (canvas.width !== w * dpr || canvas.height !== h * dpr) {
canvas.width = w * dpr;
canvas.height = h * dpr;
gl.viewport(0, 0, canvas.width, canvas.height);
}
mouse.x += (mouse.tx - mouse.x) * 0.05;
mouse.y += (mouse.ty - mouse.y) * 0.05;
const t = now / 1000;
const burst = burstAt ? Math.min((now - burstAt) / 900, 1) : 0;
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform1f(uT, t);
gl.uniform1f(uAspect, w / h);
gl.uniform1f(uBurst, burst);
gl.uniform1f(uDpr, dpr);
gl.uniform2f(uPar, mouse.x, mouse.y);
gl.drawArrays(gl.POINTS, 0, N);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
return {
burst() { burstAt = performance.now(); },
stop() { alive = false; removeEventListener('pointermove', onMove); canvas.remove(); },
};
}
|