Merge floating particles into WebGL shader and fix color picker clipping
Remove overflow:hidden from .card and .template-card that was clipping the color picker popover. Combine noise field + particle glow into a single GPU shader pass (40 drifting particles as uniforms). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* Ambient background animation — WebGL shader.
|
||||
* Ambient background animation — WebGL shader + floating particles.
|
||||
*
|
||||
* Renders a smooth flowing noise field with accent-colored highlights.
|
||||
* GPU-accelerated, no banding, very lightweight.
|
||||
* Renders a smooth flowing noise field with accent-colored highlights
|
||||
* plus drifting glowing particles, all in a single GPU shader pass.
|
||||
*/
|
||||
|
||||
const PARTICLE_COUNT = 40;
|
||||
|
||||
const VERT_SRC = `
|
||||
attribute vec2 a_pos;
|
||||
void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }
|
||||
@@ -16,8 +18,9 @@ uniform float u_time;
|
||||
uniform vec2 u_res;
|
||||
uniform vec3 u_accent;
|
||||
uniform vec3 u_bg;
|
||||
uniform vec3 u_particles[${PARTICLE_COUNT}]; // xy = position (0-1), z = radius
|
||||
|
||||
// Simplex-style noise (compact hash-based)
|
||||
// Simplex-style noise
|
||||
vec3 mod289(vec3 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
|
||||
vec2 mod289(vec2 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
|
||||
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
|
||||
@@ -59,31 +62,76 @@ void main() {
|
||||
|
||||
float n = n1 * 0.5 + n2 * 0.3 + n3 * 0.2;
|
||||
|
||||
// Map noise to a soft glow: only show the bright peaks
|
||||
// Map noise to a soft glow
|
||||
float glow = smoothstep(0.05, 0.6, n);
|
||||
|
||||
// Create two accent-derived colors with hue shift
|
||||
// Two accent-derived colors
|
||||
vec3 col1 = u_accent;
|
||||
vec3 col2 = u_accent.gbr; // channel rotation for variety
|
||||
vec3 col2 = u_accent.gbr;
|
||||
|
||||
// Mix between the two colors based on second noise layer
|
||||
float colorMix = n2 * 0.5 + 0.5;
|
||||
vec3 color = mix(col1, col2, colorMix);
|
||||
|
||||
// Final: blend colored glow onto background
|
||||
// Noise background
|
||||
vec3 result = mix(u_bg, u_bg + color * 0.6, glow * 0.14);
|
||||
|
||||
// Floating particles — accumulate glow from each
|
||||
float particleGlow = 0.0;
|
||||
float particleColor = 0.0;
|
||||
for (int i = 0; i < ${PARTICLE_COUNT}; i++) {
|
||||
vec2 ppos = u_particles[i].xy;
|
||||
float pradius = u_particles[i].z;
|
||||
// Particle position in aspect-corrected space
|
||||
vec2 pp = vec2(ppos.x * aspect, ppos.y);
|
||||
float d = length(p - pp);
|
||||
float g = pradius / (d * d * 800.0 + pradius);
|
||||
particleGlow += g;
|
||||
// Alternate color based on index parity (use float comparison)
|
||||
particleColor += g * step(0.5, fract(float(i) * 0.5));
|
||||
}
|
||||
|
||||
// Mix particle colors: accent and white-ish accent
|
||||
vec3 pCol = mix(u_accent, mix(u_accent, vec3(1.0), 0.5), particleColor / max(particleGlow, 0.001));
|
||||
result += pCol * particleGlow * 0.5;
|
||||
|
||||
gl_FragColor = vec4(result, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
let _canvas, _gl, _prog;
|
||||
let _uTime, _uRes, _uAccent, _uBg;
|
||||
let _uTime, _uRes, _uAccent, _uBg, _uParticles;
|
||||
let _raf = null;
|
||||
let _startTime = 0;
|
||||
let _accent = [76 / 255, 175 / 255, 80 / 255];
|
||||
let _bgColor = [26 / 255, 26 / 255, 26 / 255];
|
||||
|
||||
// Particle state (CPU-side, positions in 0..1 UV space)
|
||||
const _particles = [];
|
||||
|
||||
function _initParticles() {
|
||||
_particles.length = 0;
|
||||
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
||||
_particles.push({
|
||||
x: Math.random(),
|
||||
y: Math.random(),
|
||||
vx: (Math.random() - 0.5) * 0.0002,
|
||||
vy: 0.0001 + Math.random() * 0.0003,
|
||||
r: 0.003 + Math.random() * 0.006,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _updateParticles() {
|
||||
for (const p of _particles) {
|
||||
p.x += p.vx;
|
||||
p.y += p.vy;
|
||||
// Wrap around
|
||||
if (p.y > 1.05) { p.y = -0.05; p.x = Math.random(); }
|
||||
if (p.x < -0.05) p.x = 1.05;
|
||||
if (p.x > 1.05) p.x = -0.05;
|
||||
}
|
||||
}
|
||||
|
||||
function _compile(gl, type, src) {
|
||||
const s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
@@ -126,15 +174,17 @@ function _initGL() {
|
||||
_uRes = gl.getUniformLocation(_prog, 'u_res');
|
||||
_uAccent = gl.getUniformLocation(_prog, 'u_accent');
|
||||
_uBg = gl.getUniformLocation(_prog, 'u_bg');
|
||||
_uParticles = [];
|
||||
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
||||
_uParticles.push(gl.getUniformLocation(_prog, `u_particles[${i}]`));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function _resize() {
|
||||
// Render at half resolution for performance
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 1);
|
||||
const w = Math.round(window.innerWidth * dpr * 0.5);
|
||||
const h = Math.round(window.innerHeight * dpr * 0.5);
|
||||
const w = Math.round(window.innerWidth * 0.5);
|
||||
const h = Math.round(window.innerHeight * 0.5);
|
||||
_canvas.width = w;
|
||||
_canvas.height = h;
|
||||
if (_gl) _gl.viewport(0, 0, w, h);
|
||||
@@ -145,12 +195,19 @@ function _draw(time) {
|
||||
const gl = _gl;
|
||||
if (!gl) return;
|
||||
|
||||
_updateParticles();
|
||||
|
||||
const t = (time - _startTime) * 0.001;
|
||||
gl.uniform1f(_uTime, t);
|
||||
gl.uniform2f(_uRes, _canvas.width, _canvas.height);
|
||||
gl.uniform3f(_uAccent, _accent[0], _accent[1], _accent[2]);
|
||||
gl.uniform3f(_uBg, _bgColor[0], _bgColor[1], _bgColor[2]);
|
||||
|
||||
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
||||
const p = _particles[i];
|
||||
gl.uniform3f(_uParticles[i], p.x, p.y, p.r);
|
||||
}
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
@@ -158,6 +215,7 @@ function _start() {
|
||||
if (_raf) return;
|
||||
if (!_gl && !_initGL()) return;
|
||||
_resize();
|
||||
_initParticles();
|
||||
_startTime = performance.now();
|
||||
_raf = requestAnimationFrame(_draw);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user