Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻譯準備中
Unfit For Print - A Cards Against Humanity clone
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
A party game for hideous people.
Unfit for Print is a Cards Against Humanity-style game for the browser. Start a lobby, share the four-letter code, and take turns being terrible. One player judges each round, everyone else plays their worst white card, and the judge picks a winner. First to the target score wins.
Made by MYND at PPO.GG.
Not affiliated with Cards Against Humanity — we just love their game.
The lobby: fill empty seats with bots while you wait for friends
Mid-round: pick a card from your hand before the judge reveals檢視完整 README ↗A party game for hideous people.
Play now at unfit.cards · How to play · Changelog · Report a bug
Unfit for Print is a Cards Against Humanity-style game for the browser. Start a lobby, share the four-letter code, and take turns being terrible. One player judges each round, everyone else plays their worst white card, and the judge picks a winner. First to the target score wins.
Made by MYND at PPO.GG.
Not affiliated with Cards Against Humanity — we just love their game.
At the table
Lobbies and social
Unfit Labs
Everywhere else
| Layer | Tech |
|---|---|
| Framework | Nuxt 4, Vue 3 Composition API |
| UI | Nuxt UI v4, Tailwind CSS 4, GSAP |
| Real-time | Yjs CRDTs on Cloudflare Durable Objects |
| Backend | Nitro server routes, PostgreSQL with Drizzle ORM |
| Auth | Discord OAuth2 and guest accounts (nuxt-auth-utils) |
| i18n | @nuxtjs/i18n, 9 locales |
| Testing | Vitest |
| Hosting | Self-hosted Docker images, published to GHCR on release |
State is split by how long it needs to live.
sync-worker/, a Cloudflare Worker with one Durable Object per lobby. The document is saved to Durable Object storage, so it survives restarts and deploys, and is deleted 10 minutes after the last player leaves.server/api/.flowchart LR
B1[Player browser] <-- Yjs over WebSocket --> T[Sync worker]
B2[Player browser] <-- Yjs over WebSocket --> T
B1 -- REST --> W[Nuxt / Nitro API]
B2 -- REST --> W
W --> DB[(PostgreSQL)]
The game rules live in app/composables/useYjsGameEngine.ts. For the full architecture, conventions and gotchas, read AGENTS.md.
You'll need Node 22, pnpm 10 (corepack enable gets you the pinned version), and Postgres 16. Docker is the easiest way to get Postgres.
1. Install and configure
pnpm install
cp .env.example .env
DATABASE_URL and NUXT_SESSION_PASSWORD are required. Generate the session password with openssl rand -base64 32. Everything else is optional: the Discord keys are only needed for Discord login and the Activity, and the TTS and R2 keys only for read-aloud voices and decoration uploads. Guest play works without them.
2. Start Postgres and apply migrations
docker run -d --name unfit-postgres -p 5432:5432 -e POSTGRES_USER=unfit -e POSTGRES_PASSWORD=unfit -e POSTGRES_DB=unfit postgres:16-alpine
pnpm db:migrate
That container matches the default DATABASE_URL in .env.example.
3. Run the sync server
.env.example points at the production sync worker (wss://sync.unfit.cards). For development, run your own so your test lobbies stay local:
cd sync-worker && pnpm install && cp .dev.vars.example .dev.vars && pnpm dev
Then set NUXT_PUBLIC_LOBBY_TELEPORTAL_URL=ws://localhost:1235 and NUXT_SYNC_ADMIN_TOKEN=dev-sync-admin-token in .env.
4. Start the app
pnpm dev
The app runs at http://localhost:3000.
5. Add some cards
A fresh database has no cards. To load some, sign in once (guest is fine), then make yourself an admin by setting is_admin = true on your row in the users table. pnpm db:studio is the quickest way to do that. Then open /admin/cards/upload and upload a card-pack JSON: an array of { name, white: [...], black: [{ text, pick }] } packs. The schema is in server/api/dev/seed.ts.
pnpm test
The server and database suites delete table data before every test, so they refuse to run unless TEST_DATABASE_URL is set and differs from DATABASE_URL. Point it at a throwaway database, never your real one:
docker run -d --name unfit-postgres-test -p 5433:5432 -e POSTGRES_USER=unfit -e POSTGRES_PASSWORD=unfit -e POSTGRES_DB=unfit postgres:16-alpine
Then apply migrations to it with DATABASE_URL=$TEST_DATABASE_URL pnpm db:migrate.
Found a bug or have an idea? Open an issue. Pull requests are welcome.
web, server and bot.CHANGELOG.md or the version in package.json by hand.main.i18n/locales/en.json at minimum.Still in active development. Things might break, and things might not work as intended.
Licensed under CC BY-NC-SA 4.0. See LICENSE for details.