A full-stack MERN application bringing the classic "Heads Up!" party game to your browser and phone supercharged with AI-powered card generation, smart content validation, and real-time motion controls.
- Overview
- How the Game Works
- AI Integration
- Tech Stack
- Database Schema
- API Reference
- Getting Started
- Environment Variables
- Game Flow
- Device Support
Heads Up! is a real-time party guessing game where one player holds their phone to their forehead while others give clues. The player tilts the phone down to mark a correct answer and up to pass. The goal is to guess as many cards as possible before the timer runs out.
This version goes beyond the classic by integrating Groq AI at three key points:
- Deck Generation — Describe a theme, get a full deck of cards in seconds
- Content Validation — AI reviews every card before it goes live to ensure quality and appropriateness
- Smart Gameplay — Cards are shuffled randomly, sessions are tracked, and stats update automatically after every game
- Browse or create a deck
- Tap a deck to open the setup screen
- Choose a timer — 30s, 45s, or 90s
- Hold the phone vertically against your forehead
- The app detects your phone angle using the DeviceOrientation API
- When you tilt the phone face-down → Correct
- When you tilt the phone face-up → Pass
- The next card appears automatically after each action
- The game ends when the timer runs out or all cards are played
- Results screen shows your score and a full card-by-card breakdown
The same game works on desktop using keyboard controls:
→orD— Correct←orA— Pass
On-screen Pass and Correct buttons are always visible as a universal tap/click fallback on all devices.
The app uses the device beta axis from the DeviceOrientation API to detect phone angle:
| Beta Angle | Zone | Action |
|---|---|---|
0° – 30° |
Face up | Pass |
30° – 65° |
Transitioning | Waiting |
65° – 115° |
Upright (forehead) | Valid hold position |
115° – 140° |
Transitioning | Waiting |
> 140° |
Face down | Correct |
Before the game starts, the app shows a live position validator that waits for the phone to be held correctly at 65°–115° for at least 0.9 seconds before beginning the countdown. If the phone is lowered below 45° for more than 0.8 seconds mid-game, the game automatically pauses and resumes when the phone is raised back to position.
Users enter a theme (e.g. "90s Bollywood movies", "Premier League footballers", "SpaceX missions") and a card count. The backend sends a structured prompt to the Groq API:
Generate [count] Heads Up! game cards for the theme: "[theme]".
Return a JSON array of objects with a "Word" field only.
Words should be well-known, varied in difficulty, and fun to guess.
The response streams back as a JSON array and is parsed, deduplicated, and trimmed before being returned to the frontend. Users can then edit, delete, or regenerate individual cards before saving the deck.
Rate limiting: AI generation is limited per user session to prevent abuse.
Before any deck is saved as public, every card passes through an AI content check. The validator sends the full card list to Claude and receives a simple { valid: Boolean, flagged: String[] } response indicating whether any cards contain inappropriate, offensive, or low-quality content.
Review these Heads Up! game cards for a family-friendly party game.
Return JSON: { "valid": true/false, "flagged": ["word1", "word2"] }
Flag any cards that are offensive, inappropriate, or not suitable for general audiences.
Cards that are flagged are highlighted in the deck editor before the user can publish. Decks with flagged cards cannot be set to IsPublic: true until the flagged cards are removed or edited.
| Layer | Technology |
|---|---|
| Frontend | React 18 + Vite, React Router v6, Tailwind CSS |
| Backend | Node.js, Express.js |
| Database | MongoDB with Mongoose ODM |
| Authentication | JWT stored in httpOnly cookies, bcrypt password hashing |
| AI | Groq API (card generation + validation) |
| Mobile APIs | DeviceOrientation API, WakeLock API, Screen Orientation API |
User {
_id ObjectId (auto)
Username String, unique, required, trim, min:3 max:30
Password String, required, hashed (bcrypt)
AvatarColor String (hex or CSS gradient)
CreatedAt Date (auto)
UpdatedAt Date (auto)
Stats {
GamesPlayed Number, default: 0
TotalCorrect Number, default: 0
TotalPasses Number, default: 0
}
SavedDecks [ObjectId] → ref: Deck
MyDecks [ObjectId] → ref: Deck
History [
{
DeckId ObjectId → ref: Deck
PlayedAt Date
}
]
}
Deck {
_id ObjectId (auto)
Title String, required, trim, max:60
Description String, trim, max:200
BackgroundColor String (hex or CSS gradient)
CreatedAt Date (auto)
UpdatedAt Date (auto)
CreatedBy ObjectId → ref: User (null = system deck)
IsSystem Boolean, default: false
Cards [
{
Word String, required, trim, max:60
}
] max 20 cards enforced via validator
WordCount Number (auto-synced via pre-save hook)
Likes Number, default: 0
Tags [String]
Plays Number, default: 0
IsPublic Boolean, default: false
}
Note:
WordCountis never set manually. A Mongoosepre('save')hook on the Deck model automatically syncs it withCards.lengthon every save.
Note:
CreatedBy: nullmarks a deck as a built-in system deck. System decks (IsSystem: true) cannot be edited or deleted by any user.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/auth/register |
No | Register new user. Body: { Username, Password } |
POST |
/api/auth/login |
No | Login. Returns httpOnly JWT cookie. Body: { Username, Password } |
POST |
/api/auth/logout |
Yes | Clears JWT cookie |
GET |
/api/auth/me |
Yes | Returns current authenticated user object |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/decks |
No | Paginated public decks. Query: ?page=1&limit=12&tag=movies&sort=plays |
GET |
/api/decks/:id |
No | Single deck by ID |
POST |
/api/decks |
Yes | Create a new deck. Body: { Title, Description, BackgroundColor, Cards, Tags, IsPublic } |
PUT |
/api/decks/:id |
Yes | Edit deck — owner only |
DELETE |
/api/decks/:id |
Yes | Delete deck — owner only |
POST |
/api/decks/:id/like |
Yes | Toggle like on a deck |
POST |
/api/decks/:id/save |
Yes | Toggle save deck to user's SavedDecks |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/ai/generate-cards |
Yes | Generate cards via Claude. Body: { theme: String, count: Number }. Returns { cards: [{ Word }] } |
POST |
/api/ai/check-cards |
Yes | Validate card list. Body: { cards: [{ Word }] }. Returns { valid: Boolean, flagged: [String] } |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/session/start |
Yes | Start a session. Body: { DeckId, TimerDuration }. Returns { SessionId } |
POST |
/api/session/end |
Yes | End session + update all stats. Body: { SessionId, DeckId, TimerUsed, Results: [{ Word, Result }], Score: { Correct, Passed } } |
/api/session/endside effects: IncrementsDeck.Plays, updatesUser.Stats.GamesPlayed,User.Stats.TotalCorrect,User.Stats.TotalPasses, and pushes{ DeckId, PlayedAt }intoUser.History— all in a single atomic operation.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/users/:id/profile |
No | Public profile — username, avatar, stats, public decks |
GET |
/api/users/me/decks |
Yes | All decks created by the logged-in user |
GET |
/api/users/me/saved |
Yes | All decks saved by the logged-in user |
- Node.js v18 or higher
- MongoDB — local instance or MongoDB Atlas
- Groq API Key — from console.groq.com
git clone https://github.com/your-username/heads-up.git
cd heads-upBackend:
cd server
npm installFrontend:
cd client
npm installCreate a .env file inside the server/ folder (see Environment Variables below).
Start the backend (from server/):
npm run devRuns on
http://localhost:5000
Start the frontend (from client/):
npm run devRuns on
http://localhost:5173
Run both together (from root, if concurrently is set up):
npm run devCreate server/.env with the following:
# Server
PORT=5000
NODE_ENV=development
# MongoDB
MONGO_URI=mongodb://localhost:27017/headsup
# or your Atlas connection string:
# MONGO_URI=mongodb+srv://<user>:<password>@cluster.mongodb.net/headsup
# JWT
JWT_SECRET=your_super_secret_jwt_key_here
JWT_EXPIRES_IN=7d
# GROQ AI
GROQ_API_KEY=your_groq_api_keyCreate client/.env with the following:
VITE_API_BASE_URL=http://localhost:5000| Feature | API Used | iOS | Android | Desktop |
|---|---|---|---|---|
| Tilt controls | DeviceOrientation API |
Yes (permission prompt) | Yes | No |
| Keyboard controls | keydown events |
No | No | Yes |
| Tap/click buttons | onPointerDown |
Yes | Yes | Yes |
| Keep screen on | WakeLock API |
Yes Safari 16.4+ | Yes | Yes |
| iOS permission | DeviceOrientationEvent.requestPermission() |
Required | N/A | N/A |
iOS Note: Safari on iOS 13+ requires an explicit user gesture to grant
DeviceOrientationpermission. The app triggers this automatically on the "Start Game" button tap — no manual settings change needed.
Manual entry ──┐
├──▶ Deck Editor ──▶ AI Validation ──▶ Save / Publish
AI generation ──┘ (check-cards)
- User creates a deck manually or enters a theme for AI generation
- AI generates up to 20 cards via
POST /api/ai/generate-cards - User edits, deletes, or regenerates individual cards
- On publish, cards are validated via
POST /api/ai/check-cards - Flagged cards are highlighted — deck cannot go public until resolved
- Once clean, deck is saved with
IsPublic: trueand appears in community browse
Built with ❤️ using the MERN stack