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 visibilitychange event
  • Pauses requestAnimationFrame loop
  • 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
Share this project:

Updates