WTF — What The Fun
🎮 What This Project Is
WTF — What The Fun is a single-page, keyboard-first browser game where players chain words by their last letter under a 10-second countdown.
After the first two valid words, three random keys "die" (turn grey with a skull) and become unclickable — forcing the player to craft words that avoid those letters.
The game:
- Validates words asynchronously using a free dictionary API
- Persists personal best scores in
localStorage - Renders a tactile oil-pastel aesthetic UI
🧱 Tech Stack
| Layer | Technology | Purpose |
|---|---|---|
| Framework | TanStack Start v1 | Full-stack React 19 with file-based routing, SSR/SSG, server functions |
| Build Tool | Vite 7 | Dev server, bundling, HMR |
| Styling | Tailwind CSS v4 | Utility-first CSS with custom OKLCH design tokens |
| Router | TanStack Router | Type-safe, file-based routing |
| State | React (useState, useRef) |
Local state; game loop via requestAnimationFrame |
| Backend | None (client-only) | All data stored in localStorage |
| Audio | Web Audio API | Real-time synthesized sound effects |
| Graphics | Canvas2D | Background rendering + particle effects |
| Fonts | Archivo Black + Instrument Serif | Typography via Google Fonts |
🧠 Key Architectural Decisions
🚫 No Backend, No Auth
- Fully local-first architecture
- Scores and dictionary results cached in
localStorage - Game works offline after initial load
⚡ Async Validation (Non-Blocking)
- Words are added instantly as
"pending" - Dictionary API validation runs in the background
- If invalid → game ends after feedback, not during typing
⏸️ Tab Visibility Handling
- Uses
visibilitychangeevent - Pauses
requestAnimationFrameloop - Adjusts timer on resume to prevent unfair penalties
🎨 Dual-Canvas Rendering
- Base Canvas → Static oil-pastel texture (rendered once)
- Overlay Canvas → Particle effects (dynamic)
- Avoids expensive full redraws
🔊 Procedural Audio
- Generated via Web Audio API (oscillators + noise buffers)
- No MP3 assets required
- Lazy initialization on first interaction (autoplay-safe)
📁 File Structure
src/
├── routes/
│ ├── __root.tsx # HTML shell, <Outlet />, error boundaries
│ └── index.tsx # Main game page (state machine + UI)
│
├── components/game/
│ ├── Background.tsx # Dual Canvas: texture + particle FX
│ ├── Keyboard.tsx # 3D keycap grid with dead-key states
│ ├── Stats.tsx # Post-game stat cards
│ ├── TimerRing.tsx # SVG circular countdown
│ ├── ChainRail.tsx # Sliding word history
│ └── HintRotator.tsx # Starter word suggestions
│
├── lib/
│ ├── dictionary.ts # dictionaryapi.dev client + cache
│ ├── sounds.ts # Web Audio synthesizer
│ └── scores.ts # localStorage persistence
│
├── styles.css # Tailwind theme + keycap styles
└── router.tsx # Router bootstrap
🔄 Data Flow (One Round)
Player types word → keydown handler sanitizes input
↓
Space/Enter pressed → submit() runs validation rules
↓
In-memory checks:
- Length
- Starts-with rule
- Repeats
- Dead keys
↓
Word added as "pending" + timer resets (10s)
↓
Async dictionary API request (with AbortController)
↓
├─ ✅ Valid → mark "ok" + particle burst + sound
└─ ❌ Invalid → mark "bad" + end game
↓
Game over → Stats.tsx computes metrics (WPM/CPS)
↓
recordRun() saves best scores to localStorage
✨ Highlights
- ⌨️ Keyboard-first gameplay
- ⚡ Real-time async validation
- 🎨 Custom-rendered aesthetic UI
- 🔊 Fully procedural audio
- 📦 Zero backend dependency
- 📴 Offline-capable after first load
🚀 Philosophy
Fast, tactile, constraint-driven gameplay — powered entirely in the browser.
Built With
- tan
Log in or sign up for Devpost to join the conversation.