Eyeball
Five tasks a day, judged by eye.
Eyeball is a daily game about eyeballing things. Every day there are five short tasks - set an angle, halve a line, fill a glass to 70 %, recall a colour from memory, tap out a rhythm - and every player gets the same five. No ruler, no grid, no live measurements: you judge by eye, and the reveal shows exactly how far off you were. Around that daily round sits a whole game: practice, a 60-second Blitz mode, a monthly eye test, live duels and bot matches, async challenges with friends, daily quests, and your own eyeball to dress up - in English, German, French and Spanish.
What it is
- One round per day. Five tasks drawn from 30 types in six categories - geometry, quantities & proportions, colour, time & motion, optical illusions and memory. The round is generated from the date, and the day turns over at midnight Swiss time.
- Up to 400 points per task. The reveal puts your answer next to the truth and states the error in relative units - degrees, percent, ΔE - never pixels. A round tops out at 2000 and ends with a shareable emoji line, a share image and your place on the daily leaderboard.
- Final answers. Each answer goes to the server and is scored there the moment you submit it. A reload or a second device doesn’t give you another try, and memory tasks never show their stimulus twice.
The task types range from pure geometry (perpendicular, vanishing point, find the centre of a circle from an arc, halve a line three times in a row) through estimation (how many dots, fill the glass, double the area) and colour (recall a colour, guess the RGB values) to timing (stopwatch, intercept a moving target, rhythm) and perception tricks (beat a Müller-Lyer, Ebbinghaus, Ponzo or Delboeuf illusion, where does the line come out behind the bar, compare brightness).
Why it exists
Most people are sure they can spot the middle of a line or a right angle. Eyeball puts a number on it - every day, in about 90 seconds.
That only works if the number is fair. So the play field has no guide lines, no snapping and no live readouts. Errors are measured relatively, so a phone and a desktop monitor play the same game. Tap tasks let you place a point and then drag it from anywhere, so your finger never covers what you’re aiming at. And scores are computed on the server, never trusted from the client.
Core features
| Feature | What it does |
|---|---|
| 👁️ Daily round | Five tasks a day, identical for everyone, with a time limit per task, a daily leaderboard and a streak - missed a day? Rescue the streak for Linsen the day after. |
| 🎯 Reveal & tiers | Your answer next to the truth after every task, graded in five tiers from Bullseye! down to Missed. |
| 🙋 No sign-up needed | Guests get a device-bound account automatically. Signing in later with Google or email carries over rounds, streak and items. |
| 🔁 Practice | Any task type, endlessly - plus a mixed round, a weakness trainer, Zen (no clock, no points, everything mixed) and an archive of every past daily round to replay against your original. |
| ⚡ Blitz | 60 seconds, as many tasks as you can. Good answers build a combo up to ×3, bad ones reset it. Weekly and all-time leaderboards. |
| 🩺 Eye test | Once a month, every task type once. The result is a playful eye age, a bar per category and the change since last month - shareable as an image. |
| ⚔️ Duels | Live 1v1 ranked with Elo and leagues from Bronze to Master, each split into three divisions, an unranked queue, private rooms, and Last Eye Standing: up to 20 players, weakest quarter out after every task. |
| 🤖 Vs. bot | A live opponent that’s about as good as you - it aims at your own average per task type and gets stronger the more you beat it. |
| ✉️ Async duels & bets | Challenge one or more friends to the same five tasks and everyone plays when they like - optionally with a Linsen stake, winner takes the pot. |
| 🗓️ More modes | A weekly challenge with a leaderboard, Survival (endless, three lives, a rising bar), challenge links, and a pass-the-phone party mode for 2-6 players. |
| ✅ Daily quests | Three small quests a day - play, hit, challenge - with one swap per day and a bonus for finishing all three. |
| 📈 Stats & recap | Progress per task type over your last 30 attempts, an activity map, playtime, and a weekly recap of your week that you can share as an animated GIF. |
| 👥 Friends | Friend requests by username, live online status, and invites into rooms and challenges. |
| 🧿 Your own eyeball | The mascot is your avatar. Earn Linsen by playing, level up, unlock 90 achievements, and spend Linsen in the shop on irises, pupils, lids, hats, faces, eye shapes and pets - or try your luck with a lootbox. |
| 🎰 Casino | Eight games - roulette, blackjack, mines, plinko, coin flip and three slot machines - with real casino odds and provably-fair seeds. Linsen only, never real money. |
| 📤 Share | Server-rendered 1080 × 1080 images of your daily round, weekly challenge, eye test or profile, plus the emoji line. |
| 🔔 Reminders | Optional push notifications: the daily round in the evening, a warning before your streak breaks, friend invites and the new weekly challenge. |
| 🌍 Four languages | English, German, French and Spanish - picked from your device, switchable in the settings. |
| 📱 Installable | A PWA that runs without browser bars on Android and iOS, installable with one tap from the settings. |
| 🎵 Sound & haptics | Every sound effect and the menu music are synthesised live with the Web Audio API - no audio files. On Android, reveals, wins and Linsen also vibrate. |
Technical details
Stack
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router), React 19 |
| Language | TypeScript (strict) |
| Styling | Tailwind CSS v4, design tokens as CSS variables |
| Database | PostgreSQL |
| ORM | Drizzle ORM - schema-first, committed migrations, applied automatically on start |
| Auth | Better Auth - Google OAuth, email/username + password, anonymous guests |
| i18n | next-intl - four languages, no locale in the URL |
| Realtime | Server-Sent Events over Postgres LISTEN/NOTIFY |
| Notifications | Web Push (VAPID) via web-push |
| Images | next/og (Satori) for share images and app icons, gifenc for the recap GIF |
| Audio | Web Audio API, synthesised at runtime |
| Testing | Vitest |
| Deployment | Coolify + Nixpacks, no Dockerfile |
Engineering decisions
- Deterministic by date. The daily tasks are generated from the date in Europe/Zurich with a custom PRNG (cyrb128 + mulberry32), never
Math.random. The game logic lives insrc/game/with no React, Next or Node dependencies, so the exact same module runs in the browser and on the server. A determinism test pins fingerprints of individual days and fails on purpose if a change would reshuffle the rounds. - Server-authoritative play. The client sends raw input, never a score. The server regenerates the task, evaluates the answer with the same function and stores it immediately; anything after the time limit plus a two-second grace counts as a timeout. Duel transitions run under row locks with server-clock deadlines, and clients only see other players’ answers once a task is over.
- The server keeps the clock. In Blitz, starting a run sets its end time on the server. Every answer adds a fixed allowance for the round trip and the result flash, and anything that arrives after the end plus a grace period doesn’t count - so a paused or tampered device clock buys nothing.
- A bot that plans ahead. A bot match runs on the normal duel engine. Its answers are planned when the match is created: a target score from your own average per task type, times an adaptive level that moves with your wins and losses, times a random form for the match. The input is a jittered perfect answer that lands near that target. There are deliberately no bets against the bot - its strength follows yours, so a bot made weak on purpose could be farmed.
- Guests are real users. Instead of a local-only guest mode, every guest is an anonymous Better Auth user bound to the device by an httpOnly cookie. Guests stay on the same server-scored path as everyone else, and signing up later simply moves their rounds, items and push subscriptions over.
- Fair across screens. Errors are always relative - degrees, percent of a length, ΔE CIEDE2000 for colour - never pixels. Colours are compared perceptually, not by RGB distance.
- Calibrated scoring. Each task scores
round(400 · exp(-(error / k)²))with its own k per task type: flat at the top, so small errors cost little. A calibration test simulates an average player and keeps every task type between 70 and 80 % of the points. - Realtime without extra infrastructure. Duels, queues and friend presence stream over plain SSE route handlers, fanned out through Postgres
LISTEN/NOTIFY. No websocket server, no Redis - transitions happen on submissions, on timers of open connections, or on a clienttick. - Scheduled push without a cron. Reminders are sent by a timer inside the app server that runs every minute. A log table records every notification per user and day or week, so each one goes out exactly once - even after a restart or with several instances.
- The recap GIF is drawn in the browser. The weekly recap is rendered frame by frame on a canvas with the same timeline as the on-screen animation and encoded with gifenc. It’s built in the background as soon as the page loads, so the native share sheet can open the moment you tap.
- A ledger, not a balance. Linsen are derived from facts - results, runs, matches, achievements, bets - and written once per reason and reference, so rewards can be computed retroactively and are never paid twice. A test keeps the lootbox price above its average refund, so boxes can’t be farmed.
Get & use Eyeball
Play
Open eyeballit.app. No sign-up needed - you play as a guest straight away and can create an account later to keep your streak and stats across devices. Eyeball follows your device’s language (English, German, French or Spanish); you can change it in the settings.
Install as an app
- Android (Chrome): Settings → Get the Eyeball app, or the install banner.
- iOS (Safari): share button → Add to Home Screen. Install it this way if you want reminders - iOS only delivers push notifications to installed web apps.
How to play
- Tap Let’s go on the home screen to start today’s round.
- Each task tells you its goal - an angle, a ratio, a colour to match. Drag, tap or time your answer, then press Done before the timer runs out.
- After every task the reveal shows how close you were. After five, share your score and come back tomorrow.
Local setup
Needs Node.js 22+, pnpm and a PostgreSQL database.
git clone https://github.com/elRatto21/Eyeball
cd Eyeball
pnpm install
cp .env.example .env.local # DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL
pnpm dev # http://localhost:3000
Pending migrations are applied automatically when the server starts. Push notifications additionally need a VAPID key pair (pnpm dlx web-push generate-vapid-keys).
Eyeball - only your eye counts.