Inside
SWARM BUREAU.
A choreographic research bureau studying how individual points become collective form. The signature technique is gpu particle formation morphing.
Upload identities once
A single Float32Array contains IDs 0–5999. No particle positions are animated on the CPU. One vertex shader computes every formation and one drawArrays call renders the entire system.
Define three homes
A Fibonacci sphere uses the golden angle 2.399963. A parametric torus combines slow azimuth and fast cross-section angles. A helix maps normalized particle ID to vertical position.
Morph in the vertex shader
Normalized scroll becomes m in [0,2]. Smoothstep interpolates sphere to torus, then torus to helix. A Y rotation adds automatic motion; perspective division and point size convey depth.
Render lightweight light
The fragment shader clips each square point into a soft circle. Additive blending accumulates luminosity. At reduced motion, rotation is frozen and the renderer redraws only on scroll or resize.
Reproduce the site
- Download the complete source below and keep its name
index.html. - Open it through a static server. From its directory, run
python3 -m http.server 8000, then visithttp://localhost:8000. There is no install or build step. - The stylesheet lives in the head. The rendering and interaction code lives in the final script. Adjust one parameter at a time and compare the result at desktop and 375 px widths.
- Google Fonts requires a network connection. All visible artwork is computed locally. Native system fallbacks preserve readable text if fonts fail.
- Enable your operating system’s reduced-motion setting and repeat the interaction. Use browser developer tools to check errors, overflow, and frame timing before publishing.
The complete rendering and interaction code
const c=document.querySelector('#particles'),gl=c.getContext('webgl',{alpha:false,antialias:false}),rm=matchMedia('(prefers-reduced-motion: reduce)');if(gl){const vs=`attribute float id;uniform float t,m,aspect;vec3 sphere(float i){float y=1.-2.*i/6000.;float r=sqrt(1.-y*y),a=i*2.399963;return vec3(cos(a)*r,y,sin(a)*r);}void main(){float a=id*.037,b=id*2.399963;vec3 s=sphere(id);vec3 ring=vec3((1.+.3*cos(b))*cos(a),.3*sin(b),(1.+.3*cos(b))*sin(a));vec3 helix=vec3(cos(a)*.8,(id/6000.-.5)*2.5,sin(a)*.8);vec3 p=m<1.?mix(s,ring,smoothstep(0.,1.,m)):mix(ring,helix,smoothstep(0.,1.,m-1.));float angle=t*.09+.4; p.xz=mat2(cos(angle),-sin(angle),sin(angle),cos(angle))*p.xz;float depth=3.5-p.z;float scale=aspect<1.?.9:1.7;float shift=aspect<1.?0.:1.5;gl_Position=vec4((p.x*scale+shift)/aspect,p.y*scale,0.,depth);gl_PointSize=2.3*(3./depth);}`;const fs=`precision mediump float;void main(){float d=length(gl_PointCoord-.5);if(d>.5)discard;gl_FragColor=vec4(.875,.937,.208,(1.-d*2.)*.9);}`;function shader(type,src){const 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}const p=gl.createProgram();gl.attachShader(p,shader(gl.VERTEX_SHADER,vs));gl.attachShader(p,shader(gl.FRAGMENT_SHADER,fs));gl.linkProgram(p);gl.useProgram(p);const b=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,b);gl.bufferData(gl.ARRAY_BUFFER,Float32Array.from({length:6000},(_,i)=>i),gl.STATIC_DRAW);const id=gl.getAttribLocation(p,'id');gl.enableVertexAttribArray(id);gl.vertexAttribPointer(id,1,gl.FLOAT,false,0,0);gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE);const t=gl.getUniformLocation(p,'t'),m=gl.getUniformLocation(p,'m'),a=gl.getUniformLocation(p,'aspect');function draw(time=0){const d=Math.min(devicePixelRatio,1.5),w=Math.round(innerWidth*d),h=Math.round(innerHeight*d);if(c.width!==w||c.height!==h){c.width=w;c.height=h;gl.viewport(0,0,w,h)}const f=Math.min(2,scrollY/(document.documentElement.scrollHeight-innerHeight)*2);gl.clearColor(.125,.067,.141,1);gl.clear(gl.COLOR_BUFFER_BIT);gl.uniform1f(t,rm.matches?0:time*.001);gl.uniform1f(m,f);gl.uniform1f(a,innerWidth/innerHeight);gl.drawArrays(gl.POINTS,0,6000);if(!rm.matches)requestAnimationFrame(draw)}draw();addEventListener('resize',()=>{if(rm.matches)draw()});addEventListener('scroll',()=>{const f=scrollY/(document.documentElement.scrollHeight-innerHeight);document.querySelector('#phase').textContent=f<.25?'FIELD 00 / SPHERE':f<.75?'FIELD 01 / ORBIT':'FIELD 02 / SPIRAL';if(rm.matches)draw()},{passive:true});rm.addEventListener('change',()=>draw())}else{c.style.background='radial-gradient(ellipse at 65% 45%,#dfef3544,transparent 55%)';document.querySelector('#phase').textContent='STATIC FIELD / WEBGL UNAVAILABLE'}The complete visual system
:root{--body:'Overpass',sans-serif}body{background:#201124;color:#f4efdf;font-family:var(--body)}header{padding:23px 4vw;position:relative;z-index:2;display:flex;justify-content:space-between;border-bottom:1px solid #f4efdf55;font-size:13px}header b{color:#dfef35}#particles{position:fixed;inset:0;width:100%;height:100%;z-index:0}.intro{position:relative;z-index:1;min-height:90svh;padding:40px 4vw;display:flex;flex-direction:column;justify-content:space-between;pointer-events:none}.intro h1{font:500 clamp(80px,13vw,210px)/.78 'Teko';margin:40px 0;letter-spacing:-.015em;max-width:6ch;text-shadow:0 3px 30px #201124}.intro h1 span{color:#dfef35}.intro p{max-width:330px;background:#201124e8;padding:15px;font-size:16px}.formation{position:relative;z-index:1;min-height:75svh;display:flex;align-items:flex-end;padding:50px 4vw}.formation h2{font:400 70px/.85 'Teko';margin:0;color:#dfef35}.formation .card{background:#201124eb;border-left:3px solid #dfef35;padding:25px;max-width:400px}.card p{font-size:16px}.colophon{background:#201124}#phase{position:fixed;right:4vw;bottom:35px;font:13px monospace;z-index:4;background:#201124de;padding:12px;color:#dfef35}@media(max-width:600px){.intro h1{font-size:105px}.intro{min-height:88svh}.intro p{max-width:250px}.formation{min-height:65svh}.formation h2{font-size:54px}#phase{bottom:12px;font-size:12px}.colophon{padding-bottom:65px}}Original fictional design study. No stock photography, model files, image textures, or sample audio.