Oliver Kure Olsen
Me
Direction and every call: the idea, the look, what ships and what is cut. The footage and references a piece starts from. The last look, on a laptop and a phone.
work.draindk.techCounted 22 October 2026
I set the direction and make the calls. Two AI agents write the code, Claude and Codex, in one repository, with a handoff file they both read and checks that fail the build.
Me
Direction and every call: the idea, the look, what ships and what is cut. The footage and references a piece starts from. The last look, on a laptop and a phone.
Anthropic · Claude Code
Writes the code, the shaders and the Blender scripts, and runs the checks. One thread per job: code in the cloud, renders and timing on my Mac.
OpenAI
Built the first version in September and still takes jobs on the same repositories. A case trial goes on a branch of its own and merges when I say so.
Every session reads HANDOFF.md first: what is live, what is open, how I work and the rules for publishing, kept under 12 KB. AGENTS.md holds the standing rules. One agent works on a site at a time, and every session pulls before it starts, because main is production.
claude/<thread>A thread’s branch. It never deploys.
preview/<name>A preview link, when one is needed.
tsc --noEmitTypes.
next buildNext.js 16, exported as static files.
consistency47 rules over every page, the CV and its PDF, in about five seconds. One finding fails the build.
perf-site.mjsOn the Mac, before a piece ships: the whole visit in one tab, page to page, at a phone’s size with the CPU slowed four times; Safari in the iOS Simulator.
mainA push is production, on Vercel. A build that changes no page is skipped.
liveErrors report to Sentry. Every Friday a check reads them, fixes what it can reproduce and runs the rules against the live site, the Maquettes’ repositories and the store.
47 rules in scripts/consistency/rules.mjs, each a decision of mine with its date and reason. When something shows in more than one place, a page, the CV, a demo or the store, it can’t change in one and stay in another. Ten of them, in their own words:
titleOne title on the site: 3D web designerwordingNone of the words dropped from his positioningbodernes-placeBodernes sits near the beach, not on the sandpin-soundThe pin table has no sound and no Sound switchcopiesCode kept in two places changes in bothscan-no-nodThe scan doesn’t nod or bow when he faces youmagnus-white-wallMagnus' wall is painted whiteclient-labelsEach client site says what it is: Live site, Redesign or Proposalwork-orderWork runs the Simulacra first, then the live sites, then the proposalshow-i-workHow I work says who writes the code: AI agents, with him making the calls$ node scripts/consistency/check.mjs
consistency: no out/ (run next build first); checking the sources only.
consistency (build): 281 documents, 47 rules.
✗ copies: Code kept in two places changes in both
app/showroom/sky.ts ↔ app/lab/pintable/engine/sky.ts
the sun and moon: they now differ where they were the same
only in the first: const RAD = Math.PI / 180, E = RAD * 23.44;
only in the second: const RAD = Math.PI / 180, E = RAD * 23.4397;
Why: Swash’s surf runs under Bodernes, the opening’s table is Pintable’s, the WebGPU yield guard is in three places, and every Maquette page copies its repo’s src/. A fix in one copy and not the other is how the Safari band upload missed Pintable (17 October).
Owner: the thread that changed the copy. Every copy: docs/consistency-map.md#copies
1 to fix.Measured in Chrome on a MacBook Pro (M4 Pro) against the live site: a 390 px phone at 3×, the CPU slowed four times, scrolled to the end by a thumb’s flicks. The GPU is the Mac’s, so a real phone is the last check.
| Case | Phone | 95 % of frames | On a phone | Draws with |
|---|---|---|---|---|
| Terne X | 60 fps | under 16.8 ms | 6.1 MB | WebGL2 |
| Horisont | 60 fps | under 16.7 ms | 1.2 MB | WebGL2 |
| Grums | 60 fps | under 16.7 ms | 7.2 MB | WebGL2 |
| M1 microphone | 60 fps | under 16.7 ms | 4.7 MB | WebGL2 |
| Tide | 60 fps | under 16.8 ms | 13.9 MB | WebGL2 |
| Bodernes Strandcafé | 60 fps | under 16.7 ms | 2.8 MB | WebGPU, then WebGL2 |
| Murermester Magnus | 60 fps | under 16.8 ms | 2.4 MB | WebGL2 |
| Severin | 60 fps | under 16.8 ms | 3.0 MB | WebGL2 |
| Bageriet B | 60 fps | under 16.8 ms | 2.6 MB | HTML and CSS |
createImageBitmap for images, a worker for EXRs, big textures uploaded in bands of rows.dispose(), then forceContextLoss().Sentry, in the EU, errors only: no tracing, no replay, no cookies, no IP addresses. A 1.4 KB loader; the SDK comes only with the first report, so a page that works never loads it. A piece that gives up and shows its still says so (fellBack()). The first catch, 14 October: Shrike’s WebGPU texture views failing on Chrome 141, now guarded by guardWebGPU().