docs(site): switch hero to React Bits–style beams

Replace the wave gradient with a vanilla Three.js beams field using the
shared demo preset and OSG green lighting for a more dimensional look.
This commit is contained in:
Rocky
2026-07-29 23:12:49 +08:00
parent bf4492ee2f
commit 38a76e9217
6 changed files with 438 additions and 402 deletions
+373
View File
@@ -0,0 +1,373 @@
/*!
* OSGKeyboard hero beams — vanilla Three.js recreation inspired by
* React Bits "Beams" (https://reactbits.dev/backgrounds/beams).
* Parameters match the shared demo preset.
* three.js is MIT; this file is project code.
*/
(function (global) {
"use strict";
const NOISE = `
float random (in vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
float noise (in vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
float cnoise(vec3 P){
vec3 Pi0 = floor(P);
vec3 Pi1 = Pi0 + vec3(1.0);
Pi0 = mod(Pi0, 289.0);
Pi1 = mod(Pi1, 289.0);
vec3 Pf0 = fract(P);
vec3 Pf1 = Pf0 - vec3(1.0);
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
vec4 iy = vec4(Pi0.yy, Pi1.yy);
vec4 iz0 = Pi0.zzzz;
vec4 iz1 = Pi1.zzzz;
vec4 ixy = permute(permute(ix) + iy);
vec4 ixy0 = permute(ixy + iz0);
vec4 ixy1 = permute(ixy + iz1);
vec4 gx0 = ixy0 / 7.0;
vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
gx0 = fract(gx0);
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
vec4 sz0 = step(gz0, vec4(0.0));
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
vec4 gx1 = ixy1 / 7.0;
vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
gx1 = fract(gx1);
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
vec4 sz1 = step(gz1, vec4(0.0));
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));
g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;
vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));
g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;
float n000 = dot(g000, Pf0);
float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));
float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));
float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));
float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));
float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));
float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));
float n111 = dot(g111, Pf1);
vec3 fade_xyz = fade(Pf0);
vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);
vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);
float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);
return 2.2 * n_xyz;
}
`;
function hexToNormalizedRGB(hex) {
const clean = String(hex).replace("#", "");
const full = clean.length === 3
? clean.split("").map((c) => c + c).join("")
: clean;
return [
parseInt(full.substring(0, 2), 16) / 255,
parseInt(full.substring(2, 4), 16) / 255,
parseInt(full.substring(4, 6), 16) / 255
];
}
function extendMaterial(THREE, BaseMaterial, cfg) {
const physical = THREE.ShaderLib.physical;
const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;
const baseDefines = physical.defines || {};
const uniforms = THREE.UniformsUtils.clone(baseUniforms);
const defaults = new BaseMaterial(cfg.material || {});
if (defaults.color) uniforms.diffuse.value = defaults.color;
if ("roughness" in defaults) uniforms.roughness.value = defaults.roughness;
if ("metalness" in defaults) uniforms.metalness.value = defaults.metalness;
if ("envMap" in defaults) uniforms.envMap.value = defaults.envMap;
if ("envMapIntensity" in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;
Object.entries(cfg.uniforms || {}).forEach(([key, u]) => {
uniforms[key] = u !== null && typeof u === "object" && "value" in u ? u : { value: u };
});
let vert = `${cfg.header}\n${cfg.vertexHeader || ""}\n${baseVert}`;
let frag = `${cfg.header}\n${cfg.fragmentHeader || ""}\n${baseFrag}`;
for (const [inc, code] of Object.entries(cfg.vertex || {})) {
vert = vert.replace(inc, `${inc}\n${code}`);
}
for (const [inc, code] of Object.entries(cfg.fragment || {})) {
frag = frag.replace(inc, `${inc}\n${code}`);
}
return new THREE.ShaderMaterial({
defines: { ...baseDefines },
uniforms,
vertexShader: vert,
fragmentShader: frag,
lights: true,
fog: !!(cfg.material && cfg.material.fog)
});
}
function createStackedPlanesBufferGeometry(THREE, n, width, height, spacing, heightSegments) {
const geometry = new THREE.BufferGeometry();
const numVertices = n * (heightSegments + 1) * 2;
const numFaces = n * heightSegments * 2;
const positions = new Float32Array(numVertices * 3);
const indices = new Uint32Array(numFaces * 3);
const uvs = new Float32Array(numVertices * 2);
let vertexOffset = 0;
let indexOffset = 0;
let uvOffset = 0;
const totalWidth = n * width + (n - 1) * spacing;
const xOffsetBase = -totalWidth / 2;
for (let i = 0; i < n; i++) {
const xOffset = xOffsetBase + i * (width + spacing);
const uvXOffset = Math.random() * 300;
const uvYOffset = Math.random() * 300;
for (let j = 0; j <= heightSegments; j++) {
const y = height * (j / heightSegments - 0.5);
positions.set([xOffset, y, 0, xOffset + width, y, 0], vertexOffset * 3);
const uvY = j / heightSegments;
uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);
if (j < heightSegments) {
const a = vertexOffset;
const b = vertexOffset + 1;
const c = vertexOffset + 2;
const d = vertexOffset + 3;
indices.set([a, b, c, c, b, d], indexOffset);
indexOffset += 6;
}
vertexOffset += 2;
uvOffset += 4;
}
}
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
geometry.computeVertexNormals();
return geometry;
}
/**
* @param {HTMLCanvasElement} canvas
* @param {object} options
*/
function create(canvas, options) {
const THREE = global.THREE;
if (!THREE || !canvas) throw new Error("THREE or canvas missing");
const {
beamWidth = 4.5,
beamHeight = 6,
beamNumber = 26,
lightColor = "#7CFFB2",
speed = 4.5,
noiseIntensity = 2.65,
scale = 0.62,
rotation = 208,
background = "#000000",
reduceMotion = false,
heightSegments = 100
} = options || {};
const scene = new THREE.Scene();
scene.background = new THREE.Color(background);
const camera = new THREE.PerspectiveCamera(30, 1, 0.1, 100);
camera.position.set(0, 0, 20);
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: false,
powerPreference: "low-power"
});
renderer.setClearColor(background, 1);
renderer.outputColorSpace = THREE.SRGBColorSpace;
const material = extendMaterial(THREE, THREE.MeshStandardMaterial, {
header: `
varying vec3 vEye;
varying float vNoise;
varying vec2 vUv;
varying vec3 vPosition;
uniform float time;
uniform float uSpeed;
uniform float uNoiseIntensity;
uniform float uScale;
${NOISE}`,
vertexHeader: `
float getPos(vec3 pos) {
vec3 noisePos =
vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;
return cnoise(noisePos);
}
vec3 getCurrentPos(vec3 pos) {
vec3 newpos = pos;
newpos.z += getPos(pos);
return newpos;
}
vec3 getNormal(vec3 pos) {
vec3 curpos = getCurrentPos(pos);
vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));
vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));
vec3 tangentX = normalize(nextposX - curpos);
vec3 tangentZ = normalize(nextposZ - curpos);
return normalize(cross(tangentZ, tangentX));
}`,
vertex: {
"#include <begin_vertex>": "transformed.z += getPos(transformed.xyz);",
"#include <beginnormal_vertex>": "objectNormal = getNormal(position.xyz);"
},
fragment: {
"#include <dithering_fragment>": `
float randomNoise = noise(gl_FragCoord.xy);
gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`
},
material: { fog: true },
uniforms: {
diffuse: new THREE.Color(...hexToNormalizedRGB("#000000")),
time: { value: 0 },
roughness: 0.3,
metalness: 0.3,
uSpeed: { value: speed },
envMapIntensity: 10,
uNoiseIntensity: { value: noiseIntensity },
uScale: { value: scale }
}
});
const geometry = createStackedPlanesBufferGeometry(
THREE,
beamNumber,
beamWidth,
beamHeight,
0,
heightSegments
);
const mesh = new THREE.Mesh(geometry, material);
const group = new THREE.Group();
group.rotation.z = THREE.MathUtils.degToRad(rotation);
group.add(mesh);
const dir = new THREE.DirectionalLight(lightColor, 1);
dir.position.set(0, 3, 10);
group.add(dir);
scene.add(group);
scene.add(new THREE.AmbientLight(0xffffff, 1));
let playing = !reduceMotion;
let visible = true;
let raf = 0;
let last = performance.now();
let disposed = false;
function resize() {
const parent = canvas.parentElement || canvas;
const rect = parent.getBoundingClientRect();
const w = Math.max(1, Math.floor(rect.width));
const h = Math.max(1, Math.floor(rect.height));
const dpr = Math.min(window.devicePixelRatio || 1, reduceMotion ? 1 : 1.75);
renderer.setPixelRatio(dpr);
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
function frame(now) {
if (disposed) return;
raf = requestAnimationFrame(frame);
if (!playing || !visible || document.hidden) return;
const delta = Math.min(0.05, (now - last) / 1000);
last = now;
material.uniforms.time.value += 0.1 * delta;
renderer.render(scene, camera);
}
function renderOnce() {
renderer.render(scene, camera);
}
resize();
// Seed a bit of displacement so the first frame isn't flat
material.uniforms.time.value = reduceMotion ? 2.4 : 0.2;
renderOnce();
if (!reduceMotion) {
last = performance.now();
raf = requestAnimationFrame(frame);
}
const ro = new ResizeObserver(() => {
resize();
renderOnce();
});
ro.observe(canvas.parentElement || canvas);
return {
setPlaying(next) {
playing = !!next && !reduceMotion;
if (playing && !raf) {
last = performance.now();
raf = requestAnimationFrame(frame);
}
},
setVisible(next) {
visible = !!next;
if (visible && playing && !raf) {
last = performance.now();
raf = requestAnimationFrame(frame);
}
if (visible) renderOnce();
},
setLightColor(hex) {
dir.color.set(hex);
renderOnce();
},
setBackground(hex) {
scene.background = new THREE.Color(hex);
renderer.setClearColor(hex, 1);
renderOnce();
},
resize,
destroy() {
disposed = true;
cancelAnimationFrame(raf);
raf = 0;
ro.disconnect();
geometry.dispose();
material.dispose();
renderer.dispose();
}
};
}
global.OSGBeamsHero = { create };
})(typeof window !== "undefined" ? window : globalThis);
@@ -1,6 +1,6 @@
MIT License
The MIT License
Copyright (c) 2022 Mohamed ElSaadany
Copyright © 2010-2023 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+7
View File
File diff suppressed because one or more lines are too long
-341
View File
@@ -1,341 +0,0 @@
/*! wave-gradient (MIT) https://github.com/sa3dany/wave-gradient — Copyright (c) 2022 Mohamed ElSaadany */
"use strict";
var WaveGradientModule = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/wave-gradient.js
var wave_gradient_exports = {};
__export(wave_gradient_exports, {
WaveGradient: () => WaveGradient
});
// src/shaders.js
var vert = `#version 300 es
vec3 o(vec3 i,vec3 c,float r){return c*r+i*(1.-r);}vec3 o(vec3 n){return n-floor(n*(1./289.))*289.;}vec4 o(vec4 n){return n-floor(n*(1./289.))*289.;}vec4 e(vec4 n){return o((n*34.+1.)*n);}vec4 v(vec4 y){return 1.79284291400159-.85373472095314*y;}float t(vec3 l){const vec2 s=vec2(1./6.,1./3.);const vec4 u=vec4(0.,.5,1.,2.);vec3 a=floor(l+dot(l,s.yyy)),x=l-a+dot(a,s.xxx),d=step(x.yzx,x.xyz),f=1.-d,z=min(d.xyz,f.zxy),w=max(d.xyz,f.zxy),m=x-z+s.xxx,C=x-w+s.yyy,p=x-u.yyy;a=o(a);vec4 P=e(e(e(a.z+vec4(0.,z.z,w.z,1.))+a.y+vec4(0.,z.y,w.y,1.))+a.x+vec4(0.,z.x,w.x,1.));vec3 S=.142857142857*u.wyz-u.xzx;vec4 L=P-49.*floor(P*S.z*S.z),F=floor(L*S.z),R=floor(L-7.*F),n=F*S.x+S.yyyy,W=R*S.x+S.yyyy,b=1.-abs(n)-abs(W),G=vec4(n.xy,W.xy),q=vec4(n.zw,W.zw),h=floor(G)*2.+1.,g=floor(q)*2.+1.,O=-step(b,vec4(0.)),B=G.xzyw+h.xzyw*O.xxyy,A=q.xzyw+g.xzyw*O.zzww;vec3 E=vec3(B.xy,b.x),Z=vec3(B.zw,b.y),Y=vec3(A.xy,b.z),X=vec3(A.zw,b.w);vec4 V=v(vec4(dot(E,E),dot(Z,Z),dot(Y,Y),dot(X,X)));E*=V.x;Z*=V.y;Y*=V.z;X*=V.w;vec4 U=max(.6-vec4(dot(x,x),dot(m,m),dot(C,C),dot(p,p)),0.);U=U*U;return 42.*dot(U*U,vec4(dot(E,x),dot(Z,m),dot(Y,C),dot(X,p)));}uniform mediump vec2 u_Resolution;uniform float u_Amplitude,u_Realtime,u_Seed;uniform vec3 u_BaseColor;uniform int u_LayerCount;uniform struct WaveLayers{float noiseCeil;float noiseFloor;float noiseFlow;float noiseSeed;float noiseSpeed;vec2 noiseFreq;vec3 color;} u_WaveLayers[9];in vec3 a_Position;out vec3 v_Color;void main(){float T=u_Realtime*5e-6;vec2 Q=vec2(.00014,.00029),N=u_Resolution*a_Position.xy*Q;float M=u_Amplitude*(2./u_Resolution.y),K=t(vec3(N.x*3.+T*3.,N.y*4.,T*10.+u_Seed));K*=1.-pow(abs(a_Position.y),2.);K=max(0.,K);gl_Position=vec4(a_Position.x,a_Position.y+K*M,a_Position.z,1.);v_Color=u_BaseColor;for(int a=0;a<u_LayerCount;a++){WaveLayers J=u_WaveLayers[a];float K=t(vec3(N.x*J.noiseFreq.x+T*J.noiseFlow,N.y*J.noiseFreq.y,T*J.noiseSpeed+J.noiseSeed));K=K/2.+.5;K=smoothstep(J.noiseFloor,J.noiseCeil,K);v_Color=o(v_Color,J.color,pow(K,4.));}}
`;
var frag = `#version 300 es
precision mediump float;uniform vec2 u_Resolution;uniform float u_ShadowPower;in vec3 v_Color;out vec4 color;void main(){vec2 I=gl_FragCoord.xy/u_Resolution.xy;color=vec4(v_Color,1.);color.y-=pow(I.y+sin(-12.)*I.x,u_ShadowPower)*.4;}
`;
// src/wave-gradient.js
function parseRGB(hex) {
const result = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i) || hex.match(/^#?([a-f\d])([a-f\d])([a-f\d])$/i);
return result ? result.slice(1, 4).map((c) => {
return parseInt(c.length < 2 ? c + c : c, 16) / 255;
}) : null;
}
var ClipSpace = class {
static createPlaneGeometry(widthSegments, depthSegments) {
const gridX = Math.ceil(widthSegments);
const gridZ = Math.ceil(depthSegments);
const vertexCount = 3 * (gridX + 1) * (gridZ + 1);
const indexCount = 3 * 2 * gridX * gridZ;
const positions = new ArrayBuffer(4 * vertexCount);
const indices = new ArrayBuffer(4 * indexCount);
for (let z = gridZ, i = 0, view = new DataView(positions); z >= 0; z--) {
const v = z / gridZ;
const clipY = v * 2 - 1;
for (let x = gridX; x >= 0; x--, i += 3) {
const clipX = x / gridX * 2 - 1;
view.setFloat32((i + 0) * 4, clipX, true);
view.setFloat32((i + 1) * 4, clipY, true);
view.setFloat32((i + 2) * 4, v, true);
}
}
const verticesAcross = gridX + 1;
for (let z = 0, i = 0, view = new DataView(indices); z < gridZ; z++) {
for (let x = 0; x < gridX; x++, i += 6) {
view.setUint32((i + 0) * 4, (z + 0) * verticesAcross + x, true);
view.setUint32((i + 1) * 4, (z + 0) * verticesAcross + x + 1, true);
view.setUint32((i + 2) * 4, (z + 1) * verticesAcross + x, true);
view.setUint32((i + 3) * 4, (z + 0) * verticesAcross + x + 1, true);
view.setUint32((i + 4) * 4, (z + 1) * verticesAcross + x + 1, true);
view.setUint32((i + 5) * 4, (z + 1) * verticesAcross + x, true);
}
}
return { positions, indices, count: indexCount };
}
static prefixName(name, prefix) {
return `${prefix}${name[0].toUpperCase()}${name.slice(1)}`;
}
constructor(config) {
this.gl = config.gl;
this.program = this.createProgram(config.shaders);
this._attributes = {};
this.setupAttributes(config.attributes);
this._elementBuffer;
this.setElements(config.elements);
this._uniforms = {};
this.setupUniforms(config.uniforms);
}
compileShader(type, source) {
const { gl } = this;
let shader = gl.createShader(type);
if (!shader)
throw new Error("can't create shader");
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
debugProgram(program) {
const { gl } = this;
const [vs, fs] = gl.getAttachedShaders(program) ?? [];
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(
`can't link WebGL program.
${gl.getProgramInfoLog(program)}
${gl.getShaderInfoLog(vs)}
${gl.getShaderInfoLog(fs)}`
);
}
}
createProgram(shaders) {
const { gl } = this;
const [vs, fs] = [
this.compileShader(gl.VERTEX_SHADER, shaders[0]),
this.compileShader(gl.FRAGMENT_SHADER, shaders[1])
];
const program = gl.createProgram();
if (!program)
throw new Error("can't create WebGL program");
gl.attachShader(program, vs);
gl.attachShader(program, fs);
try {
gl.linkProgram(program);
this.debugProgram(program);
} catch (linkError) {
gl.deleteProgram(program);
throw linkError;
} finally {
gl.deleteShader(vs);
gl.deleteShader(fs);
}
gl.useProgram(program);
return program;
}
createBuffer() {
const { gl } = this;
const buffer = gl.createBuffer();
if (!buffer)
throw new Error("can't create buffer");
return buffer;
}
setupAttributes(attributes) {
const { gl, program } = this;
for (const [name, dataBuffer] of Object.entries(attributes)) {
const prefixedName = ClipSpace.prefixName(name, "a_");
const buffer = this.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, dataBuffer, gl.STATIC_DRAW);
const location = gl.getAttribLocation(program, prefixedName);
gl.enableVertexAttribArray(location);
gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 0, 0);
this._attributes[name] = { buffer, location };
}
}
setAttribute(attributeName, dataBuffer) {
const { gl } = this;
gl.bufferData(gl.ARRAY_BUFFER, dataBuffer, gl.STATIC_DRAW);
}
setElements(elements) {
const { gl } = this;
if (!this._elementBuffer) {
const buffer = this.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
this._elementBuffer = buffer;
}
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, elements, gl.STATIC_DRAW);
}
createUniformSetter(name, type, initialValue) {
const { gl, program } = this;
const uniformX = `uniform${type}`;
const location = gl.getUniformLocation(program, name);
const setter = (value) => {
Array.isArray(value) ? gl[uniformX](location, ...value) : gl[uniformX](location, value);
};
if (initialValue)
setter(initialValue);
return setter;
}
setupUniforms(uniforms) {
for (const [name, uniform] of Object.entries(uniforms)) {
const prefixedName = ClipSpace.prefixName(name, "u_");
switch (uniform.type) {
case void 0:
Array.isArray(uniform.value) && uniform.value.forEach(
(member, i) => {
const structName = name;
const prefixedStructName = prefixedName;
for (const [name2, uniform2] of Object.entries(member)) {
const key = `${structName}[${i}].${name2}`;
const prefixedKey = `${prefixedStructName}[${i}].${name2}`;
this._uniforms[key] = this.createUniformSetter(
prefixedKey,
uniform2.type,
uniform2.value
);
}
}
);
break;
default:
this._uniforms[name] = this.createUniformSetter(
prefixedName,
uniform.type,
uniform.value
);
}
}
}
setUniform(uniformName, newValue) {
this._uniforms[uniformName](newValue);
}
delete() {
const { gl } = this;
gl.deleteProgram(this.program);
for (const [, attribute] of Object.entries(this._attributes)) {
this.gl.deleteBuffer(attribute.buffer);
}
}
};
var WaveGradient = class {
constructor(canvas, options) {
const gl = canvas.getContext("webgl2", {
antialias: true,
depth: false,
powerPreference: "low-power"
});
if (!gl)
throw new Error("can't get WebGL2 context");
const {
amplitude = 320,
colors = ["#ef008f", "#6ec3f4", "#7038ff", "#ffba27"],
density = [0.06, 0.16],
fps = 24,
seed = 0,
speed = 1.25,
time = 0,
wireframe = false
} = options ?? {};
const { clientWidth, clientHeight } = canvas;
canvas.width = clientWidth;
canvas.height = clientHeight;
gl.viewport(0, 0, clientWidth, clientHeight);
gl.enable(gl.CULL_FACE);
gl.disable(gl.DITHER);
gl.disable(gl.DEPTH_TEST);
const geometry = ClipSpace.createPlaneGeometry(
clientWidth * density[0],
clientHeight * density[1]
);
const clipSpace = new ClipSpace({
gl,
shaders: [vert, frag],
attributes: { position: geometry.positions },
elements: geometry.indices,
uniforms: {
amplitude: { value: amplitude, type: "1f" },
baseColor: { value: parseRGB(colors[0]), type: "3f" },
realtime: { value: time, type: "1f" },
resolution: { value: [clientWidth, clientHeight], type: "2f" },
seed: { value: seed, type: "1f" },
shadowPower: { value: 6, type: "1f" },
layerCount: { value: colors.length - 1, type: "1i" },
waveLayers: {
value: colors.slice(1).map((color, i, array) => {
const r = (i + 1) / array.length + 1;
return {
noiseCeil: { value: 0.63 + 0.07 * (i + 1), type: "1f" },
noiseFloor: { value: 0.1, type: "1f" },
noiseFlow: { value: 6.5 + 0.3 * (i + 1), type: "1f" },
noiseSeed: { value: seed + 10 * (i + 1), type: "1f" },
noiseSpeed: { value: 11 + 0.3 * (i + 1), type: "1f" },
noiseFreq: { value: [2 + r, 3 + r], type: "2f" },
color: { value: parseRGB(color), type: "3f" }
};
})
}
}
});
this.gl = gl;
this.clipSpace = clipSpace;
this.density = density;
this.speed = speed;
this.frameInterval = 1e3 / fps;
this.lastFrameTime = 0;
this.shouldRender = true;
this.drawMode = wireframe ? this.gl.LINES : this.gl.TRIANGLES;
this.drawCount = geometry.count;
this.time = time;
requestAnimationFrame((now) => {
this.render(now);
});
}
resize() {
const { gl, gl: { canvas }, clipSpace } = this;
const { width, clientWidth, height, clientHeight } = canvas;
const resized = width !== clientWidth || height !== clientHeight;
if (resized) {
canvas.width = clientWidth;
canvas.height = clientHeight;
gl.viewport(0, 0, clientWidth, clientHeight);
this.clipSpace.setUniform("resolution", [clientWidth, clientHeight]);
const geometry = ClipSpace.createPlaneGeometry(
clientWidth * this.density[0],
clientHeight * this.density[1]
);
clipSpace.setAttribute("position", geometry.positions);
clipSpace.setElements(geometry.indices);
this.drawCount = geometry.count;
}
}
render(now) {
if (this.shouldRender) {
requestAnimationFrame((now2) => {
this.render(now2);
});
} else {
return;
}
const delta = now - this.lastFrameTime;
if (delta < this.frameInterval) {
if (Math.random() > 0.75 === true)
this.resize();
return;
}
this.lastFrameTime = now - delta % this.frameInterval;
this.time += Math.min(delta, this.frameInterval) * this.speed;
this.clipSpace.setUniform("realtime", this.time);
this.gl.drawElements(
this.drawMode,
this.drawCount,
this.gl.UNSIGNED_INT,
0
);
}
destroy() {
this.clipSpace.delete();
delete this.gl;
this.shouldRender = false;
}
};
return __toCommonJS(wave_gradient_exports);
})();
window.WaveGradient = WaveGradientModule.WaveGradient;