Tova

A stand at a trade fair has one problem: nobody wants to hand over their details for nothing. Tova gives them a reason. A visitor registers, plays a sixty-second canvas round catching sponsor coins and dodging hazards, answers timed trivia for bonus points and watches a live standings board. Behind it, an organiser console runs the whole event: the schedule, the fairness caps, the falling items, the trivia, the prizes, the people, and the lead list as an Excel file ready for a CRM. Four roles see four different slices of it, in Georgian and English. We built it end to end — a Next.js app, a hand-written canvas engine and the route handlers behind both.

Year: 2026 · Category: event technology

Our role

Full-stack. The game engine and its fairness plan, the scoring and standings rules, the organiser console, the data model, sign-in, and the bilingual interface.

The problem

A stand at a trade fair is expensive and the conversation at it is short. Visitors drift past, glance at the banner and keep walking, and the ones who stop are asked to fill in a form with their name, company and number for no obvious reward. The organiser ends the day with a stack of cards, half of them illegible, and no way to say which sponsors actually held anyone's attention.

The organiser's side is no better: the schedule, the prizes, the questions and the list of who came live in different places, and the only fair way to pick a winner is one that nobody can argue with. A game fixes the first problem only if it is fair, because the moment a visitor suspects the score was luck or a trick, the prize and the lead are both worth less.

The approach

We made the game the reason to register and made fairness a property of the engine instead of a promise.

A visitor signs up, plays one sixty-second canvas round and is on the board. The round is planned before it starts, so every player faces the same total of catchable coins and the same total of penalties, arranged differently. Trivia adds bonus points on top, once per question, and the standing is the best round plus those points, so repetition does not help. The board refreshes while people watch.

Everything the organiser touches sits behind a console with four roles, so a staff member can look without being able to break anything. Events, falling items, trivia and prizes are all data the organiser edits, the people page handles accounts, and the participants page turns the lead list into an Excel file in one click. Sponsor items carry the brand names that make the game worth sponsoring.

Highlights

  • Fair by construction: the round is planned up front so every player gets exactly the event's catchable budget and exactly its penalty budget on a four-stage ramp. Only the arrangement is random, and the server clamps a submitted score to the budget as well.
  • The standings rule is the best single round plus correct trivia points, never a sum of rounds, so time spent at the stand cannot buy a place on the board. The organiser can hide the board, and the player's card shows a countdown to the end instead of a rank.
  • Trivia is answered once and the correct answer is never sent to the browser. A timer auto-submits at zero, and the seconds-left counter turns red at five.
  • Four roles over one console: a supervisor reads, an admin edits events, items, trivia and prizes, a superadmin also manages people, and nobody can change, suspend or delete themselves.
  • Everything an organiser needs to run a fair day: schedule, round length, fairness caps, difficulty, speed level, board size and three visibility toggles per event.
  • The lead list leaves as a real Excel workbook, built so a hostile cell value cannot become a formula.
  • Georgian and English from the data model up: 760 strings in each language with the same keys, and Georgian-capable type throughout.

Architecture

  • Application: Next.js with the App Router and React 19. Server components fetch the data for the console and the standings; client components call a small set of `/api/*` route handlers for everything that mutates. Tailwind CSS 4 with Georgian-capable type and a monospace face for scores.
  • Game engine: A custom HTML5 Canvas 2D engine with no game library: a pre-planned spawn schedule, a capped fixed-step loop, pointer, touch and arrow-key control for the paddle, and effects that drop on slow devices.
  • Validation and anti-cheat: zod schemas on every route handler. The server clamps every submitted score to the event's point budget, and the round plan guarantees the same budget for every player.
  • Auth: Signed JWT sessions in an `httpOnly` cookie, passwordless sign-in with a one-time code, and a route gate that redirects signed-out visitors away from `/play`, `/me` and `/admin`. Four roles: player, supervisor, admin and superadmin.
  • Data: A relational database through an ORM, with users, events, items, plays, trivia questions, answers and prizes. A unique constraint on (user, question) means a question can only be answered once.
  • Email and storage: Transactional mail for sign-in codes and for notifying prize winners, and object storage for the sponsor art an organiser uploads.
  • Export: A server-built Excel workbook of the participant list, ready to import into a CRM.
  • Hosting: Serverless, with the route handlers and pages deployed together. AI image generation for item art is planned: the button exists and answers coming soon.

In numbers

Roles: 4 · Screens: 17 · Mocked API routes: 41 · Languages: 2 · Game engine lines: 870 · Demo source lines: 14.8k

Results

  • A stand has a reason for people to stop and leave their details, and the organiser walks away with a clean lead list as an Excel file instead of a stack of cards.
  • Winners are decided by a rule nobody has to argue about: the same budget for every player, the best single round, and a server clamp behind it.
  • A whole event — its schedule, caps, items, trivia and prizes — is something an organiser edits in a console rather than something we deploy.
  • Staff can watch the standings, drill into a single player's plays and read the numbers on Insights without being able to change an event.

What is used

  • Application: Next.js 16 (App Router, server components), React 19, TypeScript, Tailwind CSS 4, Noto Sans and Noto Serif Georgian, Monospace face for scores
  • Game: HTML5 Canvas 2D, Custom engine, no game library, Pre-planned spawn schedule, Capped fixed-step loop, Adaptive effects
  • Data and auth: Relational database through an ORM, Unique constraint on (user, question), zod validation on every route handler, Signed JWT sessions in an httpOnly cookie, One-time-code sign-in, Route gate for /play, /me and /admin
  • Services: Transactional email (sign-in codes, prize winners), Object storage for sponsor art, ExcelJS (participant workbook), Serverless hosting
  • Demo build: Vite 6, React 18, React Router 6, TanStack Query 5, i18next 24 (KA/EN), Recharts 2, Radix UI, Tailwind CSS 4, framer-motion, @portfolio/demo-kit mock API and store, Hand-written OOXML and ZIP writers

The demo

A fictional re-skin of a real product we built, under the invented brand Tova, with every name, sponsor and person replaced. The Next.js server is gone: an in-browser mock answers all 41 API routes from seed data compiled into the bundle, so no network request is made. Data lives in memory and resets on reload, there is no real authentication, no email, no storage, and the Excel export is built in the tab. The demo opens signed out on the public landing page, and the demo bar signs you in as any of four roles.