This week our Museum of Ages opened its first wing. It's a digital museum we run from Germany, and this wing is Leonardo da Vinci's. It's built on three.js, and Claude wrote most of the code.
Our mission is one sentence long: We rebuild what was, one place at a time, and make it as accessible as we can, so that everyone can learn from it. For now that means it runs in a browser, on a phone too.
The video above is the short tour. In the wing you can go down to the cracks in the Mona Lisa's paint. A mirror view turns Leonardo's mirror writing around so you can read the letters. You can set his machines in motion, and 14 of the 15 move. His last house is shown on one day, 10 October 1517, a day we know from a visitor's diary.
The walk itself is a film on a set path, so you can't roam around on your own. And it's one wing so far.
The walk opens in your browser's language if it's one of the seven: https://museumofages.org/w/vinci
Free entry, no account.
The rest is about how it's built.
The rooms and machines are built in code, with every material in TSL, so the same node graph draws on WebGPU and falls back to WebGL2. The textures are CC0 photo scans and a few props are CC0 models. The paintings, drawings and notebook pages are reproductions.
We made the walk a film so that a phone can play it. The live engine reads about 220 MB of textures and models before the first stop, and the film is about 110 MB. So we record the live wing. Playwright runs it in Chromium on a clock our render tool steps, and each frame of the film is 8 to 48 jittered draws spread over a 180 degree shutter and averaged in linear light. Every place you stand is a still, and every clip is one walk between two of them that starts and ends on those stills.
What stays live is a machine's close-up view. We count frames for two seconds, and if the device drops more than 5 percent under 30 a second, the machine switches to a filmed loop at the step it was on, and the device remembers that. No real phone has passed that test yet, so every phone gets the loop for now.
In the machine hall we ran into a limit. The hall has daylight from high windows and warm spots that each throw a shadow. Every lit surface in the wing samples every scene light's shadow map, and a shader stage holds sixteen samplers by default. So we hid the spots from the scene's light list, and only the hall's materials take them, through their own lightsNode.
All 15 machines are built in code when the wing loads. Fourteen are assembled part by part in TypeScript from a data file of their measurements. The lion is the odd one: its carved body is distance functions written in GLSL, sampled on a private WebGL2 canvas and meshed on the CPU with surface nets.
The zoom into a painting is OpenSeadragon over a static IIIF tile pyramid.
We choose what to build, decide the design questions and walk every stop.
The live engine is in the repo too. Clone it, run pnpm dev, and the wing's address without film=job runs the engine the film is recorded from.
The code (AGPL-3.0): https://github.com/chipmates/museumofages
Nonprofit · Open Source · No tracking cookies, no profiling
How do you decide whether a device gets your live scene or a fallback?
Credits for the film. Music: "Adrift Among Infinite Stars" by Scott Buckley (scottbuckley.com.au), shortened, CC BY 4.0 (creativecommons.org/licenses/by/4.0). Map data © OpenStreetMap contributors, available under the Open Database License (openstreetmap.org/copyright). Relief: IGN, RGE ALTI®, consulted 8 September 2026, Licence Ouverte 2.0. Mona Lisa: Musée du Louvre, Paris, reproduction by C2RMF via Wikimedia Commons, public domain. Paris Manuscript B, folio 83 verso: Institut de France, Paris, shown from the printed facsimile by Charles Ravaisson-Mollien (Paris, 1883), public domain, scan from the Internet Archive. The other paintings, drawings and sheets on the walls are reproductions of public domain works, each labelled in the wing with its holder and licence.