← TRIPTYCH v1
index.html
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>TRIPTYCH</title><style>html,body{margin:0;height:100%;background:#000;overflow:hidden;font:14px monospace;color:#9ab}canvas{position:fixed;left:0;top:0;width:100%;height:100%}#s{position:fixed;left:0;top:0;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:radial-gradient(#213,#000)}h1{font-size:9vmin;letter-spacing:2vmin;margin:0 0 0 2vmin;color:#fff;text-shadow:0 0 30px #f3a}button{font:inherit;font-size:20px;letter-spacing:6px;margin:5vmin;padding:14px 40px;background:0;color:#fff;border:1px solid #f3a;cursor:pointer}button:hover{background:#f3a}#t{position:fixed;left:5vmin;bottom:5vmin;color:#fff;font-size:3vmin;letter-spacing:1.2vmin;opacity:0;transition:opacity .6s;text-shadow:0 0 12px #f3a}</style><canvas id=c></canvas><div id=t></div><div id=s><h1>TRIPTYCH</h1><button id=b>START</button><p>three scenes · one minute · F or double-click for fullscreen</div><script id=f type=x>
precision highp float;
uniform vec4 U,V;
vec4 o;
mat2 R(float a){float c=cos(a),s=sin(a);return mat2(c,s,-s,c);}
vec3 P(float t){return .5+.5*cos(6.283*(t+vec3(0,.33,.67)));}
float H(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
float G(vec2 p){
vec2 i=floor(p),f=p-i;
return mix(mix(H(i),H(i+vec2(1,0)),f.x),mix(H(i+vec2(0,1)),H(i+1.),f.x),f.y);
}
float M(vec3 p){
if(U.z<.5){
p.xy*=R(p.z*.15);
float g=dot(sin(p*3.),cos(p.zxy*3.));
g+=.12*dot(sin(p*11.),cos(p.zxy*11.));
o.x=g;
return max((abs(g)-.2)*.17,1.-length(p.xy+vec2(sin(p.z*.5),cos(p.z*.4))*.3));
}
if(U.z<1.5){
float s=1.,k;
o=vec4(9);
for(int i=0;i<8;i++){
p=2.*fract(.5*p+.5)-1.;
k=dot(p,p);
o=min(o,vec4(abs(p),k));
k=(1.2+.3*sin(U.x*.2)+.04*U.w)/k;
p*=k;s*=k;
}
return .25*abs(p.y)/s;
}
return (p.y-G(p.xz*.5)*smoothstep(1.,7.,abs(p.x))*(3.5+U.w))*.4;
}
void main(){
vec2 u=(gl_FragCoord.xy*2.-V.xy)/V.y,e;
u*=1.-.07*V.w;
float T=U.x,t=0.,d,i=0.,z=U.z,F=50.,l;
vec3 ro,ta,c=vec3(0),p,n,w,x,rd,q;
if(z<.5){ro=vec3(0,0,T*2.5);ta=ro+vec3(sin(T*.5)*.4,cos(T*.4)*.4,1);u*=R(sin(T*.3)*.6);F=30.;}
else if(z<1.5){T*=.25;ro=vec3(2.8*cos(.1+.33*T),.4+.2*cos(.37*T),2.8*cos(.5+.35*T));ta=vec3(1.9*cos(1.2+.41*T),.4+.1*cos(.27*T),1.9*cos(2.+.38*T));F=20.;}
else{ro=vec3(sin(T*.5)*.5,1.+.3*sin(T*.7),T*8.);ta=ro+vec3(0,-.02,1);}
w=normalize(ta-ro);x=normalize(cross(vec3(0,1,0),w));
rd=normalize(u.x*x+u.y*cross(w,x)+1.4*w);
for(int j=0;j<96;j++){p=ro+rd*t;d=M(p);if(d<.001*t||t>F)break;t+=d;i++;}
vec4 b=o;
i/=96.;
if(t<F){
e=vec2(.001*t+.0003,0);
n=normalize(vec3(M(p+e.xyy),M(p+e.yxy),M(p+e.yyx))-d);
l=max(dot(n,-rd),0.);
if(z<.5){
q=P(p.z*.03+b.x*.25+.55);
c=q*q*(l*l*.7+.03)+pow(l,40.)*.5;
c+=P(p.z*.03+.2)*pow(.5+.5*sin(p.z*.8-U.y*3.1416),30.)*(1.+4.*U.w)*smoothstep(.2,.0,abs(b.x)-.2+.15);
c=c*exp(-t*.16)+vec3(.05,.2,.5)*i*i;
}else if(z<1.5){
q=mix(vec3(.9,.95,1),vec3(.05,.5,.9),clamp(6.*b.y,0.,1.));
q=mix(q,vec3(1,.15,.5),pow(clamp(1.-2.*b.z,0.,1.),8.));
l=.08+.9*max(dot(n,normalize(vec3(.6,.7,-.4))),0.)+.2*l;
c=q*l*pow(clamp(b.w*2.,0.,1.),2.)*(1.+.6*U.w);
c=mix(vec3(.02,.03,.08),c,exp(-t*.5))+vec3(.9,.2,.5)*i*i*i*.5;
}else{
e=abs(fract(p.xz*.5)-.5);
l=smoothstep(.475-t*.004,.5,max(e.x,e.y));
c=mix(vec3(.04,0,.1)+vec3(0,.3,.5)*p.y*.25,vec3(1,.15,.7)*(1.+3.*U.w),l);
}
}
if(z>1.5){
e=rd.xy/rd.z-vec2(-ro.x*.01,.22);
l=length(e);
q=mix(vec3(.8,.2,.5),vec3(.03,0,.12),clamp(rd.y*3.,0.,1.));
q+=step(.997,H(floor(rd.xy*300.)))*clamp(rd.y*4.,0.,1.);
q+=vec3(.9,.3,.2)*.1/(l+.1);
d=smoothstep(.3,.29,l)*step(.3-e.y*1.5,.5+.5*sin(e.y*60.-U.x*3.));
q=mix(q,mix(vec3(1,.2,.5),vec3(1,.9,.2),e.y*2.+.5)*1.3,d);
c=mix(q,c,t<F?exp(-t*t*.0012):0.);
}
c+=V.z*.5*c+V.z*.12;
c*=1.-.25*dot(u,u);
c=smoothstep(0.,1.,pow(c/(1.+c*.3),vec3(.45)));
c+=(H(u+T)-.5)*.03;
gl_FragColor=vec4(c,1);
}
</script><script>
var Q=location.search,c=document.getElementById('c'),g=c.getContext('webgl'),A,t0,S=1,W,Hh,fr=0,ft=0,lt=0,gd=0,nd=4,tv=0,tz=-1,TX=document.getElementById('t'),
D=document.documentElement,
fs=function(){if(document.fullscreenEnabled||document.webkitFullscreenEnabled){if(document.fullscreenElement||document.webkitFullscreenElement)(document.exitFullscreen||document.webkitExitFullscreen).call(document);else(D.requestFullscreen||D.webkitRequestFullscreen).call(D)}},
sh=function(t,s){var x=g.createShader(t);g.shaderSource(x,s);g.compileShader(x);if(!g.getShaderParameter(x,35713))console.error(g.getShaderInfoLog(x));return x},
pr=g.createProgram();
g.attachShader(pr,sh(35633,'attribute vec2 a;void main(){gl_Position=vec4(a,0,1);}'));
g.attachShader(pr,sh(35632,document.getElementById('f').textContent));
g.linkProgram(pr);g.useProgram(pr);
g.bindBuffer(34962,g.createBuffer());g.bufferData(34962,new Float32Array([-1,-1,3,-1,-1,3]),35044);
g.enableVertexAttribArray(0);g.vertexAttribPointer(0,2,5126,false,0,0);
var uU=g.getUniformLocation(pr,'U'),uV=g.getUniformLocation(pr,'V'),
rs=function(){W=c.width=innerWidth*S|0;Hh=c.height=innerHeight*S|0;g.viewport(0,0,W,Hh)};
onresize=rs;rs();
ondblclick=fs;onkeydown=function(e){if(e.keyCode==70)fs()};
// ---- music: 32 bars at 128 bpm, rendered once, looped
function song(R){
var SB=R*60/128,N=SB*128|0,L=new Float32Array(N),Rr=new Float32Array(N),
roots=[45,41,48,43],th=[3,4,4,4],mel="7..75.3.3..0.35.7..a.7.55..2.5a.",
HZ=[],hz=function(n){return HZ[n]},saw=function(f,t){return f*t%1*2-1},
lp=0,bp=0,pl=0,pR=0,ln=0,lf=0,le=-1,lt=0,lph=0,ll=0,al=0,seed=1,
DL=SB*.75|0,dl=new Float32Array(DL),dr=new Float32Array(DL),ex=Math.exp;
for(i=0;i<128;i++)HZ[i]=440*Math.pow(2,(i-69)/12);
for(var i=0;i<N;i++){
var t=i/R,b=i/SB,sec=b/16|0,bar=b/4|0,st=b*4|0,ts=b*4%1*SB/4/R,tk=b%1*SB/R,
ch=bar%4,root=roots[ch],K=sec>0&&sec!=6,m=0,w=0,sc=K?1-.8*ex(-tk*9):1,n,hn,f,x,k,p=0,q=0,u,j;
seed=seed*16807%2147483647;n=seed/1073741823.5-1;hn=n-ln;ln=n;
if(K){
// kick, hats
m+=Math.sin(6.283*(48*tk+5*(1-ex(-tk*35))))*ex(-tk*7)*.95;
m+=hn*(st%4==2?ex(-ts*25)*.22:ex(-ts*80)*.1);
}
// crash on section starts
if(K&&sec!=2&&sec!=5)m+=hn*ex(-(b%16)*SB/R*2.5)*.3;
// snare on 2 and 4, roll into the last section
if(sec>2&&K&&(b|0)%2)m+=(n*.35+Math.sin(1150*tk)*.3*ex(-tk*20))*ex(-tk*13);
if(bar==27)m+=(n*.3+Math.sin(1150*ts)*.2)*ex(-ts*30)*(b%4)/4;
// bass, off the kick, through a resonant low-pass
if(sec>1&&K){
f=hz(root-12+(st%8==6?12:0));
x=(saw(f,t)+saw(f*1.007,t))*(st%4?1:0)*ex(-ts*5);
k=.07+.28*ex(-ts*12)+(sec==7?.12:0);
lp+=k*bp;bp+=k*(x-lp-.35*bp);m+=lp*.5*sc;
}
// pad, three chord notes, detuned left and right
for(k=0;k<3;k++){f=hz(root+12+(k?k>1?7:th[ch]:0));p+=saw(f*1.005,t+k);q+=saw(f*.995,t+k*2)}
k=.035+.025*Math.sin(t*.5);pl+=(p-pl)*k;pR+=(q-pR)*k;
// arp
f=hz(root+24+(st%3?st%3>1?7:th[ch]:0)+(st%8>4?12:0));
al+=((f*t%1<.3+.15*Math.sin(t)?1:-1)*ex(-ts*9)-al)*.35;
w+=al*.1*Math.min(1,b/8)*(sec==6?1.3:1);
// lead
if(sec==4||sec==5||sec==7){
j=b*2|0;x=mel[j%32];
if(x!='.'&&j!=le){le=j;lf=hz(69+parseInt(x,36));lt=t}
u=t-lt;lph+=lf*(1+.006*Math.sin(u*34)*Math.min(1,u*3))/R;
ll+=((lph%1*2-1+Math.sin(6.283*lph))*ex(-u*2.2)*Math.min(1,u*300)-ll)*.25;
w+=ll*.13;
}
j=i%DL;x=dl[j];u=dr[j];
dl[j]=w+u*.45;dr[j]=x*.5;
L[i]=Math.tanh(1.3*(m+pl*.05*sc+w+x*.6));
Rr[i]=Math.tanh(1.3*(m+pR*.05*sc+w*.7+u*.8));
}
return[L,Rr];
}
function draw(now){
requestAnimationFrame(draw);
var t=(A.currentTime-t0)%60,b=t*128/60,sec=b/16|0,z,fl;
if(t<0)t=b=sec=0;
if(sec<6){z=sec>>1;fl=b%32}
else if(sec<7){z=((b/4|0)+1)%3;fl=b%4}
else{z=(b/2|0)%3;fl=b%2}
if(b>124)z=(b|0)%3,fl=b%1;
g.uniform4f(uU,t+z*7,b,z,sec>0&&sec!=6?Math.exp(-3*(b%1)):0);
g.uniform4f(uV,W,Hh,Math.exp(-fl*4),sec>2&&sec!=6&&(b|0)%2?Math.exp(-5*(b%1)):0);
fl=sec<6&&fl<10?1:0;if(fl!=tv||z!=tz){tv=fl;tz=z;if(fl)TX.textContent=['I \u00b7 HELIX','II \u00b7 APOLLONIA','III \u00b7 HORIZON'][z];TX.style.opacity=fl}
g.drawArrays(4,0,3);
// drop resolution when frames run slow
fr++;if(now-lt<100)ft+=now-lt;lt=now;
if(fr>30){z=ft/fr;if(z>18&&S>.3){S*=.8;gd=0;nd*=2;rs()}else if(z<17.3&&S<1&&++gd>nd){S=Math.min(1,S/.8);gd=0;rs()}window.FPS=1e3/z;fr=ft=0}
}
document.getElementById('b').onclick=function(){
this.textContent='RENDERING';
A=new(window.AudioContext||window.webkitAudioContext);
A.resume();
setTimeout(function(){
var d=song(A.sampleRate),B=A.createBuffer(2,d[0].length,A.sampleRate),s=A.createBufferSource(),G=A.createGain(),o=+(/t=(\d+)/.exec(Q)||[0,0])[1];
B.getChannelData(0).set(d[0]);B.getChannelData(1).set(d[1]);
s.buffer=B;s.loop=true;G.gain.value=/mute=1/.test(Q)?0:.8;
s.connect(G);G.connect(A.destination);
t0=A.currentTime+.05-o;s.start(t0+o,o);
document.getElementById('s').style.display='none';
draw(0);
},50);
};
</script>