| <!doctype html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width,initial-scale=1"> |
| <title>Sentinel Demo — Single Timeline Color Cycle</title> |
|
|
| <style> |
| html,body{ |
| margin:0; |
| height:100%; |
| background:#000; |
| overflow:hidden; |
| } |
| canvas{ |
| width:100%; |
| height:100%; |
| display:block; |
| } |
| </style> |
| </head> |
|
|
| <body> |
| <canvas id="gl"></canvas> |
|
|
| <script> |
| (() => { |
| |
| const canvas=document.getElementById("gl"); |
| const gl=canvas.getContext("webgl",{antialias:true}); |
| |
| if(!gl){ |
| document.body.innerHTML="WebGL not supported"; |
| return; |
| } |
| |
| function resize(){ |
| canvas.width=window.innerWidth; |
| canvas.height=window.innerHeight; |
| gl.viewport(0,0,canvas.width,canvas.height); |
| } |
| resize(); |
| window.addEventListener("resize",resize); |
| |
| function compile(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) || "shader compile failed"); |
| } |
| return s; |
| } |
| |
| function program(v,f){ |
| const p=gl.createProgram(); |
| gl.attachShader(p,compile(gl.VERTEX_SHADER,v)); |
| gl.attachShader(p,compile(gl.FRAGMENT_SHADER,f)); |
| gl.linkProgram(p); |
| if(!gl.getProgramParameter(p, gl.LINK_STATUS)){ |
| throw new Error(gl.getProgramInfoLog(p) || "program link failed"); |
| } |
| return p; |
| } |
| |
| const quad=gl.createBuffer(); |
| gl.bindBuffer(gl.ARRAY_BUFFER,quad); |
| gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([ |
| -1,-1, 1,-1, -1, 1, |
| -1, 1, 1,-1, 1, 1 |
| ]),gl.STATIC_DRAW); |
| |
| const VS=` |
| attribute vec2 a; |
| varying vec2 v; |
| void main(){ |
| v=a*0.5+0.5; |
| gl_Position=vec4(a,0.0,1.0); |
| } |
| `; |
| |
| |
| |
| |
| |
| |
| const FS=` |
| precision highp float; |
| |
| varying vec2 v; |
| uniform vec2 r; |
| uniform float t; |
| |
| /* ADD ONLY */ |
| uniform vec3 uCol; |
| |
| float ring(vec2 p,float rad){ |
| float d=length(p); |
| return smoothstep(0.02,0.0,abs(d-rad)); |
| } |
| |
| void main(){ |
| |
| vec2 uv=v*2.0-1.0; |
| uv.x*=r.x/r.y; |
| |
| float time=t*0.8; |
| |
| float R=0.35+sin(time)*0.01; |
| |
| float phase=mod(floor(t/5.0),5.0); |
| |
| if(phase==1.0){ |
| R+=sin(atan(uv.y,uv.x)*6.0+time)*0.005; |
| } |
| |
| if(phase==2.0){ |
| R+=sin(length(uv)*40.0-time*2.0)*0.002; |
| } |
| |
| if(phase==3.0){ |
| R+=sin(time*6.0)*0.015; |
| } |
| |
| if(phase==4.0){ |
| R+=sin(atan(uv.y,uv.x)*80.0+time*4.0)*0.008; |
| } |
| |
| float base=ring(uv,R); |
| float glow=exp(-8.0*abs(length(uv)-R)); |
| |
| /* CHANGED: from vec3 red to uCol */ |
| vec3 color = |
| uCol*(base*1.4) + |
| uCol*(glow*0.8); |
| |
| gl_FragColor=vec4(color,1.0); |
| |
| } |
| `; |
| |
| const prog=program(VS,FS); |
| const uRes=gl.getUniformLocation(prog,"r"); |
| const uTime=gl.getUniformLocation(prog,"t"); |
| |
| const uCol = gl.getUniformLocation(prog,"uCol"); |
| |
| |
| |
| const LINE_VS=` |
| attribute vec2 a; |
| void main(){ |
| gl_Position=vec4(a,0.0,1.0); |
| } |
| `; |
| |
| const LINE_FS=` |
| precision highp float; |
| uniform vec3 uColor; |
| uniform float alpha; |
| void main(){ |
| gl_FragColor=vec4(uColor,alpha); |
| } |
| `; |
| |
| const lineProg=program(LINE_VS,LINE_FS); |
| const lineA=gl.getAttribLocation(lineProg,"a"); |
| const lineColor=gl.getUniformLocation(lineProg,"uColor"); |
| const lineAlpha=gl.getUniformLocation(lineProg,"alpha"); |
| const lineBuf=gl.createBuffer(); |
| |
| |
| |
| const TIMELINE_COLORS = [ |
| { name:"red", rgb:[1.00,0.00,0.00] }, |
| { name:"blue", rgb:[0.10,0.55,1.00] }, |
| { name:"green",rgb:[0.00,1.00,0.35] }, |
| { name:"gold", rgb:[1.00,0.78,0.12] } |
| ]; |
| |
| const PHASE_SECONDS = 5; |
| const PHASE_COUNT = 5; |
| const DEMO_CYCLE_SECONDS = PHASE_SECONDS * PHASE_COUNT; |
| |
| function activeTimelineColor(time){ |
| const completedCycles = Math.floor(time / DEMO_CYCLE_SECONDS); |
| const idx = completedCycles % TIMELINE_COLORS.length; |
| return TIMELINE_COLORS[idx].rgb; |
| } |
| |
| |
| |
| function pxToClip(x,y){ |
| return [ |
| (x/canvas.width)*2-1, |
| 1-(y/canvas.height)*2 |
| ]; |
| } |
| |
| function useLine(color, alpha){ |
| gl.useProgram(lineProg); |
| gl.bindBuffer(gl.ARRAY_BUFFER, lineBuf); |
| gl.enableVertexAttribArray(lineA); |
| gl.vertexAttribPointer(lineA,2,gl.FLOAT,false,0,0); |
| gl.uniform3f(lineColor, color[0], color[1], color[2]); |
| gl.uniform1f(lineAlpha, alpha); |
| } |
| |
| function drawLine(x1,y1,x2,y2,color,alpha){ |
| const p1=pxToClip(x1,y1); |
| const p2=pxToClip(x2,y2); |
| useLine(color, alpha); |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([p1[0],p1[1], p2[0],p2[1]]), gl.STATIC_DRAW); |
| gl.drawArrays(gl.LINES, 0, 2); |
| } |
| |
| |
| |
| function drawScanArm(time, color){ |
| |
| const phase = Math.floor(time/PHASE_SECONDS) % PHASE_COUNT; |
| if(phase !== 4) return; |
| |
| const angle=time*0.7; |
| const cx=canvas.width/2; |
| const cy=canvas.height/2; |
| const len=Math.max(canvas.width,canvas.height)*1.6; |
| |
| gl.enable(gl.BLEND); |
| gl.blendFunc(gl.SRC_ALPHA, gl.ONE); |
| |
| const tailSteps=18; |
| for(let i=0;i<tailSteps;i++){ |
| const k=i/(tailSteps-1); |
| const a=1.0-k; |
| const tailAngle=angle - k*0.35; |
| |
| const tx=cx+Math.cos(tailAngle)*len; |
| const ty=cy+Math.sin(tailAngle)*len; |
| |
| drawLine(cx,cy,tx,ty,color, 0.02 + a*0.08); |
| } |
| |
| const x=cx+Math.cos(angle)*len; |
| const y=cy+Math.sin(angle)*len; |
| |
| drawLine(cx,cy,x,y,color,0.36); |
| |
| gl.disable(gl.BLEND); |
| } |
| |
| |
| |
| function drawTimeline(time, color){ |
| const padX = Math.max(24, canvas.width*0.08); |
| const xL = padX; |
| const xR = canvas.width - padX; |
| const w = xR - xL; |
| |
| const y = canvas.height - Math.max(110, canvas.height*0.16); |
| |
| gl.enable(gl.BLEND); |
| gl.blendFunc(gl.SRC_ALPHA, gl.ONE); |
| |
| |
| drawLine(xL,y,xR,y,color,0.10); |
| |
| |
| const ticks=18; |
| for(let i=0;i<=ticks;i++){ |
| const x=xL+(i/ticks)*w; |
| const h=(i%3===0)?8:4; |
| drawLine(x,y-h,x,y+h,color,0.08); |
| } |
| |
| |
| const cycleT = (time % DEMO_CYCLE_SECONDS) / DEMO_CYCLE_SECONDS; |
| const eased = 0.5 - 0.5*Math.cos(cycleT * Math.PI * 2.0); |
| const px = xL + eased*w; |
| |
| drawLine(px-14,y,px+14,y,color,0.55); |
| drawLine(px,y-10,px,y+10,color,0.35); |
| |
| |
| for(let i=1;i<=10;i++){ |
| const tx = px - i*10; |
| if(tx < xL) break; |
| drawLine(tx-4,y,tx+4,y,color,0.14*(1-i/11)); |
| } |
| |
| gl.disable(gl.BLEND); |
| } |
| |
| |
| let aLoc=-1; |
| function bindHalo(){ |
| gl.useProgram(prog); |
| gl.bindBuffer(gl.ARRAY_BUFFER,quad); |
| aLoc = gl.getAttribLocation(prog,"a"); |
| gl.enableVertexAttribArray(aLoc); |
| gl.vertexAttribPointer(aLoc,2,gl.FLOAT,false,0,0); |
| } |
| |
| const start=performance.now(); |
| |
| function draw(){ |
| const time=(performance.now()-start)/1000; |
| |
| |
| const color = activeTimelineColor(time); |
| |
| gl.clearColor(0,0,0,1); |
| gl.clear(gl.COLOR_BUFFER_BIT); |
| |
| bindHalo(); |
| |
| gl.uniform2f(uRes, canvas.width, canvas.height); |
| gl.uniform1f(uTime, time); |
| |
| |
| gl.uniform3f(uCol, color[0], color[1], color[2]); |
| |
| gl.drawArrays(gl.TRIANGLES,0,6); |
| |
| |
| drawScanArm(time, color); |
| drawTimeline(time, color); |
| |
| requestAnimationFrame(draw); |
| } |
| |
| draw(); |
| |
| })(); |
| </script> |
| </body> |
| </html> |