Created
March 11, 2026 22:19
-
-
Save KaushikShresth07/dcd1014966ad97bbf39c8b55fbb0289a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class OrbRenderer { | |
| constructor(container, opts = {}) { | |
| this.container = container; | |
| this.hue = opts.hue ?? 0; | |
| this.hoverIntensity = opts.hoverIntensity ?? 0.2; | |
| this.bgColor = opts.backgroundColor ?? [0.02, 0.02, 0.06]; | |
| this.targetHover = 0; | |
| this.currentHover = 0; | |
| this.currentRot = 0; | |
| this.lastTs = 0; | |
| this.canvas = document.createElement('canvas'); | |
| this.canvas.style.width = '100%'; | |
| this.canvas.style.height = '100%'; | |
| this.container.appendChild(this.canvas); | |
| this.gl = this.canvas.getContext('webgl', { alpha: true, premultipliedAlpha: false, antialias: false }); | |
| if (!this.gl) { console.warn('WebGL not available'); return; } | |
| this._build(); | |
| this._resize(); | |
| this._onResize = this._resize.bind(this); | |
| window.addEventListener('resize', this._onResize); | |
| this._raf = requestAnimationFrame(this._loop.bind(this)); | |
| } | |
| static VERT = ` | |
| precision highp float; | |
| attribute vec2 position; | |
| attribute vec2 uv; | |
| varying vec2 vUv; | |
| void main(){ vUv=uv; gl_Position=vec4(position,0.0,1.0); }`; | |
| static FRAG = ` | |
| precision highp float; | |
| uniform float iTime; | |
| uniform vec3 iResolution; | |
| uniform float hue; | |
| uniform float hover; | |
| uniform float rot; | |
| uniform float hoverIntensity; | |
| uniform vec3 backgroundColor; | |
| varying vec2 vUv; | |
| vec3 rgb2yiq(vec3 c){float y=dot(c,vec3(.299,.587,.114));float i=dot(c,vec3(.596,-.274,-.322));float q=dot(c,vec3(.211,-.523,.312));return vec3(y,i,q);} | |
| vec3 yiq2rgb(vec3 c){return vec3(c.x+.956*c.y+.621*c.z,c.x-.272*c.y-.647*c.z,c.x-1.106*c.y+1.703*c.z);} | |
| vec3 adjustHue(vec3 color,float hueDeg){float h=hueDeg*3.14159265/180.0;vec3 yiq=rgb2yiq(color);float cosA=cos(h);float sinA=sin(h);float i2=yiq.y*cosA-yiq.z*sinA;float q2=yiq.y*sinA+yiq.z*cosA;yiq.y=i2;yiq.z=q2;return yiq2rgb(yiq);} | |
| vec3 hash33(vec3 p3){p3=fract(p3*vec3(.1031,.11369,.13787));p3+=dot(p3,p3.yxz+19.19);return -1.0+2.0*fract(vec3(p3.x+p3.y,p3.x+p3.z,p3.y+p3.z)*p3.zyx);} | |
| float snoise3(vec3 p){const float K1=.333333333;const float K2=.166666667;vec3 i=floor(p+(p.x+p.y+p.z)*K1);vec3 d0=p-(i-(i.x+i.y+i.z)*K2);vec3 e=step(vec3(0.0),d0-d0.yzx);vec3 i1=e*(1.0-e.zxy);vec3 i2=1.0-e.zxy*(1.0-e);vec3 d1=d0-(i1-K2);vec3 d2=d0-(i2-K1);vec3 d3=d0-0.5;vec4 h=max(0.6-vec4(dot(d0,d0),dot(d1,d1),dot(d2,d2),dot(d3,d3)),0.0);vec4 n=h*h*h*h*vec4(dot(d0,hash33(i)),dot(d1,hash33(i+i1)),dot(d2,hash33(i+i2)),dot(d3,hash33(i+1.0)));return dot(vec4(31.316),n);} | |
| vec4 extractAlpha(vec3 c){float a=max(max(c.r,c.g),c.b);return vec4(c/(a+1e-5),a);} | |
| const vec3 baseColor1=vec3(.611765,.262745,.996078); | |
| const vec3 baseColor2=vec3(.298039,.760784,.913725); | |
| const vec3 baseColor3=vec3(.062745,.078431,.600000); | |
| const float innerRadius=0.6; | |
| const float noiseScale=0.65; | |
| float light1(float i,float a,float d){return i/(1.0+d*a);} | |
| float light2(float i,float a,float d){return i/(1.0+d*d*a);} | |
| vec4 draw(vec2 uv){ | |
| vec3 c1=adjustHue(baseColor1,hue);vec3 c2=adjustHue(baseColor2,hue);vec3 c3=adjustHue(baseColor3,hue); | |
| float ang=atan(uv.y,uv.x);float len=length(uv);float invLen=len>0.0?1.0/len:0.0; | |
| float bgLum=dot(backgroundColor,vec3(.299,.587,.114)); | |
| float n0=snoise3(vec3(uv*noiseScale,iTime*0.5))*0.5+0.5; | |
| float r0=mix(mix(innerRadius,1.0,0.4),mix(innerRadius,1.0,0.6),n0); | |
| float d0=distance(uv,(r0*invLen)*uv); | |
| float v0=light1(1.0,10.0,d0); | |
| v0*=smoothstep(r0*1.05,r0,len); | |
| float innerFade=smoothstep(r0*0.8,r0*0.95,len); | |
| v0*=mix(innerFade,1.0,bgLum*0.7); | |
| float cl=cos(ang+iTime*2.0)*0.5+0.5; | |
| float a2=iTime*-1.0;vec2 pos=vec2(cos(a2),sin(a2))*r0;float d=distance(uv,pos); | |
| float v1=light2(1.5,5.0,d);v1*=light1(1.0,50.0,d0); | |
| float v2=smoothstep(1.0,mix(innerRadius,1.0,n0*0.5),len); | |
| float v3=smoothstep(innerRadius,mix(innerRadius,1.0,0.5),len); | |
| vec3 colBase=mix(c1,c2,cl); | |
| float fadeAmt=mix(1.0,0.1,bgLum); | |
| vec3 darkCol=mix(c3,colBase,v0);darkCol=(darkCol+v1)*v2*v3;darkCol=clamp(darkCol,0.0,1.0); | |
| vec3 lightCol=(colBase+v1)*mix(1.0,v2*v3,fadeAmt);lightCol=mix(backgroundColor,lightCol,v0);lightCol=clamp(lightCol,0.0,1.0); | |
| vec3 fc=mix(darkCol,lightCol,bgLum); | |
| return extractAlpha(fc); | |
| } | |
| vec4 mainImage(vec2 fragCoord){ | |
| vec2 center=iResolution.xy*0.5;float sz=min(iResolution.x,iResolution.y); | |
| vec2 uv=(fragCoord-center)/sz*2.0; | |
| float s2=sin(rot);float c2=cos(rot);uv=vec2(c2*uv.x-s2*uv.y,s2*uv.x+c2*uv.y); | |
| uv.x+=hover*hoverIntensity*0.1*sin(uv.y*10.0+iTime); | |
| uv.y+=hover*hoverIntensity*0.1*sin(uv.x*10.0+iTime); | |
| return draw(uv); | |
| } | |
| void main(){ | |
| vec2 fc=vUv*iResolution.xy;vec4 col=mainImage(fc); | |
| gl_FragColor=vec4(col.rgb*col.a,col.a); | |
| }`; | |
| _compile(type, src) { | |
| const gl = this.gl; | |
| const s = gl.createShader(type); | |
| gl.shaderSource(s, src); | |
| gl.compileShader(s); | |
| if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { | |
| console.error('Shader compile error:', gl.getShaderInfoLog(s)); | |
| gl.deleteShader(s); | |
| return null; | |
| } | |
| return s; | |
| } | |
| _build() { | |
| const gl = this.gl; | |
| const vs = this._compile(gl.VERTEX_SHADER, OrbRenderer.VERT); | |
| const fs = this._compile(gl.FRAGMENT_SHADER, OrbRenderer.FRAG); | |
| if (!vs || !fs) return; | |
| this.pgm = gl.createProgram(); | |
| gl.attachShader(this.pgm, vs); | |
| gl.attachShader(this.pgm, fs); | |
| gl.linkProgram(this.pgm); | |
| if (!gl.getProgramParameter(this.pgm, gl.LINK_STATUS)) { | |
| console.error('Program link error:', gl.getProgramInfoLog(this.pgm)); | |
| return; | |
| } | |
| gl.useProgram(this.pgm); | |
| const posLoc = gl.getAttribLocation(this.pgm, 'position'); | |
| const uvLoc = gl.getAttribLocation(this.pgm, 'uv'); | |
| const posBuf = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, posBuf); | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW); | |
| gl.enableVertexAttribArray(posLoc); | |
| gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0); | |
| const uvBuf = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, uvBuf); | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0, 2,0, 0,2]), gl.STATIC_DRAW); | |
| gl.enableVertexAttribArray(uvLoc); | |
| gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 0, 0); | |
| this.u = {}; | |
| ['iTime','iResolution','hue','hover','rot','hoverIntensity','backgroundColor'].forEach(name => { | |
| this.u[name] = gl.getUniformLocation(this.pgm, name); | |
| }); | |
| gl.enable(gl.BLEND); | |
| gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); | |
| gl.clearColor(0,0,0,0); | |
| } | |
| _resize() { | |
| const dpr = window.devicePixelRatio || 1; | |
| const w = this.container.clientWidth; | |
| const h = this.container.clientHeight; | |
| this.canvas.width = w * dpr; | |
| this.canvas.height = h * dpr; | |
| if (this.gl) this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); | |
| } | |
| _loop(ts) { | |
| this._raf = requestAnimationFrame(this._loop.bind(this)); | |
| if (!this.pgm) return; | |
| const gl = this.gl; | |
| const t = ts * 0.001; | |
| const dt = this.lastTs ? t - this.lastTs : 0.016; | |
| this.lastTs = t; | |
| this.currentHover += (this.targetHover - this.currentHover) * Math.min(dt * 4, 1); | |
| if (this.currentHover > 0.5) this.currentRot += dt * 0.3; | |
| gl.clear(gl.COLOR_BUFFER_BIT); | |
| gl.useProgram(this.pgm); | |
| gl.uniform1f(this.u.iTime, t); | |
| gl.uniform3f(this.u.iResolution, this.canvas.width, this.canvas.height, this.canvas.width / this.canvas.height); | |
| gl.uniform1f(this.u.hue, this.hue); | |
| gl.uniform1f(this.u.hover, this.currentHover); | |
| gl.uniform1f(this.u.rot, this.currentRot); | |
| gl.uniform1f(this.u.hoverIntensity, this.hoverIntensity); | |
| gl.uniform3f(this.u.backgroundColor, this.bgColor[0], this.bgColor[1], this.bgColor[2]); | |
| gl.drawArrays(gl.TRIANGLES, 0, 3); | |
| } | |
| setActive(active) { | |
| this.targetHover = active ? 1.0 : 0.0; | |
| const ctn = this.container; | |
| if (active) ctn.classList.add('active'); | |
| else ctn.classList.remove('active'); | |
| } | |
| destroy() { | |
| cancelAnimationFrame(this._raf); | |
| window.removeEventListener('resize', this._onResize); | |
| if (this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas); | |
| const ext = this.gl.getExtension('WEBGL_lose_context'); | |
| if (ext) ext.loseContext(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment