Build notes / 05

Inside
SWARM BUREAU.

A choreographic research bureau studying how individual points become collective form. The signature technique is gpu particle formation morphing.

01

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.

02

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.

03

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.

04

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

  1. Download the complete source below and keep its name index.html.
  2. Open it through a static server. From its directory, run python3 -m http.server 8000, then visit http://localhost:8000. There is no install or build step.
  3. 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.
  4. Google Fonts requires a network connection. All visible artwork is computed locally. Native system fallbacks preserve readable text if fonts fail.
  5. 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.
Download complete HTML ↓
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.