funslop · 25 September 2026
The 1998 EYE4U Flash intro, rebuilt frame by frame as live code, with every cut landing on a downbeat of its original soundtrack. Directed by Marcelo, made by Claude.
Built in one sitting, 12:03–16:24 · 11 prompts · 487 tool calls · tokens: 712 k output, 824 input, 1.6 M cache writes, 194.6 M cache reads
This follows the player above. It scrolls through the grid Claude fitted to the soundtrack: bars, beats, the kicks it detected and all 112 catalogued events. Click anywhere to jump there.
The same soundtrack under both. The rebuild is time-warped onto the beat, so the two drift apart by a few frames and meet again at every cut.
Left: the original site as screen-captured, 854 × 480, with the Flash player showing frames at an irregular 16–21 fps. Right: the rebuild, rendered at 1080p60. Download the side-by-side (13 MB).
Everything here was made by Claude, Anthropic's AI model, working as an agent in Claude Code. It watched the capture frame by frame, listened to the track, wrote its own analysis and measuring tools, built the animation and the renderer, and checked each result against the original. Marcelo directed in a handful of plain-English notes and judged what came back. The rule throughout was to measure, never guess.
Counted from this session's transcript when the page was published. Each response re-reads the whole conversation so far, which is why cache reads dominate.
The 84.9-second capture (854 × 480, 30 fps) was split into its 2,544 frames and read as contact sheets. Every change on screen went into a catalogue with its time and a one-line description: 112 events, made up of 56 hits, 33 moves, 11 staggered builds and 12 hard cuts.

The soundtrack was taken apart in Python with numpy and librosa: first a harmonic/percussive split, then spectral flux per frequency bin to find the kick drum. A single matched filter failed because the kick band ripples, so the detector works bin by bin. It found 222 kicks.
The tempo is 135.1 BPM in 4/4. But the site restarts its music loop at every section change, so a single grid would drift. Instead the grid is re-anchored and re-fitted in 7 blocks, each landing within 1.4–6.8 ms (median) of its kicks. The beat map above is this grid.
| block | section | starts | bpm | kicks on grid | median error |
|---|
Before writing any animation, Claude laid all 112 events on the grid as a storyboard. Each event got a rule: cuts go on downbeats, interface hits on beats or eighths, and a few stay where they were. Marcelo approved the storyboard before any code was written.
The chart below shows how far each event sits from the beat, in the 1998 original and in the rebuild.
| id | type | rule | 1998 | beat | rebuild vs beat | on screen |
|---|
The rebuild is one HTML page with one SVG stage, drawn in the capture's own 854 × 480 units and scaled to 1080p. A single pure function, seek(t), computes every attribute from the time. There are no CSS animations, no timers and no state carried from frame to frame, so any frame can be drawn in any order and always comes out the same. The outlines were traced from the capture's pixels and fitted to circles and ellipses wherever they were geometric.
async function seek(t) { // the only entry point const s = warp(t); // rebuild time → 1998 time for (const sc of SCENES) { const on = active(sc.windows, s); show(sc.g, on); if (on) sc.draw(s, t); // every attribute, from time alone } drawCursor(s, t); await Promise.all(PENDING); // wait for image decodes }

Snapping each event straight onto its beat made the motion stutter. So the rebuild plays the original animation through a time warp: one smooth curve from rebuild time to 1998 time, solved as a quadratic program with OSQP.
Cuts are hard constraints, so they land exactly on their downbeats. Playback speed stays between 0.5× and 2.3×, and it changes by at most 1.4% per 1/120 s, so you never see it step. Within those limits, 88 events are pulled toward their beats as soft targets and land a median 46 ms away (worst 156 ms). The other 9 would have forced time to run backwards or past the speed limit, so they ride along between their neighbours.
Motion that is hard to keyframe by hand was measured instead. The drop spin, the iris and the red sunburst were fitted frame by frame by silhouette matching.
The flower and the eye were rebuilt as the Flash tweens they originally were. The flower rides one cubic Bézier motion guide, eased by arc length, with a constant spin. The eye is three chained tweens. The 1998 player showed frames at an irregular 16–21 fps, so every measurement was first re-timed onto a steady Flash-frame clock, and only then fitted.
1998 capture
rebuild“everything is janky and motion is not smooth, shapes jump around each frame. review it frame by frame and make sure the motions are smooth and interpolation is perfect”Marcelo, on the first render
That note led Claude to build two meters. One is an audit that steps seek() at 60 Hz and flags every sudden change in an element's on-screen velocity. The other is an optical-flow jerk meter for rendered video.
The fixes were the smooth warp above; Bessel tangents instead of monotone splines, which stop and start at every key; stable slots for pooled shapes; and eased holds. Two more notes followed: the flower's arc dipped, and the eye trembled on its way in. Both were fixed by measuring the original again, not by adjusting by eye.

For video, Playwright drives headless Chromium. Each 60 fps frame is the average of 9 sub-frames spread across a 180° shutter, blended with ffmpeg, and the soundtrack is normalised to −14 LUFS.
The player on this page renders live instead. The soundtrack is decoded once and played through Web Audio, and the picture clock follows the time actually reaching your speakers, to within about 0.2 ms. That keeps every cut on its beat in the browser too.