Inspiration
Cambodia is called the Kingdom of Wonder, but most of that wonder never makes it past Angkor Wat. Twenty-four other provinces each with its own temples, waterfalls, floating villages, and stories sit just off the well-worn tourist trail, under-visited and under-documented. We wanted to build something that pulls travelers out to those places and rewards them for actually showing up, not just scrolling past a photo.
The other half of the inspiration was trust. Anyone can tap "I've been here" on a map. We wanted a check-in that means something proof that a person physically stood at a heritage site so that points, rewards, and collectibles are earned, not gamed. That single constraint shaped the entire architecture.
What it does
COTA is a gamified, bilingual (English / Khmer) travel PWA for exploring Cambodia:
- Explore 25+ provinces and their heritage spots through 3D procedural scenes and 360° WebXR/VR panorama tours.
- Prove you were there with two GPS-verified mechanics Proof of Visit (PoV) and Proof of Arrival (PoA) using real device location, not mock data.
- Earn points, climb a leaderboard, open mystery boxes, and mint a souvenir NFT for each verified spot.
- Book & guide agents build itineraries, tourists book them, guides run them, and service staff pick up gigs.
- Ask Apsara, an AI voice guide and travel concierge, for context on where you're standing.
- Everyone tourist, agent, guide, service staff, admin gets a role-specific dashboard.
How we built it
The stack is deliberately serverless: a Vite + React 19 + TypeScript single-page app talking directly to Supabase (Postgres, Row-Level Security, Realtime, Edge Functions, Storage). There is no custom app server the browser is the client, Supabase is the backend, and it deploys to Cloudflare Pages.
- 3D / VR: three.js with @react-three/fiber, drei, and xr.
- Maps & location: Leaflet + the browser Geolocation API.
- AI: proxied through a Supabase Edge Function so provider keys never ship in the client bundle.
- i18n: i18next, every string mirrored across en.json and km.json.
- PWA: vite-plugin-pwa (Workbox) for offline support and install-to-home-screen.
The math behind a check-in
Challenges we ran into
- GPS is noisy especially near stone walls and tree cover, exactly where heritage sites are. Choosing $r_{ ext{threshold}}$ was a tradeoff: too tight rejects real visitors, too loose makes the "proof" meaningless.
- Security can't live in the client. A PWA ships all its logic to the browser, so we pushed real authorization into Row-Level Security and a protected authorized_role column client-side checks are UX only.
- Points must never double-count or vanish. Spend flows refund server-side on failure, and awards run through Postgres RPCs so concurrent taps can't corrupt a balance.
- Two languages, everywhere every feature landed in English and Khmer at once, including a non-Latin script.
- Keeping secrets out of a client-only app meant moving AI keys behind an Edge Function proxy instead of a VITE_ variable.
What we learned
- Design the trust model first the constraints, RLS policies, and refund logic all followed from one non-negotiable rule.
- The database is the source of truth, not the UI.
- Serverless doesn't mean effortless Supabase moved the hard thinking into RLS and Edge Functions.
- A little geometry can be the entire product experience.
What's next for COTA
- Move point awards fully behind Edge Functions.
- On-chain souvenir NFTs and a richer rewards marketplace.
- Offline-first heritage packs for low-connectivity provinces.
Built With
- react-19
- typescript

Log in or sign up for Devpost to join the conversation.