For my birthday, I got the Adidas FIFA World Cup ball collection.
Fifteen mini balls. Fifteen tournaments. A shelf full of football history.
The soccer lover in me loved it immediately. The developer in me had a different reaction:
What if each physical ball could unlock its own digital museum page?
So over the weekend I built World Cup Ball Scanner: a browser-based museum for the collection. You can point your phone camera at a ball, let the app read the printed name, and jump into that tournament's story, stats, legends, and highlight video.
- Live demo: https://world-cup-visions.vercel.app
- GitHub repo: https://github.com/UribeJr/world-cup-visions
What I built
The app turns a physical collection into an interactive exhibit:
- Camera scanner: reads the printed ball name with in-browser OCR.
- 3D gallery: a WebGL sphere of every Adidas World Cup match ball from 1970 to 2026.
- Tournament pages: swipeable detail pages with host, champion, final score, attendance, legends, fun facts, and ball design history.
- Highlight videos: each ball can play an owner-supplied tournament clip.
- No backend: static React app, self-hosted OCR assets, no accounts, no analytics.
The goal was not just "make a database of balls." I wanted it to feel like walking up to a museum placard, except the placard reacts to the physical object in your hand.
The product idea
I kept coming back to one simple user flow:
Pick up a ball -> scan it -> unlock the year.
That gave the project a clear shape. The app did not need user accounts, comments, admin dashboards, or a content management system. It needed to answer a very specific moment:
"I have this ball in my hand. What happened at that World Cup?"
So every screen is built around that.
The home screen pushes you toward scanning or browsing. The gallery gives you a visual way to explore the full collection. The detail pages give each tournament room to breathe.
How the scanner works
The scanner uses Tesseract.js, running fully in the browser.
The flow is:
- Open the camera with
getUserMedia. - Show a framing guide so the printed ball name sits in a predictable area.
- Crop the frame to that guide before OCR.
- Normalize OCR text: uppercase, strip diacritics, collapse whitespace.
- Match against ball keywords with priority and light fuzzy matching.
The keyword priority matters more than I expected.
For example, TELSTAR is ambiguous. There is the 1970 Telstar, the 1974 Telstar Durlast, and the 2018 Telstar 18. If OCR only sees TELSTAR, the app should not confidently guess the wrong one.
So the matcher checks more specific phrases first:
TELSTAR 18DURLAST- then bare
TELSTAR
If a result is genuinely ambiguous, the app asks the user instead of pretending to know.
That was a good reminder: for physical-world software, "I don't know, help me choose" can be better UX than a false positive.
The 3D gallery
The gallery was the part I wanted to feel special.
I could have made a grid of cards, and the app would have worked. But a grid did not feel like a collection. It felt like a catalog.
So I built a WebGL sphere gallery with Three.js and GSAP. The cards wrap around the viewport, you can drag to rotate, and selecting a ball transitions into the detail page.
There is also a list fallback for reduced motion or browsers without WebGL support, because a fun visual should not block the core experience.
Making the tournament pages feel like exhibits
Each ball page combines two kinds of content:
- Structured data: host, winner, runner-up, final score, teams, matches, goals, attendance.
- Authored exhibit text: ball design, material, historical context, legends, memorable moments, and fun facts.
For example:
- Jabulani 2010 is not just "Spain won." It is the first World Cup in Africa, the vuvuzela soundtrack, Iniesta's extra-time winner, and one of the most controversial balls ever made.
- Brazuca 2014 is Germany's fourth title, Brazil's 7-1 trauma, and the ball that restored confidence after Jabulani's aerodynamics controversy.
- Trionda 2026 is the upcoming expanded tournament across Canada, Mexico, and the United States.
The app is basically a little sports-history layer wrapped around a physical collection.
Built with
The stack:
- Vite + React + TypeScript
react-router- Tesseract.js for browser OCR
- Three.js for the gallery scene
- GSAP for gallery/hero motion
- Vitest for matcher and data tests
- Vercel for hosting
The repo is public here:
https://github.com/UribeJr/world-cup-visions
Where AI helped
I used Cursor and AI assistance throughout the build, but I tried to keep it as a collaborator rather than the driver.
The useful parts were:
- scaffolding boring glue code faster;
- refactoring matcher logic and tests;
- debugging Vercel deployment issues;
- polishing the README and public repo;
- checking edge cases I might have missed.
The parts I still had to own were the product decisions:
- what the museum should feel like;
- when to scan vs browse;
- how much motion was too much;
- what to cut so it stayed a weekend project;
- how to connect the physical birthday gift to a digital experience.
That balance felt right. AI helped me move faster, but the point of the project was still personal.
What I would improve next
If I keep working on it, I would like to:
- tune OCR keywords after testing each physical ball in different lighting;
- add a more polished scan success animation;
- add more behind-the-scenes stories for each tournament.
Final thought
This started as a birthday gift sitting on a shelf.
By Sunday night, it had become a tiny browser museum: part scanner, part gallery, part football-history rabbit hole.
That is my favorite kind of weekend project: personal enough to care about, technical enough to learn from, and small enough to actually finish.
If you have a minute, try it here:
https://world-cup-visions.vercel.app
https://github.com/UribeJr/world-cup-visions
What physical collection would you digitize this way?




Top comments (0)