this logo: 245 b

← UNDERTOW v1

index.html

<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#070511"><title>UNDERTOW — a procedural transmission</title><style>*{box-sizing:border-box}html,body{margin:0;height:100%;overflow:hidden;background:#070511;color:#eee;font:12px ui-monospace,SFMono-Regular,Consolas,monospace}canvas{display:block;width:100%;height:100%}#hud{position:fixed;inset:0;pointer-events:none;padding:30px 36px;display:flex;flex-direction:column;justify-content:space-between}header,footer{display:flex;justify-content:space-between;align-items:center;gap:20px}.mark{letter-spacing:.36em;font-size:11px}.thin{opacity:.45;letter-spacing:.18em;font-size:10px}button{pointer-events:auto;border:1px solid #ffffff35;border-radius:30px;color:#eee;background:#0d092780;padding:11px 18px;font:10px inherit;letter-spacing:.12em;cursor:pointer;backdrop-filter:blur(12px)}button:hover{background:#ffffff20}#title{position:absolute;left:8%;top:38%;transition:opacity 1.5s;max-width:84%}h1{font-family:Arial,Helvetica,sans-serif;font-weight:300;letter-spacing:.13em;font-size:clamp(42px,8.3vw,140px);line-height:1;margin:15px 0 20px;text-shadow:0 3px 40px #0c052f}#subtitle{letter-spacing:.3em;font-size:clamp(9px,1vw,12px);opacity:.7}#line{position:fixed;left:0;bottom:0;height:2px;background:linear-gradient(90deg,#8668fd,#e6d8ff);width:0;box-shadow:0 0 10px #c19cff}#message{position:fixed;inset:0;display:none;place-content:center;text-align:center;padding:30px;background:#090613dd}#message button{margin:20px auto}@media(max-width:600px){#hud{padding:20px}.thin{font-size:8px}.desktop{display:none}#title{left:7%}}</style><canvas id="c" aria-label="An iridescent metallic ocean with continuously morphing sculptures"></canvas><div id="hud"><header><span class="mark">U N D E R T O W</span><button id="sound" aria-label="Enable generated soundtrack">ENABLE SOUND ↗</button></header><div id="title"><span class="thin">A PROCEDURAL TRANSMISSION / 001</span><h1>UNDERTOW</h1><div id="subtitle">EVERY SURFACE REMEMBERS THE SEA.</div></div><footer><div><span id="act" class="mark">I / THE SURFACE</span><div class="thin" style="margin-top:10px">REALTIME · NO ASSETS · NO BOUNDARIES</div></div><div class="thin"><span id="clock">00:00</span><span class="desktop"> &nbsp; / &nbsp; SPACE PAUSE · F FULLSCREEN · ESC STOP</span></div></footer></div><div id="line"></div><div id="message"><h2 id="msg">TRANSMISSION PAUSED</h2><button id="resume">RESUME ↗</button></div><script>
'use strict';
const c=document.getElementById('c'),gl=c.getContext('webgl',{alpha:false,antialias:false,powerPreference:'high-performance'}),duration=112;
const vs=`attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}`;
const fs=`precision highp float;
uniform vec2 R;uniform float T;uniform float quality;
#define PI 3.14159265359
mat2 rot(float a){float s=sin(a),c=cos(a);return mat2(c,-s,s,c);}
float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453123);}
float noise(vec2 p){vec2 i=floor(p),f=fract(p);f=f*f*(3.-2.*f);return mix(mix(hash(i),hash(i+vec2(1,0)),f.x),mix(hash(i+vec2(0,1)),hash(i+1.),f.x),f.y);}
float waves(vec2 p){float t=T*.35;return .22*sin(p.x*.7+t)*cos(p.y*.54-t)+.09*sin(p.x*1.8+p.y*1.3+t*2.)+.035*sin(p.x*4.-p.y*3.+t*3.);}
float torus(vec3 p,vec2 t){return length(vec2(length(p.xz)-t.x,p.y))-t.y;}
float smin(float a,float b,float k){float h=clamp(.5+.5*(b-a)/k,0.,1.);return mix(b,a,h)-k*h*(1.-h);}
float phase(float a,float b){return smoothstep(a,b,T);}
vec2 map(vec3 p){float floorD=p.y+1.9-waves(p.xz);vec3 q=p;q.y-=.7+.22*sin(T*.25);q.xz=rot(T*.13)*q.xz;q.xy=rot(.4*sin(T*.12))*q.xy;float morph=.5+.5*sin(T*.065-1.2);q.xz=rot(q.y*(.12+.3*morph))*q.xz;float rad=2.05+.24*sin(T*.18);float a=torus(q,vec2(rad,.26+.19*morph));vec3 b=q;b.xy=rot(PI*.5)*b.xy;float d=torus(b,vec2(rad*.88,.22));b=q;b.yz=rot(PI*.5)*b.yz;d=smin(d,torus(b,vec2(rad*.72,.19)),.32);d=smin(a,d,.35+.25*morph);float orb=length(q)-(.58+.36*sin(T*.09)*sin(T*.09));d=smin(d,orb,.38);float ring=torus(p-vec3(0,-1.3,0),vec2(3.15+.2*sin(T*.2),.045));d=min(d,ring);if(d<floorD)return vec2(d,1.);return vec2(floorD,0.);}
vec3 normal(vec3 p){vec2 e=vec2(.002,0);return normalize(vec3(map(p+e.xyy).x-map(p-e.xyy).x,map(p+e.yxy).x-map(p-e.yxy).x,map(p+e.yyx).x-map(p-e.yyx).x));}
vec3 sky(vec3 rd){float h=max(rd.y,0.);vec3 col=mix(vec3(.115,.065,.20),vec3(.008,.012,.042),pow(h,.4));float band=exp(-abs(rd.y-.12)*13.);col+=band*vec3(.28,.12,.42);vec3 sun=normalize(vec3(-.65,.22,-.7));float sd=dot(rd,sun);col+=vec3(.8,.6,1.)*pow(max(sd,0.),128.)*.8;col+=vec3(.5,.2,1.)*pow(max(sd,0.),12.)*.2;float arc=abs(length(rd.xy-vec2(-.3,.18))-.42);col+=vec3(.38,.18,.7)*exp(-arc*240.)*.3;float stars=pow(hash(floor(rd.xz/(abs(rd.y)+.15)*500.)),90.)*smoothstep(.05,.3,rd.y);col+=stars*.5;return col;}
vec3 env(vec3 r){vec3 col=sky(r);float stripes=pow(max(0.,sin(r.x*8.+r.y*5.+T*.08)),18.);col+=stripes*vec3(.8,.75,.9)*smoothstep(-.5,.6,r.y);float panel=exp(-pow((r.y-.45)*8.,2.))*pow(max(0.,cos(r.x*3.)),8.);col+=panel*vec3(1.3,1.2,1.4);return col;}
float shadow(vec3 p,vec3 l){float s=1.,t=.04;for(int i=0;i<18;i++){float h=map(p+l*t).x;s=min(s,10.*h/t);t+=clamp(h,.04,.4);}return clamp(s,.15,1.);}
void main(){vec2 uv=(gl_FragCoord.xy*2.-R)/R.y;float time=T;float orbit=-.5+T*.075;float plunge=phase(24.,48.)-phase(75.,103.);float dist=8.2-2.0*plunge+.55*sin(T*.075);vec3 ro=vec3(sin(orbit)*dist,2.4-1.8*plunge+.4*sin(T*.12),cos(orbit)*dist);vec3 target=vec3(0,.1+.4*sin(T*.06),0);vec3 ww=normalize(target-ro),uu=normalize(cross(ww,vec3(.08*sin(T*.08),1,0))),vv=cross(uu,ww);vec3 rd=normalize(uv.x*uu+uv.y*vv+1.65*ww);vec3 col=sky(rd);float t=.1;vec2 hit;bool ok=false;for(int i=0;i<100;i++){vec3 p=ro+rd*t;hit=map(p);if(hit.x<.0025*t){ok=true;break;}t+=hit.x*.72;if(t>45.)break;}
if(ok){vec3 p=ro+rd*t,n=normal(p),r=reflect(rd,n);vec3 light=normalize(vec3(-.5,.8,-.6));float diff=max(dot(n,light),0.);float fres=pow(1.-max(dot(-rd,n),0.),4.);float ao=clamp(map(p+n*.25).x/.25,.2,1.);float sh=shadow(p+n*.02,light);vec3 irid=.55+.45*cos(vec3(0,2,4)+dot(n,-rd)*7.+p.y*.6+T*.07);vec3 reflected=env(r);if(hit.y>.5){col=reflected*mix(vec3(.7,.65,.86),irid,.3);col+=pow(max(dot(reflect(-light,n),-rd),0.),60.)*vec3(1.4,1.2,1.8);col*=.65+.35*ao;col+=vec3(.25,.1,.5)*fres;col+=diff*sh*vec3(.13,.09,.2);}else{vec3 pp=p;pp.y=-pp.y-3.8;float mist=exp(-length(p.xz)*.14);col=reflected*vec3(.32,.24,.48);col+=vec3(.14,.05,.25)*mist;float rip=sin(length(p.xz)*9.-T*2.5);col+=pow(max(rip,0.),24.)*.05*vec3(.5,.3,1.)*mist;col=mix(col,reflected,.35+fres*.4);}
col=mix(col,sky(rd),1.-exp(-t*.018));}
// Volumetric orbital dust, deliberately sparse and depth layered.
for(int j=0;j<3;j++){float z=float(j)+1.;vec2 st=uv*(22.+z*13.);st.y+=T*(.06+z*.04);st.x+=sin(T*.1+z)*.5;vec2 id=floor(st),f=fract(st)-.5;float seed=hash(id+z*53.);float spark=exp(-dot(f,f)*800.)*step(.985,seed);col+=spark*vec3(.65,.42,1.)*(.4+.6*sin(seed*30.+T));}
float vign=1.-.25*dot(uv*.55,uv*.55);col*=vign;float intro=smoothstep(0.,2.,T);float ending=1.-smoothstep(107.,112.,T);col*=intro*ending;col=col/(1.+col);col=pow(max(col,0.),vec3(.4545));col+= (hash(gl_FragCoord.xy+fract(T)*100.)-.5)/255.;gl_FragColor=vec4(col,1.);}`;
let program,uR,uT,failed=false;
function fail(s){failed=true;document.getElementById('message').style.display='grid';document.getElementById('msg').textContent=s;document.getElementById('resume').style.display='none'}
function shader(type,src){let s=gl.createShader(type);gl.shaderSource(s,src);gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))throw Error(gl.getShaderInfoLog(s));return s}
try{if(!gl)throw Error('WebGL is not available. Please enable hardware acceleration.');program=gl.createProgram();gl.attachShader(program,shader(gl.VERTEX_SHADER,vs));gl.attachShader(program,shader(gl.FRAGMENT_SHADER,fs));gl.linkProgram(program);if(!gl.getProgramParameter(program,gl.LINK_STATUS))throw Error(gl.getProgramInfoLog(program));gl.useProgram(program);let b=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,b);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,3,-1,-1,3]),gl.STATIC_DRAW);let a=gl.getAttribLocation(program,'p');gl.enableVertexAttribArray(a);gl.vertexAttribPointer(a,2,gl.FLOAT,false,0,0);uR=gl.getUniformLocation(program,'R');uT=gl.getUniformLocation(program,'T');}catch(e){fail(e.message);console.error(e)}
let start=performance.now(),pauseAt=0,paused=false,stopped=false,scale=.75,last=0,avg=16,frames=0,ctx,master,active=[],soundOn=false,nextNote=0;
const title=document.getElementById('title'),act=document.getElementById('act'),clockEl=document.getElementById('clock'),message=document.getElementById('message');
function resize(){let d=Math.min(devicePixelRatio,1.5)*scale;c.width=Math.max(1,Math.floor(innerWidth*d));c.height=Math.max(1,Math.floor(innerHeight*d));if(gl)gl.viewport(0,0,c.width,c.height)}addEventListener('resize',resize);resize();
function elapsed(){return (paused?pauseAt:performance.now()-start)/1000}
function tone(freq,when,len,amp,type,slide){let o=ctx.createOscillator(),g=ctx.createGain(),f=ctx.createBiquadFilter();o.type=type;o.frequency.setValueAtTime(freq,when);if(slide)o.frequency.exponentialRampToValueAtTime(freq*slide,when+len*.7);f.type='lowpass';f.frequency.setValueAtTime(900+amp*9000,when);g.gain.setValueAtTime(0,when);g.gain.linearRampToValueAtTime(amp,when+.025);g.gain.exponentialRampToValueAtTime(.0001,when+len);o.connect(f);f.connect(g);g.connect(master);g.connect(delay);o.start(when);o.stop(when+len+.02);active.push(o);o.onended=()=>{active=active.filter(x=>x!==o);o.disconnect();g.disconnect();f.disconnect()}}
let delay;function audio(){if(!ctx){ctx=new (window.AudioContext||window.webkitAudioContext)();master=ctx.createGain();master.gain.value=.65;let compressor=ctx.createDynamicsCompressor();master.connect(compressor);compressor.connect(ctx.destination);delay=ctx.createDelay(1);delay.delayTime.value=.375;let feedback=ctx.createGain();feedback.gain.value=.3;delay.connect(feedback);feedback.connect(delay);feedback.connect(master)}ctx.resume();soundOn=!soundOn;master.gain.setTargetAtTime(soundOn?.65:0,ctx.currentTime,.12);document.getElementById('sound').textContent=soundOn?'SOUND ON · MUTE':'ENABLE SOUND ↗';nextNote=Math.floor(elapsed()/.375)}
document.getElementById('sound').onclick=audio;
function score(t){if(!soundOn||!ctx||paused)return;const step=.375,n=Math.floor(t/step);if(n<nextNote)return;nextNote=n+1;let bar=Math.floor(n/16),notes=[0,3,7,10,14,10,7,3],roots=[36,32,39,34],root=roots[Math.floor(bar/2)%4],midi=root+24+notes[n%8];let intensity=Math.sin(Math.min(t/duration,1)*Math.PI);let when=ctx.currentTime+.01;if(n%2===0)tone(440*Math.pow(2,(midi-69)/12),when,.65,.055+intensity*.03,'sine',n%8===0?1.5:.997);if(n%8===0){tone(440*Math.pow(2,(root-69)/12),when,2.7,.11,'triangle',1.001);tone(440*Math.pow(2,(root+19-69)/12),when,2.3,.035,'sine',1.01)}if(t>20&&t<96&&n%4===0)tone(95,when,.22,.18,'sine',.32);if(t>40&&t<88&&n%2)tone(6000,when,.045,.012,'triangle',.5)}
function pause(stop=false){if(!paused){pauseAt=performance.now()-start;paused=true;stopped=stop;if(stop){for(const o of active){try{o.stop()}catch(e){}}active=[]}message.style.display='grid';document.getElementById('msg').textContent=stop?'TRANSMISSION STOPPED':'TRANSMISSION PAUSED';document.getElementById('resume').textContent=stop?'REPLAY ↗':'RESUME ↗';if(ctx)ctx.suspend()}else{start=performance.now()-(stopped?0:pauseAt);paused=false;message.style.display='none';if(stopped){nextNote=0;title.style.opacity=1}stopped=false;if(ctx&&soundOn)ctx.resume()}}
document.getElementById('resume').onclick=()=>pause();addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();pause()}if(e.key==='Escape'&&!paused)pause(true);if(e.key.toLowerCase()==='f'){if(!document.fullscreenElement)document.documentElement.requestFullscreen?.().catch(()=>{});else document.exitFullscreen?.()}if(e.key.toLowerCase()==='m')audio()});document.addEventListener('visibilitychange',()=>{if(document.hidden&&!paused)pause()});c.addEventListener('webglcontextlost',e=>{e.preventDefault();fail('Graphics context lost. Reload to restart.')});
const labels=['I / THE SURFACE','II / LIQUID MEMORY','III / BELOW THE LIGHT','IV / THE RETURN'];
function frame(now){if(failed)return;requestAnimationFrame(frame);if(paused)return;let t=elapsed();if(t>=duration){pause(true);document.getElementById('msg').textContent='EVERY SURFACE REMEMBERS.';return}let dt=now-last;last=now;if(dt>0&&dt<200)avg=avg*.96+dt*.04;if(++frames%180===0){if(avg>30&&scale>.4){scale-=.08;resize()}else if(avg<18&&scale<.9){scale+=.04;resize()}}gl.uniform2f(uR,c.width,c.height);gl.uniform1f(uT,t);gl.drawArrays(gl.TRIANGLES,0,3);title.style.opacity=t<9?1:t>99?(t-99)/5:0;document.getElementById('subtitle').textContent=t>99?'112 SECONDS / ONE EQUATION / NO ASSETS':'EVERY SURFACE REMEMBERS THE SEA.';act.textContent=labels[Math.min(3,Math.floor(t/28))];clockEl.textContent='0'+Math.floor(t/60)+':'+String(Math.floor(t%60)).padStart(2,'0');document.getElementById('line').style.width=t/duration*100+'%';score(t)}if(!failed)requestAnimationFrame(frame);
// Read-only deterministic seek hook for renderer verification.
window.demo={seek(t){start=performance.now()-t*1000;nextNote=Math.floor(t/.375)},get status(){return {time:elapsed(),paused,scale,width:c.width,height:c.height,audio:soundOn}}};
</script></html>