A long hall of bound reed arches seen from its entrance, with lattice windows along its sides and a tea tray on the floor.
The mudhif, from the way in.
All work
Our own project · Case study

The studio walk

A reed hall with five rooms to play in. One library, three.js, and code we wrote for everything else.

Areas
Software
Who worked on it
The Nabu Studios team
When
August to November 2026
Where
Baghdad, Iraq
Made with
three.js, and AI coding agents directed by our team

A portfolio you're allowed to touch

Most portfolios ask you to look. This one lets you play, and it's built for the curious visitor. It's a hall of bound reed, which the walk calls the mudhif, with five doors. Behind each door is a room that works like a toy and shows one craft: the drafting room, the set, the motion room, the code room and the server room.

It's the second click. The home page stays light, and the walk waits one click away for anyone who already wants to see more, so all of its weight stays on its own page.

There's nothing to finish. You walk the hall, pick a door, play with the room, and walk back out.

  • A nearly blank sheet of paper on a drafting table covered in pencil hatching, with our star drawn small at its foot.
    The drafting room.
  • A camera on a stand facing a lit bottle and two small jars on a table, against a tiled wall.
    The set.
  • A galloping horse drawn as a row of frames across a pale plate, with our star in orange in the corner.
    The motion room.
  • An old computer monitor on a desk, showing a few lines of code in orange above our star and wordmark.
    The code room.
  • Rows of server racks under a status board with a world map, and a console with a load lever in front.
    The server room.

It took a few wrong walks to find this one

The walk took two months and several directions to find. In August it began as a tour of five rooms, each drawn in its own art style and each holding a working demonstration of a service.

In September each room opened on an imagined business “before”, and the visitor's hand turned it into “after”. Soon after, one of our founders wrote the walk's words in Iraqi Arabic, room by room, and from then on the Arabic was the source and the English its mirror.

At the start of October, visitors got lost in four ways: how to move, what to do in a room, where they were, and why it was a game at all. Our answer was a level map and tasks that earned stars. Played half built, the tasks felt unclear, wordy and a lot like homework, and the map broke the walk, so the same day it became one fix per room instead.

Four days later we cut further. The tasks still felt forced, and the words had started to read like an exhibit turning into an advert. So the rooms became toys with nothing to finish, and the only words left were the room names, one line at each door and one line at the end.

The new pieces then looked like they'd wandered in from another game, so we rebuilt them in the hall's own light. Then we tested them blind: a reviewer who hadn't seen the new pieces tried to spot them in the hall, and any piece the reviewer could still pick out after three rounds of fixes went to Awab to judge.

A few words came back later, where a visitor needed them. On 27 October each place got one line saying what it's for, and the thing you play with in each room glows until you've used it. Use it, and the line and the glow go away. On a phone, a sideways swipe now walks the hall too, in the direction the language reads. And on 1 November the hall got a card at its start that says why the walk exists, and fades once you walk in.

Craft

three.js, and nothing else

The walk uses one library, three.js, for the plumbing of 3D in a browser. It keeps track of the scene and the camera, and hands the graphics card the triangles to draw.

Everything you see and hear is made in code. No 3D models, no image textures, no sound files. No add-ons for three.js, no game engine, and no physics or animation library.

We also skipped the stack most 3D sites are built on. React with React Three Fiber measured 294.3 KB on its own; the Preact equivalent measured 135.1 KB. React Three Fiber also pulls in all of three.js, so the build can no longer keep only the parts the walk uses. Preact shows up in exactly one place: the flat version of the walk, for devices that can't run 3D.

We designed the walk and directed every round of it. AI coding agents wrote much of the code, and nothing they wrote counted as done until it passed our written specs, the tests and the measured limits on this page.

  alias: {
    react: "preact/compat",
    "react-dom": "preact/compat",
    "react-dom/client": "preact/compat/client",
  },
scripts/build-island.mjsWherever the walk's code asks for React, the build hands it Preact instead.
Craft

A walk on a strict diet

Every byte the walk sends is paid for by a visitor, often on a phone in Baghdad, so the walk has a ceiling on its size. Go over it and the build fails, so the walk can't quietly put on weight.

To fit under it, the walk's page leaves out Next.js's own code, which measured about 190 KB by itself. The page arrives as a plain document, the walk loads as its own script, and each room loads only as you near its door.

The ceiling started at 250 KB. On 11 August 2026, when finished rooms no longer fit, we raised it to 400 KB on purpose, with a rule written beside the number: work that doesn't fit gets cut.

On this site the ceiling is set from the walk's first measurement here, plus a tenth, and it counts every room.

// Set on 2026-10-14 from the first measurement in this repo: 302.5 KB for /en/studio/ and 302.5 KB for
// /ar/studio/, the walk's whole closure; the larger value times 1.1, rounded up, is 333.
export const STUDIO_BUDGET_KB = 333;
scripts/check-budget.mjsThe walk's ceiling, with the measurement it was set from.
Craft

Drawn in code, down to the paper

Nothing in the walk is a picture loaded from a file. The hall's walls and reed mats, the rooms and their furniture are built from shapes in code. Their light and color come from small programs called shaders, written for each room and run on the graphics card for every pixel.

Take the drafting room's paper. There's no scan of paper behind it. Its uneven tone and faint age spots are noise made by a formula, laid down in four layers, each finer and fainter than the last.

Even the loading screen is drawn. It presses in the 108 squares of our mark from the same data the logo is drawn from.

float fbm(vec2 p){
  float s = 0.0, a = 0.5;
  for (int i = 0; i < 4; i++) { s += a * vnoise(p); p *= 2.07; a *= 0.5; }
  return s;
}
src/studio-island/rooms/discovery-glsl.jsFour layers of noise, each about twice as fine and half as strong: the formula behind the paper's uneven tone and its faint spots.
Craft

It sounded like a horror game

In early September we walked the finished route with the sound on, and it felt like a horror game. Measured, the reason was plain: every room's sound was filtered noise with nothing pitched in it, all in one dark, low band. With the picture off, you couldn't tell the rooms apart.

Ambience isn't music, so the walk got a score. It's one piece in F major with a chord for each room, in the order you meet them: D minor in the hall, then on through the rooms to C at the code room, a chord that wants to resolve. It's held suspended in the server room, and resolves to F as the far doors open.

Between one room and the next, only one or two of the three voices move, each to the nearest note of the new chord. That's why changing rooms sounds like the same music carrying on, not a cut.

Two days later the chords alone sounded like static, so the score got a tune, written note by note rather than generated, so it never sounds like a machine running up a chord. Four days after that, each room's phrase got an answer, so a visitor who lingers hears a call and its reply instead of a loop.

Even the pitch was measured. An octave lower, 82 percent of the score's weight sat under 200 Hz, where small laptop and phone speakers play almost nothing, so we moved it up. The rooms' own sounds are never nudged onto the beat. Each one belongs to something happening in the picture, and the music bends around it.

The two recordings below are the same code that plays in the walk, recorded as it played. The sounds a room makes when you play with it aren't in them.

export const CHORDS: Record<string, readonly [number, number, number]> = {
  // …
  corridor: [A3, D4, F4],
  // …
  code: [C4, E4, G4],
  // …
  server: [C4, F4, G4],
  // …
  closing: [C4, F4, A4],
};
src/studio-island/score.tsFour of the walk's chords as the code holds them, from the hall's D minor to F at the doors; the rooms in between are left out here.
The hall's chord, then each room's in turn, recorded from the walk's own score as it played, with the tune over it.
The server room's suspended chord, then F as the far doors open.
Craft

Making the first visit bearable

On 20 September we measured the walk on a laptop. It held 60 frames a second in every room, and a first visit still froze: the page stopped for 13.6 seconds in all, against about 3 to 4 seconds on the next two walks, while the graphics card prepared each room's shaders. The longer the shader, the longer the wait.

Scheduling that work differently would only have moved the wait around, so we went after its cause.

The cause was the compiler Chrome uses on Windows, which copies every function into each place it's called. On 24 September we rewrote the heaviest shaders to give it less to hold, with exactly the same pixels as before. That took 45 percent off the set's compile time and 23 percent off the drafting room's.

The drafting room also started preparing while the visitor was still in the hall, which cut its arrival freeze from about four seconds to between a quarter of a second and 1.6 seconds.

The rooms still took a moment to build as you reached them: about 180 milliseconds for the server room and 100 for the code room, which a visitor standing in the hall felt as a stall. Since 26 October those two build in slices, one piece a frame, and in our phone test the worst frame on arrival fell from about 166 milliseconds to 66.

vec4 hash21x4(vec2 i){
  vec4 x = fract(vec4(i.x, i.x + 1.0, i.x, i.x + 1.0) * 127.31);
  vec4 y = fract(vec4(i.y, i.y, i.y + 1.0, i.y + 1.0) * 311.7);
  vec4 d = x * (x + 34.23) + y * (y + 34.23);
  return fract((x + d) * (y + d));
}
src/studio-island/rooms/discovery-glsl.jsThe paper asks for a square's four corners in one go instead of in four calls, which leaves the compiler a quarter as much to hold.

Where it stands

The walk runs in English and in Arabic. On this site it weighs 304.5 KB with every room included, under its 333 KB ceiling.

Measured on our test laptop on 14 October, at desktop size, it ran at between 56 and 60 frames a second across the hall and its rooms.

Play it with the sound on.

The far end of the reed hall: an arched doorway of reed panels and lattice, with warm light coming through from beyond.
The far end of the mudhif, where the walk ends.