{"id":48386184,"url":"https://github.com/raphaelbleier/powerpoint_karaoke","last_synced_at":"2026-04-05T22:13:34.428Z","repository":{"id":343937067,"uuid":"1179036377","full_name":"raphaelbleier/powerpoint_karaoke","owner":"raphaelbleier","description":"Present or Panic is a multiplayer PowerPoint Karaoke web app for local parties, workshops, and chaotic presentation nights.","archived":false,"fork":false,"pushed_at":"2026-03-12T11:58:36.000Z","size":3496,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-12T17:49:14.744Z","etag":null,"topics":["karaoke-application","powerpoint","powerpoint-slides"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/raphaelbleier.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-11T16:12:37.000Z","updated_at":"2026-03-12T11:58:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/raphaelbleier/powerpoint_karaoke","commit_stats":null,"previous_names":["raphaelbleier/powerpoint_karaoke"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/raphaelbleier/powerpoint_karaoke","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raphaelbleier%2Fpowerpoint_karaoke","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raphaelbleier%2Fpowerpoint_karaoke/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raphaelbleier%2Fpowerpoint_karaoke/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raphaelbleier%2Fpowerpoint_karaoke/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/raphaelbleier","download_url":"https://codeload.github.com/raphaelbleier/powerpoint_karaoke/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raphaelbleier%2Fpowerpoint_karaoke/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31451781,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"ssl_error","status_checked_at":"2026-04-05T21:22:51.943Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["karaoke-application","powerpoint","powerpoint-slides"],"created_at":"2026-04-05T22:13:32.334Z","updated_at":"2026-04-05T22:13:33.391Z","avatar_url":"https://github.com/raphaelbleier.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"frontend/public/brand/present-or-panic-logo.svg\" alt=\"Present or Panic logo\" width=\"420\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/raphaelbleier/powerpoint_karaoke/actions/workflows/docker-publish.yml\"\u003e\u003cimg src=\"https://github.com/raphaelbleier/powerpoint_karaoke/actions/workflows/docker-publish.yml/badge.svg\" alt=\"CI and Docker publish status\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/raphaelbleier/powerpoint_karaoke/pkgs/container/powerpoint_karaoke\"\u003e\u003cimg src=\"https://img.shields.io/badge/GHCR-public-blue\" alt=\"GHCR public package\" /\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-green.svg\" alt=\"MIT License\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# Present or Panic\n\nPresent or Panic is a multiplayer PowerPoint Karaoke web app for local parties, workshops, and chaotic presentation nights.\n\nOne 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.\n\n## Why Present or Panic?\n\n- Great for parties, team events, workshops, classrooms, and improv nights\n- Runs with one host screen and multiple phone controllers\n- Uses your own Google Drive decks, PDFs, and Google Slides\n- Ships with a public Docker image for fast self-hosting\n- Includes CI validation and protected-main workflow setup for safer releases\n\n## Quick Start\n\n### Run the public image with Docker Compose\n\n1. Copy [docker-compose.yml](docker-compose.yml)\n2. Set your Google Drive environment values\n3. Start the app:\n\n```bash\ndocker-compose up -d\n```\n\nThe compose file already pulls the public image from:\n\n```text\nghcr.io/raphaelbleier/powerpoint_karaoke:latest\n```\n\n### Run the public image directly\n\n```bash\ndocker pull ghcr.io/raphaelbleier/powerpoint_karaoke:latest\n\ndocker run -d \\\n  --name present_or_panic \\\n  -p 8080:8080 \\\n  -e PORT=8080 \\\n  -e POWERPOINTS_FOLDER_ID=your-folder-id \\\n  -e GOOGLE_API_KEY=your-api-key \\\n  ghcr.io/raphaelbleier/powerpoint_karaoke:latest\n```\n\nThen open the app on:\n\n- `http://localhost:8080`\n\n### Minimum required environment\n\nFor a public Google Drive folder:\n\n- `POWERPOINTS_FOLDER_ID`\n- `GOOGLE_API_KEY`\n\nFor a private Google Drive folder:\n\n- `POWERPOINTS_FOLDER_ID`\n- `GOOGLE_SERVICE_ACCOUNT_EMAIL`\n- `GOOGLE_PRIVATE_KEY`\n\n## Features\n\n### Core Gameplay\n- Create rooms with 4-digit room codes\n- Host screen for lobby, presentation, voting, and leaderboard phases\n- Phone controller view for players joining the room\n- Random presenter selection per round\n- Random presentation selection from chosen Google Drive categories\n- Multi-round games with configurable `maxRounds`\n- End-of-game leaderboard with podium display\n- Restart game with same setup or start fresh\n\n### Real-Time Multiplayer\n- Socket.IO-based live state sync\n- Host rejoin detection after refresh\n- Player reconnect support with persisted browser identity\n- Grace-period disconnect cleanup for stale player sessions\n- Room code normalization on client and server\n- Host-only game phase control\n- Presenter and host slide control support\n\n### Presentation Handling\n- Google Drive folders are treated as categories\n- Supports PDF presentations\n- Supports Google Slides presentations\n- PDF proxy endpoint avoids common CORS problems\n- Google Drive content cache refreshes automatically every 5 minutes\n- Manual refresh endpoint available on the backend\n\n### UI / Experience\n- Animated React UI with Framer Motion\n- Dedicated host and controller interfaces\n- Voting flow with 1–5 star scoring\n- Score calculation based on average vote × 10\n- Responsive phone-first controller experience\n- Presentation timer with automatic countdown (host + controller)\n- Frontend error boundary fallback screen for unexpected UI crashes\n- Stronger player-name sanitization and validation in join flow\n\n### Deployment\n- Docker multi-stage build\n- `docker-compose.yml` for self-hosting\n- GitHub Actions workflow for publishing to GHCR\n- Public container image published to `ghcr.io/raphaelbleier/powerpoint_karaoke:latest`\n- Works well for LAN / same-network play\n\n## Highlights\n\n- Full playable game loop: lobby → presentation → voting → leaderboard\n- Host authority built into the socket flow\n- Reconnect-friendly player identity via browser persistence\n- Google Drive-backed category and presentation loading\n- Backend unit + socket-flow integration tests running in CI before image publishing\n- Public GHCR package for quick deployment\n\n## Roadmap\n\n### Current\n- Full playable game loop: lobby → presentation → voting → leaderboard\n- Phone controller flow with reconnect-friendly browser identity\n- Google Drive categories with PDF and Google Slides support\n- Host fast-join QR code for phone access from the lobby screen\n- Public GHCR image, Docker deployment, CI validation, and branch-protection templates\n\n### In Progress\n- Better empty states when Google Drive is not configured\n- Improved room and session resilience around reconnect edge cases\n\n### Planned\n- Host-side presentation timer presets (quick toggle buttons)\n- Host moderation actions (kick/mute voting)\n- In-room reconnect indicator for temporarily disconnected users\n\n### Future\n- Spectator mode for view-only participants\n- Custom scoring options for hosts\n- Category weighting for more tailored randomness\n- Browser E2E coverage with Playwright\n- Persistent room storage option (Redis/Postgres)\n- Per-round presenter history + analytics export\n\n## How the Game Works\n\n1. The host creates a room on the main screen.\n2. Players join the room from their phones using the 4-digit code.\n3. The host selects one or more presentation categories from Google Drive.\n4. The app randomly chooses a player and a presentation.\n5. The selected player presents while controlling slides from their phone.\n6. The rest of the group votes from 1 to 5 stars.\n7. Scores are tallied and the next round begins.\n8. After the configured number of rounds, the final leaderboard is shown.\n\n## Architecture\n\n### Stack\n- Frontend: React, Vite, React Router, Framer Motion, Lucide React\n- Backend: Node.js, Express, Socket.IO, Google APIs\n- Content source: Google Drive\n- Deployment: Docker + GitHub Container Registry\n\n### Project Structure\n```text\nbackend/\n  index.js              # Express API, Google Drive integration, Socket.IO game logic\n\nfrontend/src/\n  socket.js             # socket singleton, room normalization, persisted identity\n  components/\n    Home.jsx            # landing page / room creation / join\n    HostView.jsx        # host screen for all game phases\n    ControllerView.jsx  # player phone controller\n    Slideshow.jsx       # presentation display\n\nplanning/\n  task_plan.md\n  findings.md\n  progress.md\n```\n\n## Google Drive Setup\n\nThe backend reads presentations from a single root Google Drive folder.\n\n- Each subfolder becomes a category\n- PDFs and Google Slides files inside those folders become playable presentations\n\nExample:\n\n```text\nPresent or Panic\n├── Startups/\n│   ├── pitch-deck.pdf\n│   └── quarterly-strategy.google-slides\n└── Fun/\n    └── vacation-photos.pdf\n```\n\n### Access Options\n\n#### Option A: Public Google Drive Folder + API Key\nUse this if the folder is shared as “anyone with the link can view”.\n\nRequired:\n- `GOOGLE_API_KEY`\n- `POWERPOINTS_FOLDER_ID`\n\n#### Option B: Private Google Drive Folder + Service Account\nUse this if the folder must stay private.\n\nRequired:\n- `GOOGLE_SERVICE_ACCOUNT_EMAIL`\n- `GOOGLE_PRIVATE_KEY`\n- `POWERPOINTS_FOLDER_ID`\n\nYou must also share the Google Drive folder with the service account email address.\n\n## Environment Variables\n\nSet these in `backend/.env` or in your container environment:\n\n| Variable | Required | Description |\n|---|---|---|\n| `PORT` | No | Backend port, defaults to `8080` |\n| `POWERPOINTS_FOLDER_ID` | Yes | Google Drive root folder ID |\n| `GOOGLE_API_KEY` | Optional* | For public Drive folders |\n| `GOOGLE_SERVICE_ACCOUNT_EMAIL` | Optional* | For private Drive folders |\n| `GOOGLE_PRIVATE_KEY` | Optional* | Private key for the service account |\n\n`*` Use either `GOOGLE_API_KEY` or the service-account pair.\n\n## Local Development\n\n### Prerequisites\n- Node.js 18+\n- A Google Drive folder with presentations\n\n### 1. Install dependencies\n\n```bash\ncd backend\nnpm install\n\ncd ../frontend\nnpm install\n```\n\n### 2. Configure backend environment\n\nCreate `backend/.env` and set at least:\n\n```env\nPORT=8080\nPOWERPOINTS_FOLDER_ID=your-folder-id\nGOOGLE_API_KEY=your-api-key\n```\n\nOr use the service-account variables instead of `GOOGLE_API_KEY`.\n\n### 3. Start the backend\n\n```bash\ncd backend\nnpm run dev\n```\n\n### 4. Start the frontend\n\n```bash\ncd frontend\nnpm run dev\n```\n\n### 5. Open the app\n\n- Host view: `http://localhost:5173`\n- Backend API: `http://localhost:8080`\n\n## Docker / Self-Hosting\n\nThe repository and GHCR package are public, so you can pull the latest image directly from GitHub:\n\n```bash\ndocker pull ghcr.io/raphaelbleier/powerpoint_karaoke:latest\n```\n\n### Docker Compose\n\nThe included `docker-compose.yml` already points at the published image. Set your environment values, then run:\n\n```bash\ndocker-compose up -d\n```\n\n### Docker Run\n\nYou can also run the published image directly:\n\n```bash\ndocker run -d \\\n  --name present_or_panic \\\n  -p 8080:8080 \\\n  -e PORT=8080 \\\n  -e POWERPOINTS_FOLDER_ID=your-folder-id \\\n  -e GOOGLE_API_KEY=your-api-key \\\n  ghcr.io/raphaelbleier/powerpoint_karaoke:latest\n```\n\nFor a private Google Drive folder, replace `GOOGLE_API_KEY` with `GOOGLE_SERVICE_ACCOUNT_EMAIL` and `GOOGLE_PRIVATE_KEY`.\n\n### GitHub Container Registry\n\nThe repository includes `.github/workflows/docker-publish.yml`.\n\nOn every push to `main`, GitHub Actions builds the Docker image and publishes it to:\n\n```text\nghcr.io/raphaelbleier/powerpoint_karaoke:latest\n```\n\n## Current Limitations\n\nThese are known limitations in the current version:\n\n- Room state is stored in memory only; a server restart removes active rooms\n- Disconnect cleanup uses an in-memory grace period and is reset on server restart\n- No spectator mode yet\n- Browser E2E tests are still missing\n\n## Status\n\nThis project is already playable and covers the full core loop:\n\n- lobby\n- presentation\n- voting\n- score tallying\n- leaderboard\n- replay / new game\n\nCurrent release state:\n\n- public repository\n- public GHCR image\n- automated smoke-test gate in CI (backend tests + frontend lint + frontend build)\n- ready for self-hosted launches\n\n## License\n\nThis project is licensed under the MIT License. See [LICENSE](LICENSE).\n\n## GitHub Protection\n\nThis repository includes:\n\n- a code owner file at `.github/CODEOWNERS`\n- a main-branch ruleset template at `.github/rulesets/main-branch-protection.json`\n\nThe intended protection policy for `main` is:\n\n- pull requests only\n- passing `smoke-tests` status check required\n- repository owner approval required via code owner review\n- no force pushes\n- no branch deletion\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraphaelbleier%2Fpowerpoint_karaoke","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraphaelbleier%2Fpowerpoint_karaoke","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraphaelbleier%2Fpowerpoint_karaoke/lists"}