Settle the GOAT debate the only honest way: become one.
Drop in a photo and watch it melt into Messi or Ronaldo, pixel by pixel, right in the browser. Pick your side. No backend, no sign-up, nothing leaves your machine.
Messi or Ronaldo. People have argued about it for two decades and nobody has won. GOATify does not pick a winner, it lets you join the argument by morphing your own face into whichever GOAT you ride for. Choose Messi, choose Ronaldo, and watch the transformation happen frame by frame.
(Yes, you can also load your own target image and morph into your local pub legend instead.)
Every photo is just a bag of colored dots. To turn picture A into picture B, you need to decide where each dot in A should travel to land on B, then slide them all there at once. That "where does each pixel go" question is an assignment problem, and how you answer it is the whole game.
GOATify ships three matchers, from rough-and-cheap to slow-and-perfect:
| Matcher | The idea | Quality | Speed | Max size |
|---|---|---|---|---|
| Sliced OT (default) | Approximate optimal transport by sorting along random directions, refined over N passes | High | Fast | 256 |
| Greedy | Grab the nearest unused pixel, repeat | Okay | Medium | 256 |
| Hungarian | The textbook exact minimum-cost matching (O(N³)) | Perfect | Slow | 32 |
Hungarian is the "correct" answer but it scales like a brick, so it is locked to tiny resolutions. Sliced OT is what you actually want: nearly as clean, fast enough to feel live. All of it runs in a Web Worker, so the slider stays buttery while the math grinds.
You also control the resolution, the number of iterations, and how the source is zoomed and framed before the run. More pixels and more iterations means a sharper morph and a longer wait. Hit play, watch it go, download the frame you like.
pnpm install
pnpm dev # dev server with HMRpnpm build # type-check + production build
pnpm preview # serve the build
pnpm lint # oxlint- React 19 + TypeScript + Vite
- Material 3 (
@material/web), custom green theme, light and dark modes - Web Workers to keep matching off the main thread
- Canvas 2D to draw the morph
munkres-jsfor the Hungarian solver
src/
├─ components/ canvas, settings, app bar, M3 bits
├─ layouts/ desktop & mobile (all state lives in App)
├─ hooks/ use-is-mobile, use-theme
├─ lib/
│ ├─ match.worker.ts worker entry, picks a matcher
│ ├─ slicedOT.ts sliced optimal transport
│ ├─ hungarian.ts exact assignment
│ ├─ match/greedy.ts greedy nearest-match
│ ├─ morph.ts interpolates between matched point sets
│ └─ image.ts turns an image into a point set
└─ App.tsx top-level state & wiring
