One parentless commit with only the files that the open seed tasks touch and the modules they import. The full squashed import (SPEC 8.6) is a later step: this tree is not the cutoff tree. T2T-StandIn-Source-Commit: 43f95b7333efe73aeaebf806023f17bffea27aba
117 lines
4.3 KiB
JavaScript
117 lines
4.3 KiB
JavaScript
// 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(0.33, 0.86, 0.77);
|
|
vec3 violet = vec3(0.51, 0.45, 1.0);
|
|
vec3 goldc = vec3(0.82, 0.63, 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(); },
|
|
};
|
|
}
|