No photograph on this site is simply shown. Every image is made of tens of thousands of glyph grains: they appear first as a red latent image under the safelight, then turn to colour bit by bit, the way paper does in the developer. This is how it works.
The grains are glyphs
Behind the page there is a single fixed WebGL canvas covered by a grid: one point every 3.6 pixels on a desktop, about a hundred thousand. Each point samples its colour from the photograph in the shader, and also knows its place in a line of code — on the home page, a line about developing a print. Before development the point is drawn as that character; after, it becomes a round silver grain.
float shape = mix(glyph, disc, vDev);vec3 latent = uSafe * (0.18 + vLuma * 1.25);gl_FragColor = vec4(mix(latent, vColor, vDev), shape * vAlpha);Shadows first
On real paper the shadows arrive first: where the silver is densest it darkens first. So each grain’s progress subtracts its own brightness, plus a little randomness. The picture surfaces from the dark areas instead of fading in all at once.
float dev = clamp((uDevelop * 1.7 - luma * 0.5 - aRand * 0.35) / 0.85, 0.0, 1.0);The developer is a wave equation
The ripples under your pointer come from a 2D wave equation solved on a quarter-resolution float texture: each frame the new height is half the sum of the four neighbours, minus the previous height, times a little damping. Grains shift along the slope of the surface, so you seem to look at the print through moving liquid. Left alone, it drops something in every few seconds anyway.
float h = (sum * 0.5 - s.g) * 0.982;h += uDrop.z * exp(-dot(d, d) / (uRadius * uRadius));Inversion and transitions
The photographs section isn’t WebGL at all, just CSS: invert() reverses each picture and a layer of safelight red sits over it. The nearer a frame is to the centre of the screen, the less it is inverted; in the middle it comes up in its own colours. Between pages, two cinema bars close over a red safelight seam.
Restraint
All the spectacle lives in the picture, so the type stays small: 11 pixels for the interface, 15 for text. Slow devices step down on their own — if the first 60 frames average more than 24 milliseconds, the grains get fewer and larger — and with reduced motion turned on, every movement stops while the content stays whole.
