Present or Panic is a multiplayer PowerPoint Karaoke web app for local parties, workshops, and chaotic presentation nights.
One screen runs the host view, players join on their phones, and the app pulls random presentations from Google Drive. A random player has to present a random deck, everyone else votes, and the game keeps score across multiple rounds.
- Great for parties, team events, workshops, classrooms, and improv nights
- Runs with one host screen and multiple phone controllers
- Uses your own Google Drive decks, PDFs, and Google Slides
- Ships with a public Docker image for fast self-hosting
- Includes CI validation and protected-main workflow setup for safer releases
- Copy docker-compose.yml
- Set your Google Drive environment values
- Start the app:
docker-compose up -dThe compose file already pulls the public image from:
ghcr.io/raphaelbleier/powerpoint_karaoke:latest
docker pull ghcr.io/raphaelbleier/powerpoint_karaoke:latest
docker run -d \
--name present_or_panic \
-p 8080:8080 \
-e PORT=8080 \
-e POWERPOINTS_FOLDER_ID=your-folder-id \
-e GOOGLE_API_KEY=your-api-key \
ghcr.io/raphaelbleier/powerpoint_karaoke:latestThen open the app on:
http://localhost:8080
For a public Google Drive folder:
POWERPOINTS_FOLDER_IDGOOGLE_API_KEY
For a private Google Drive folder:
POWERPOINTS_FOLDER_IDGOOGLE_SERVICE_ACCOUNT_EMAILGOOGLE_PRIVATE_KEY
- Create rooms with 4-digit room codes
- Host screen for lobby, presentation, voting, and leaderboard phases
- Phone controller view for players joining the room
- Random presenter selection per round
- Random presentation selection from chosen Google Drive categories
- Multi-round games with configurable
maxRounds - End-of-game leaderboard with podium display
- Restart game with same setup or start fresh
- Socket.IO-based live state sync
- Host rejoin detection after refresh
- Player reconnect support with persisted browser identity
- Grace-period disconnect cleanup for stale player sessions
- Room code normalization on client and server
- Host-only game phase control
- Presenter and host slide control support
- Google Drive folders are treated as categories
- Supports PDF presentations
- Supports Google Slides presentations
- PDF proxy endpoint avoids common CORS problems
- Google Drive content cache refreshes automatically every 5 minutes
- Manual refresh endpoint available on the backend
- Animated React UI with Framer Motion
- Dedicated host and controller interfaces
- Voting flow with 1–5 star scoring
- Score calculation based on average vote × 10
- Responsive phone-first controller experience
- Presentation timer with automatic countdown (host + controller)
- Frontend error boundary fallback screen for unexpected UI crashes
- Stronger player-name sanitization and validation in join flow
- Docker multi-stage build
docker-compose.ymlfor self-hosting- GitHub Actions workflow for publishing to GHCR
- Public container image published to
ghcr.io/raphaelbleier/powerpoint_karaoke:latest - Works well for LAN / same-network play
- Full playable game loop: lobby → presentation → voting → leaderboard
- Host authority built into the socket flow
- Reconnect-friendly player identity via browser persistence
- Google Drive-backed category and presentation loading
- Backend unit + socket-flow integration tests running in CI before image publishing
- Public GHCR package for quick deployment
- Full playable game loop: lobby → presentation → voting → leaderboard
- Phone controller flow with reconnect-friendly browser identity
- Google Drive categories with PDF and Google Slides support
- Host fast-join QR code for phone access from the lobby screen
- Public GHCR image, Docker deployment, CI validation, and branch-protection templates
- Better empty states when Google Drive is not configured
- Improved room and session resilience around reconnect edge cases
- Host-side presentation timer presets (quick toggle buttons)
- Host moderation actions (kick/mute voting)
- In-room reconnect indicator for temporarily disconnected users
- Spectator mode for view-only participants
- Custom scoring options for hosts
- Category weighting for more tailored randomness
- Browser E2E coverage with Playwright
- Persistent room storage option (Redis/Postgres)
- Per-round presenter history + analytics export
- The host creates a room on the main screen.
- Players join the room from their phones using the 4-digit code.
- The host selects one or more presentation categories from Google Drive.
- The app randomly chooses a player and a presentation.
- The selected player presents while controlling slides from their phone.
- The rest of the group votes from 1 to 5 stars.
- Scores are tallied and the next round begins.
- After the configured number of rounds, the final leaderboard is shown.
- Frontend: React, Vite, React Router, Framer Motion, Lucide React
- Backend: Node.js, Express, Socket.IO, Google APIs
- Content source: Google Drive
- Deployment: Docker + GitHub Container Registry
backend/
index.js # Express API, Google Drive integration, Socket.IO game logic
frontend/src/
socket.js # socket singleton, room normalization, persisted identity
components/
Home.jsx # landing page / room creation / join
HostView.jsx # host screen for all game phases
ControllerView.jsx # player phone controller
Slideshow.jsx # presentation display
planning/
task_plan.md
findings.md
progress.md
The backend reads presentations from a single root Google Drive folder.
- Each subfolder becomes a category
- PDFs and Google Slides files inside those folders become playable presentations
Example:
Present or Panic
├── Startups/
│ ├── pitch-deck.pdf
│ └── quarterly-strategy.google-slides
└── Fun/
└── vacation-photos.pdf
Use this if the folder is shared as “anyone with the link can view”.
Required:
GOOGLE_API_KEYPOWERPOINTS_FOLDER_ID
Use this if the folder must stay private.
Required:
GOOGLE_SERVICE_ACCOUNT_EMAILGOOGLE_PRIVATE_KEYPOWERPOINTS_FOLDER_ID
You must also share the Google Drive folder with the service account email address.
Set these in backend/.env or in your container environment:
| Variable | Required | Description |
|---|---|---|
PORT |
No | Backend port, defaults to 8080 |
POWERPOINTS_FOLDER_ID |
Yes | Google Drive root folder ID |
GOOGLE_API_KEY |
Optional* | For public Drive folders |
GOOGLE_SERVICE_ACCOUNT_EMAIL |
Optional* | For private Drive folders |
GOOGLE_PRIVATE_KEY |
Optional* | Private key for the service account |
* Use either GOOGLE_API_KEY or the service-account pair.
- Node.js 18+
- A Google Drive folder with presentations
cd backend
npm install
cd ../frontend
npm installCreate backend/.env and set at least:
PORT=8080
POWERPOINTS_FOLDER_ID=your-folder-id
GOOGLE_API_KEY=your-api-keyOr use the service-account variables instead of GOOGLE_API_KEY.
cd backend
npm run devcd frontend
npm run dev- Host view:
http://localhost:5173 - Backend API:
http://localhost:8080
The repository and GHCR package are public, so you can pull the latest image directly from GitHub:
docker pull ghcr.io/raphaelbleier/powerpoint_karaoke:latestThe included docker-compose.yml already points at the published image. Set your environment values, then run:
docker-compose up -dYou can also run the published image directly:
docker run -d \
--name present_or_panic \
-p 8080:8080 \
-e PORT=8080 \
-e POWERPOINTS_FOLDER_ID=your-folder-id \
-e GOOGLE_API_KEY=your-api-key \
ghcr.io/raphaelbleier/powerpoint_karaoke:latestFor a private Google Drive folder, replace GOOGLE_API_KEY with GOOGLE_SERVICE_ACCOUNT_EMAIL and GOOGLE_PRIVATE_KEY.
The repository includes .github/workflows/docker-publish.yml.
On every push to main, GitHub Actions builds the Docker image and publishes it to:
ghcr.io/raphaelbleier/powerpoint_karaoke:latest
These are known limitations in the current version:
- Room state is stored in memory only; a server restart removes active rooms
- Disconnect cleanup uses an in-memory grace period and is reset on server restart
- No spectator mode yet
- Browser E2E tests are still missing
This project is already playable and covers the full core loop:
- lobby
- presentation
- voting
- score tallying
- leaderboard
- replay / new game
Current release state:
- public repository
- public GHCR image
- automated smoke-test gate in CI (backend tests + frontend lint + frontend build)
- ready for self-hosted launches
This project is licensed under the MIT License. See LICENSE.
This repository includes:
- a code owner file at
.github/CODEOWNERS - a main-branch ruleset template at
.github/rulesets/main-branch-protection.json
The intended protection policy for main is:
- pull requests only
- passing
smoke-testsstatus check required - repository owner approval required via code owner review
- no force pushes
- no branch deletion