← Learn · Build · 13 November 2026 · 7 min

The Claw: A Toy Store Where You Win the Toys

How I built a toy store whose hero is a claw machine: a Codex photo of an empty cabinet, ten Codex toys cut out with macOS Vision, Matter.js bodies shaped like each toy, and a claw that holds on (sometimes). Prompts and code inside.

The output, liveavivashishta.com/lab/claw
Open the live page ↗

Everyone has put money into a claw machine and walked away with nothing. That feeling is a better store front than any hero banner, so I built a toy store where the hero is the machine. Every toy in the glass is for sale, and every toy can be won. Play it at avivashishta.com/lab/claw.

Move over the glass to aim. Click (or press space, or hit the yellow button on the cabinet) to drop. The claw goes down until it touches a toy, closes, and lifts. If it still has the toy at the top, it carries it to the chute and lets go, the toy rolls out of the flap at the bottom and it is in your bag. Some toys hold better than others, and every third try is a sure thing, because a store that never lets you win is just a slot machine.

The short version: Codex makes one photo of an empty claw machine and one product shot of each toy. A script cuts the toys out with macOS Vision and turns each outline into a convex polygon. The page drops two of each toy into the glass as Matter.js bodies with those shapes, draws them on a canvas laid exactly over the glass in the photo, and moves a hand-drawn chrome claw with a small state machine.

Step 1: an empty machine (Codex)

The cabinet is the stage, so it has to be empty: no toys and no claw, because both are going to move. It also has to be straight on, so the glass is a rectangle I can lay a canvas over.

A perfectly straight-on, orthographic front view of a glossy cobalt blue arcade claw machine cabinet, centered, filling most of the frame height with a little margin. The upper part is a large clear glass display box, completely EMPTY inside (no prizes, no toys, no claw), with a plain flat pale grey floor inside and soft warm lights along its top edge; a slim chrome gantry rail runs across the top inside the glass. Below the glass is a cobalt blue base with a small control panel ledge (one red ball-top joystick and one big yellow button) and, at the bottom right, a square brushed chrome prize chute opening with a flap. Chrome feet. Pale warm grey seamless studio background, soft even light, symmetric, photoreal product photo. No text, no words, no logos, no stickers anywhere. Portrait 2:3, 1024x1536.

Then I measured the photo once: where the rail is, where the floor is, where the chute and the yellow button are, as fractions of the image. Those four numbers are the whole layout:

const GLASS = { l: 0.122, t: 0.133, r: 0.879, b: 0.573 }; // from the rail to the middle of the floor
const CHUTE = { l: 0.64, t: 0.755, r: 0.835, b: 0.876 };
const BUTTON = { x: 0.596, y: 0.628, r: 0.045 };

The canvas sits at GLASS, so the claw's carriage runs on the photo's own rail and the toys sit on the photo's own floor. The yellow button is a real <button> placed over the printed one.

Step 2: the toys

Ten original toys, one prompt, only the description changes. "An original design, no logos, no brand" matters here: ask for a vinyl figure without it and you get something that looks a lot like a famous one.

A collectible toy product photo of a glossy vinyl art toy of a chubby little astronaut in a white space suit with a round gold-tinted visor and small cobalt blue backpack. The toy alone, an original design, no text, no logos, no brand, front three-quarter view, centered with generous margin, on a pure seamless white background (#FFFFFF), soft studio light, a very faint contact shadow only, photoreal, crisp detail. Square 1:1, 1024x1024.

Step 3: cut out, and give each toy its shape

The prep script cuts each toy out with macOS Vision (the same subject lifting as "Copy Subject" in Photos), trims it and saves it as WebP with alpha. Then it reads the alpha at quarter size, takes the convex hull of every solid pixel and drops points until 14 are left:

// Drops the point that adds the least area until there are at most n left.
while (h.length > n) {
  let best = 0, min = Infinity;
  h.forEach((p, i) => { const a = area(h[i - 1], p, h[i + 1]); if (a < min) { min = a; best = i; } });
  h.splice(best, 1);
}

That polygon is the toy in Matter.js. A duck rolls like a duck and the gift box stacks like a box. Boxes for everything would look fine standing still and wrong the moment the pile moves.

bun scripts/prepare-claw.mjs ./raw

Step 4: the pile

Two of each toy rain into the glass when the page opens, a tenth of a second apart, which is also the loading animation. Plush toys get high friction and almost no bounce; the ceramic cat, the tin robot and the vinyl astronaut are heavier and slide. On the right is the prize hole: no floor, behind a low acrylic wall. Anything that falls through it is a win, and the same toy drops back into the top of the machine a moment later so the pile never runs out.

Step 5: the claw

The claw is drawn, not photographed, because it has to open and close: a cable, a chrome hub and three prongs (the middle one squashed to look like it points at you). It isn't a physics body. It is a small state machine: move, down, close, up, carry, release, home. Going down, it stops where the highest toy under it begins:

const b = near(S.x); // the toy whose top is under the claw
const floor = Math.min(H - 14, b ? b.bounds.min.y + 30 : H - 14);

When it closes, it picks the nearest toy within reach and ties it on with a soft Matter.js constraint, so the toy swings as it rises. Whether it slips is decided right then, from the toy's grip, and if it is going to slip it lets go at a random height on the way up. It feels unfair in exactly the way the real ones do.

const holds = S.sure || Math.random() < toy.grip;
const c = Matter.Constraint.create({ pointA: { x: S.x, y: S.y }, bodyB: b, pointB: { x: 0, y: -12 }, length: 0, stiffness: 0.06, damping: 0.12 });
S.held = { body: b, c, slipAt: holds ? -1 : TOP + (S.y - TOP) * (0.3 + Math.random() * 0.4) };

The small things

  • It plays itself. After nine quiet seconds the machine goes into attract mode and has a go on its own, like a real one in an arcade. Its wins don't go in your bag.
  • The store is real enough. The toy under the claw shows its name, price and grip on the left with a Buy button. The shelf on the right lists every toy with "Try for it", which aims the claw at the highest copy and drops.
  • The prize comes out of the chute. A copy of the toy slides out of the flap in the photo, then the "You won" card pops up and the bag count bumps.
  • Five coins, then Top up. Every third paid try holds whatever it grabs.
  • Sound from my Reels SFX library: a latch for the drop, a click when it closes, a thud when it slips, a ding when you win.
  • Keys: ← → to move, space to drop.

Build your own

Build a full-screen claw machine store page (React, Matter.js, canvas). Assets: cabinet.webp (a straight-on photo of an empty claw machine) and one cut-out WebP per toy, plus each toy's outline as a convex polygon. Lay a canvas exactly over the glass in the photo (its position as fractions of the image). In it, make a Matter.js world: walls, a floor up to 70% across, then a hole behind a low wall. Drop two of each toy in as bodies made from their polygons and draw each toy's image at its body's position and angle.

Draw a claw (cable, hub, three prongs that open and close) and move it with a state machine: follow the pointer, on click move to the target, go down until just inside the top of the highest toy under it, close, attach the nearest toy with a soft constraint, lift (with a chance to let go part way, based on the toy's grip), carry to above the hole and open. A toy that falls through the hole is won: show it sliding out of the chute in the photo and add it to the bag. Every third try always holds. Show the toy under the claw with its price and a Buy button, and a shelf of all toys with "Try for it".