A low-friction, face-to-face icebreaker application for classrooms. Users form groups of 2-4 using a request/accept system, answer "Would you rather?" questions, and have real conversations while devices fade into the background.
- Avatar Identity: Users select emoji avatars and get unique 2-digit codes (10-99)
- Group Formation: Request/accept system for 2-4 member groups with spam prevention
- Question System: Groups receive the same A/B "Would you rather?" question
- Self-paced: Groups complete conversations at their own pace
- State Machine: 8-state XState flow with session persistence
- Host Dashboard: Create rooms, start/stop Phase 1, adjust time, view live stats
- Projector Display: Large-screen real-time view of room activity
- Dark Mode: Full dark mode support across all pages
- Auto-generation: Creates slideshow from Phase 1 data (≥60% response threshold)
- Animated Reveals: 6-second race effect showing percentage breakdowns
- Optional Voting: Users can vote on which option they think was more popular
- Leaderboard: Top-5 scoring users at game end
- Manual Navigation: Host controls forward/backward through slides
- Frontend: Next.js 14 (App Router), TypeScript, Tailwind CSS
- Backend: Convex (real-time database and functions)
- State Management: XState (user flow state machine)
- Animations: Framer Motion
- UI Libraries: canvas-confetti
- Node.js 18+ and npm
- A Convex account (free tier works great)
- Install dependencies:
npm install- Set up Convex:
npx convex devThis will:
- Prompt you to log in to Convex (or create an account)
- Create a new Convex project
- Generate a
.env.localfile with your deployment URL - Start the Convex development server
- Run the development server:
In a separate terminal:
npm run dev- Open your browser:
- Landing page: http://localhost:3000
- Host view: http://localhost:3000/host
- User view: http://localhost:3000/user
- Projector view: http://localhost:3000/projector
- Go to http://localhost:3000/host
- Click "Create New Room"
- Enter room name and configure max group size (2-4 members, default 4)
- Share the 4-letter room code (e.g., "ABCD") with users
- Save your 4-digit PIN (needed to control the room)
- Click "Start Phase 1" when ready
- Monitor active groups and completed conversations in real-time
- After Phase 1 ends, preview and start the Phase 2 slideshow game
- Go to http://localhost:3000 (landing page)
- Enter the 4-letter room code (e.g., "ABCD")
- Select an emoji avatar from 3 options
- You'll receive a unique 2-digit code (e.g., "42")
- Browse available users and send a join request to someone
- Wait for acceptance or accept incoming requests
- Answer the "Would you rather?" A/B question with your group
- Talk face-to-face about your answers
- Click "Complete Group" when done to meet new people
- After Phase 1 ends, optionally vote during the Phase 2 slideshow
- Go to http://localhost:3000/projector
- Enter the 4-letter room code (e.g., "ABCD")
- Display shows:
- Phase 1: Room name, status badges, countdown timer, user count, active/completed groups
- Phase 2: Synchronized slideshow with animated percentage reveals
ice/
├── app/
│ ├── page.tsx # Landing page with 4-letter code entry
│ ├── user/page.tsx # User view with 8-state XState flow
│ ├── host/page.tsx # Host dashboard with Phase 1/2 controls
│ ├── projector/page.tsx # Projector display (Phase 1 stats + Phase 2 slideshow)
│ └── ConvexClientProvider.tsx # Providers (Convex, Toast, ConfirmDialog)
├── components/
│ ├── Keypad.tsx # Reusable 4-letter code entry
│ ├── SlideshowQuestion.tsx # Phase 2 slideshow with animated reveals
│ ├── RequestBanner.tsx # Join request UI
│ ├── Toast.tsx # Toast notification system
│ ├── ConfirmDialog.tsx # Confirmation dialog provider
│ └── LoadingSpinner.tsx # Loading indicator
├── convex/
│ ├── schema.ts # 10-table database schema
│ ├── rooms.ts # Room management
│ ├── users.ts # User join/lookup
│ ├── groups.ts # Group formation, questions, answers
│ ├── questions.ts # Question bank management
│ ├── games.ts # Phase 2 game generation & voting
│ ├── crons.ts # Cleanup jobs
│ └── testData.ts # Test data generators
└── lib/
├── userStateMachine.ts # XState state machine (8 states)
└── avatars.ts # 200+ emoji avatars
- Landing page: Minimal 4-letter code entry (uppercase letters)
- Auto-advance: Automatically navigates when 4 letters are entered
- No buttons: One-tap entry reduces friction
- Request/accept system: Browse available users and send join requests
- Flexible group sizes: 2-4 members (configurable per room, default 4)
- Spam prevention: Exponential backoff (1s → 2s → 4s → 8s → 16s) between requests
- Mutual requests: If two users request each other, auto-accepted
- Request expiration: 30-second timeout on pending requests
- Emoji-based: 200+ emoji avatars for playful, anonymous identity
- Random selection: Users pick from 3 randomly presented options
- 2-digit codes: Each user gets unique code (10-99) for easy identification
- "Would you rather?" format: A/B choice questions for easy answering
- Same question per group: All group members see the same question
- Rotation tracking: Avoids last 3 questions per user to prevent repeats
- 35 pre-seeded questions: Across multiple categories
- Follow-up prompts: Seed face-to-face conversations
User flow: not_joined → browsing → waiting_for_acceptance → question_active → session_locked → Phase 2 states
Convex handles all real-time updates automatically:
- Group requests and acceptances
- Question delivery
- Answer submissions
- Room stats
- Phase 1/2 transitions
- Slideshow synchronization
The app comes pre-seeded with 35 "Would you rather?" questions:
- Would you rather have the ability to fly OR be invisible?
- Would you rather explore space OR the deep ocean?
- Would you rather read minds OR see the future?
- Would you rather live in the mountains OR by the beach?
- Would you rather have more time OR more money?
- And 30 more...
Hosts can manage questions through the Convex dashboard.
npm run dev # Start Next.js dev server
npm run build # Build for production
npm run start # Start production server
npm run lint # Run ESLint
npx convex dev # Start Convex dev server
npx convex dashboard # Open Convex dashboard
# E2E Testing
npm run test:e2e # Run E2E tests (headless)
npm run test:e2e:ui # Run E2E tests with Playwright UI
npm run test:e2e:headed # Run E2E tests in headed mode (see browser)
npm run test:e2e:debug # Run E2E tests with step-by-step debuggingThe project includes comprehensive end-to-end tests using Playwright that simulate real multiplayer scenarios.
Prerequisites:
- Start the Convex dev server:
npx convex dev - Start the Next.js dev server:
npm run dev - In a separate terminal, run tests:
npm run test:e2e
Test Coverage:
- ✅ Happy path: Host creates room + 4 players join and browse
- ✅ Room capacity: Enforces 35 player limit
- ✅ Late joiners: Players can join after game starts
- ✅ Avatar uniqueness: System provides unique avatar options
- ✅ Session lock: Phase transitions work correctly
Test Helpers:
The tests/e2e/helpers.ts module provides high-level functions for common actions:
createHostRoom(page)- Create room and get room codecreatePlayers(browser, configs)- Spawn multiple isolated player sessionsstartGame(hostPage)- Start Phase 1 from host viewwaitForPhase(page, phase)- Wait for specific game statewaitForToast(page, type, message)- Assert on toast notifications
Data Test IDs:
All interactive elements have data-testid attributes for stable selection:
- Landing page:
room-code-input-{0-3} - Host view:
create-room-button,room-code,start-game-button - User view:
avatar-selection,avatar-option-{emoji},available-users-list - Questions:
question-text,question-option-a,question-option-b - Toasts:
toast-{type},toast-message
Writing New Tests:
See tests/e2e/multiplayer.spec.ts for examples. Each test should:
- Create isolated browser contexts for each player
- Use helper functions for common actions
- Assert on visible UI elements (not internal state)
- Clean up contexts in
finallyblocks
Note on Room Capacity:
The backend enforces a maximum of 35 players per room to match typical classroom sizes. This is checked in the joinRoom mutation and displays a user-friendly error toast when exceeded.
When deploying to production (e.g., Vercel), you need to deploy both your Convex backend and Next.js frontend:
1. Deploy Convex Functions First:
npm run deploy:convex
# or manually: npx convex deploy --yesThis pushes your Convex functions to your production Convex deployment. Your production Convex URL will be something like https://your-deployment.convex.cloud.
2. Set Environment Variables in Vercel:
- Go to your Vercel project → Settings → Environment Variables
- Add
NEXT_PUBLIC_CONVEX_URLwith your production Convex URL (not the dev URL!) - Make sure it's enabled for the Production environment
3. Deploy Next.js to Vercel:
- Push to your main branch (if auto-deploy is enabled), or
- Manually trigger a deployment from the Vercel dashboard
Common Deployment Issue:
If you get errors like [CONVEX Q(functionName)] Server Error, it usually means:
- You forgot to run
npm run deploy:convexafter making changes to Convex functions - Your Vercel environment variable is pointing to the dev URL instead of prod URL
Quick Deploy Script:
npm run deploy:prod # Deploys Convex, then builds Next.jsYou can temporarily close public access to the app (e.g., between class sessions) while maintaining access for yourself as a developer.
Add these to your Vercel project → Settings → Environment Variables:
APP_CLOSED- Set to"true"or"1"to close the app, or"false"(or leave unset) to open itAPP_BYPASS_SECRET(optional) - A secret token that allows you to bypass the closed state
When APP_CLOSED="true":
- All public routes show a "We'll be back soon" page
- The app is completely inaccessible to the public
- Next.js assets and the closed page itself are still served
When APP_CLOSED="false" or unset:
- The app behaves normally
- No blocking or redirection occurs
Bypass Access (when APP_BYPASS_SECRET is set):
- Visit
https://your-app.vercel.app/bypass?token=YOUR_SECRET - This sets a bypass cookie that lasts 7 days
- You'll be redirected to the home page and can use the app normally
- All subsequent navigation works as if the app is open
To close the app:
- Go to Vercel → Your Project → Settings → Environment Variables
- Set
APP_CLOSEDto"true" - Redeploy (or wait for auto-redeploy if enabled)
To re-open the app:
- Set
APP_CLOSEDto"false"or remove the variable - Redeploy
To access the app while it's closed:
- Set
APP_BYPASS_SECRETto a secret value (e.g.,"my-secret-123") - Redeploy
- Visit
https://your-app.vercel.app/bypass?token=my-secret-123 - You'll be able to use the app normally for 7 days
Security Note: The bypass mechanism is intentionally simple and not cryptographically secure. It's designed to keep casual users out, not to provide high-security access control. Don't rely on it for sensitive data protection.
- Question Management UI: Host interface to add/edit/disable questions in-app
- QR Code Joining: Scan code instead of typing 4 letters
- Advanced Analytics: Export CSV of aggregates, category breakdowns
- Multi-session Support: Track rooms over time with history
- Custom Branding: Configurable themes and room backgrounds
- Mobile App: Native iOS/Android apps for better performance
- Ensure
npx convex devis running in a separate terminal - Check that
.env.localhasNEXT_PUBLIC_CONVEX_URLset - Restart the Next.js dev server after Convex initialization
- Verify Phase 1 is "Active" in host dashboard
- Check that room code is entered correctly (4 letters, uppercase)
- Ensure Convex dev server is running
- Check that room hasn't expired (48-hour TTL)
- User must send request and wait for acceptance
- Can't request to join full groups (check maxGroupSize setting)
- Spam prevention may be active (exponential backoff between requests)
- Requests expire after 30 seconds
- Ensure Phase 1 had sufficient participation (≥60% response rate per question)
- Game is auto-generated when Phase 1 stops
- Check Convex dashboard logs for any errors
MIT
This is an MVP implementation. Contributions welcome for Phase 2 features, improved UX, and bug fixes!