yep-yogesh/Guessync/main 124k tokens More Tools
```
├── .gitignore (100 tokens)
├── Bruno/
   ├── Room APIs/
      ├── Create Room.bru (300 tokens)
      ├── Join Room.bru (300 tokens)
      ├── folder.bru
   ├── Song GET/
      ├── Song-b-index.bru
      ├── folder.bru
   ├── User GET/
      ├── User Authentication UID.bru (200 tokens)
      ├── folder.bru
   ├── bruno.json
├── LICENSE (omitted)
├── README.md (1100 tokens)
├── client/
   ├── .env.example (100 tokens)
   ├── README.md (200 tokens)
   ├── eslint.config.js (200 tokens)
   ├── index.html (100 tokens)
   ├── package-lock.json (50.1k tokens)
   ├── package.json (300 tokens)
   ├── postcss.config.cjs
   ├── public/
      ├── AI-p.png
      ├── AI.png
      ├── _redirects
      ├── avatars/
         ├── 1.png
         ├── 10.png
         ├── 11.png
         ├── 12.png
         ├── 2.png
         ├── 3.png
         ├── 4.png
         ├── 5.png
         ├── 6.png
         ├── 7.png
         ├── 8.png
         ├── 9.png
      ├── copy.png
      ├── dice.png
      ├── film.png
      ├── howto/
         ├── door.svg (100 tokens)
         ├── majesticons_music-line.svg (100 tokens)
         ├── node.svg (200 tokens)
         ├── question.svg (200 tokens)
      ├── logo.png
      ├── logo.svg (2.1k tokens)
      ├── music.png
      ├── pfp.png
      ├── rect.png
      ├── twinkle.png
      ├── vite.svg (300 tokens)
      ├── wordmark.png
   ├── src/
      ├── App.css
      ├── App.jsx (300 tokens)
      ├── assets/
         ├── copy.png
         ├── google.svg (100 tokens)
         ├── guessync.gif
         ├── react.svg (800 tokens)
         ├── send.png
         ├── spotify.png
      ├── components/
         ├── LandingPage/
            ├── AvatarSelector.jsx (700 tokens)
         ├── common/
            ├── AvatarGrid.jsx (500 tokens)
            ├── GlowingButton.jsx (200 tokens)
            ├── Navbar.jsx (1200 tokens)
      ├── config/
         ├── firebase.js (100 tokens)
      ├── context/
         ├── AuthContext.jsx (400 tokens)
      ├── data/
         ├── avatarList.js (100 tokens)
      ├── index.css (100 tokens)
      ├── main.jsx (100 tokens)
      ├── pages/
         ├── AboutDev.jsx (1400 tokens)
         ├── CreateRoom.jsx (4.9k tokens)
         ├── GameRoom.jsx (5.1k tokens)
         ├── HomePage.jsx (1900 tokens)
         ├── HowToPlay.jsx (1700 tokens)
         ├── JoinRoom.jsx (2.2k tokens)
         ├── LandingPage.jsx (1100 tokens)
         ├── Login.jsx (1400 tokens)
         ├── Profile.jsx (1000 tokens)
         ├── Signup.jsx (1600 tokens)
         ├── WaitingRoom.jsx (2.3k tokens)
      ├── setupTests.js
      ├── socket.js (200 tokens)
      ├── styles/
         ├── tailwind.css
      ├── tests/
         ├── JoinRoom.test.jsx (400 tokens)
   ├── tailwind.config.cjs (100 tokens)
   ├── vite.config.js
├── netlify.toml
├── server/
   ├── .env.example (200 tokens)
   ├── Dockerfile
   ├── config/
      ├── db.js (100 tokens)
      ├── firebase-adminsdk.js (100 tokens)
      ├── firebase.js (100 tokens)
   ├── controllers/
      ├── roomController.js (700 tokens)
      ├── userController.js (200 tokens)
   ├── cronJobs/
      ├── roomCleanup.js (100 tokens)
   ├── models/
      ├── Room.js (300 tokens)
      ├── Song.js
      ├── User.js (200 tokens)
   ├── package-lock.json (32.9k tokens)
   ├── package.json (200 tokens)
   ├── routes/
      ├── authRoutes.js
      ├── roomRoutes.js (100 tokens)
      ├── songRoutes.js (200 tokens)
      ├── userRoutes.js (100 tokens)
   ├── scripts/
      ├── fetchSpotifySongs.js (200 tokens)
      ├── fetchYouTubeIds.js (200 tokens)
   ├── server.js (300 tokens)
   ├── sockets/
      ├── socketHandler.js (1400 tokens)
   ├── utils/
      ├── fillPlaylistFromSpotify.js (300 tokens)
      ├── generateCode.js
      ├── getAIHint.js (200 tokens)
      ├── getSpotifyAccessToken.js (200 tokens)
      ├── getYoutubeVideoId.js (100 tokens)
```


## /.gitignore

```gitignore path="/.gitignore" 
# ------------------
# Root-level ignores
# ------------------
.DS_Store
.env
*.log

# ------------------
# Client ignores
# ------------------
client/node_modules/
client/config/firebase.js
client/dist/
client/.vite/
client/.env
client/.env.local
client/.DS_Store

# Vite & build cache
client/.cache/
client/.parcel-cache/
client/build/
client/.eslintcache

# Editor/IDE folders (optional but useful)
client/.vscode/
client/.idea/

# ------------------
# Server ignores
# ------------------
server/node_modules/
server/.env
server/config/firebase/firebase-adminsdk.json
server/.DS_Store

# Editor/IDE folders (optional but useful)
server/.vscode/
server/.idea/

```

## /Bruno/Room APIs/Create Room.bru

```bru path="/Bruno/Room APIs/Create Room.bru" 
meta {
  name: Create Room
  type: http
  seq: 1
}

post {
  url: https://guessync.onrender.com/api/room/create
  body: json
  auth: inherit
}

headers {
  Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjNiZjA1MzkxMzk2OTEzYTc4ZWM4MGY0MjcwMzM4NjM2NDA2MTBhZGMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiWW9nZXNoIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL25vdC1ndWVzc3luYyIsImF1ZCI6Im5vdC1ndWVzc3luYyIsImF1dGhfdGltZSI6MTc1MDU5NDQ1MywidXNlcl9pZCI6InBwYmJqd21ueFBRTU90NXl0amh4aEx5eHA3TjIiLCJzdWIiOiJwcGJiandtbnhQUU1PdDV5dGpoeGhMeXhwN04yIiwiaWF0IjoxNzUwNTk0NDU1LCJleHAiOjE3NTA1OTgwNTUsImVtYWlsIjoidGVzdDFAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsidGVzdDFAdGVzdC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.aw15qoR4P8ZcoClnvbP_fUitg32gRWCSJs6cWyHCo3qjZyCNysmk8ZDU_e03nFDWVMY6oClm_-sEtq_OZCA7fPxrFLNBI5akkRfaooY1wDeg5YciFi6MtyN_1aA4zWLyeouO0As3KAYE5JyzAxWcYbTG9okFII19QyvfGL84MkcMzZuys9S9XKU5SOfYwFDBj2X8NZD5K1U97KWAf1oznKDuMqu8mDCWu7aGus4VBhXj_9HtiZ8xOuD5PKjWzBzJUSwWg2_Svb-_hNXpf37h1Co2rm3yVTm3TMgeU8n7I22YRAF_oORbxRsnSz6GmhMcJm_kvbqyYNbUjlq15y4X1w
  Content-Type: application/json
}

body:json {
  {
    "uid": "user_123",
    "name": "Yogesh",
    "avatar": "https://i.imgur.com/avatar.png",
    "useSpotify": true,
    "playlistId": "37i9dQZF1DXcBWIGoYBM5M", 
    "code": "123456",
    "rounds": 5,
    "duration": 15,
    "languages": ["Tamil", "Hindi"],
   "rules": {
    "showHints": true,
    "enableChat": true
  }
    "language": "tamil"
  }
  
}

```

## /Bruno/Room APIs/Join Room.bru

```bru path="/Bruno/Room APIs/Join Room.bru" 
meta {
  name: Join Room
  type: http
  seq: 2
}

post {
  url: https://guessync.onrender.com/api/room/join
  body: json
  auth: inherit
}

headers {
  Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjNiZjA1MzkxMzk2OTEzYTc4ZWM4MGY0MjcwMzM4NjM2NDA2MTBhZGMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiWW9nZXNoIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL25vdC1ndWVzc3luYyIsImF1ZCI6Im5vdC1ndWVzc3luYyIsImF1dGhfdGltZSI6MTc1MDU5NDQ1MywidXNlcl9pZCI6InBwYmJqd21ueFBRTU90NXl0amh4aEx5eHA3TjIiLCJzdWIiOiJwcGJiandtbnhQUU1PdDV5dGpoeGhMeXhwN04yIiwiaWF0IjoxNzUwNTk0NDU1LCJleHAiOjE3NTA1OTgwNTUsImVtYWlsIjoidGVzdDFAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsidGVzdDFAdGVzdC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.aw15qoR4P8ZcoClnvbP_fUitg32gRWCSJs6cWyHCo3qjZyCNysmk8ZDU_e03nFDWVMY6oClm_-sEtq_OZCA7fPxrFLNBI5akkRfaooY1wDeg5YciFi6MtyN_1aA4zWLyeouO0As3KAYE5JyzAxWcYbTG9okFII19QyvfGL84MkcMzZuys9S9XKU5SOfYwFDBj2X8NZD5K1U97KWAf1oznKDuMqu8mDCWu7aGus4VBhXj_9HtiZ8xOuD5PKjWzBzJUSwWg2_Svb-_hNXpf37h1Co2rm3yVTm3TMgeU8n7I22YRAF_oORbxRsnSz6GmhMcJm_kvbqyYNbUjlq15y4X1w
  Content-Type: application/json
}

body:json {
  {
    "uid": "user_456",
    "name": "Ryan",
    "avatar": "https://i.imgur.com/avatar2.png",
    "code": "123456"
  }
  
}

```

## /Bruno/Room APIs/folder.bru

```bru path="/Bruno/Room APIs/folder.bru" 
meta {
  name: Room APIs
}

```

## /Bruno/Song GET/Song-b-index.bru

```bru path="/Bruno/Song GET/Song-b-index.bru" 
meta {
  name: Song-b-index
  type: http
  seq: 1
}

get {
  url: https://guessync.onrender.com/api/song/by-index?roomCode=123486&index=0
  body: none
  auth: inherit
}

params:query {
  roomCode: 123486
  index: 0
}

```

## /Bruno/Song GET/folder.bru

```bru path="/Bruno/Song GET/folder.bru" 
meta {
  name: Song GET
}

```

## /Bruno/User GET/User Authentication UID.bru

```bru path="/Bruno/User GET/User Authentication UID.bru" 
meta {
  name: User Authentication UID
  type: http
  seq: 1
}

get {
  url: https://guessync.onrender.com/api/auth/me
  body: none
  auth: inherit
}

headers {
  Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjNiZjA1MzkxMzk2OTEzYTc4ZWM4MGY0MjcwMzM4NjM2NDA2MTBhZGMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiWW9nZXNoIiwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL25vdC1ndWVzc3luYyIsImF1ZCI6Im5vdC1ndWVzc3luYyIsImF1dGhfdGltZSI6MTc1MDU5NDQ1MywidXNlcl9pZCI6InBwYmJqd21ueFBRTU90NXl0amh4aEx5eHA3TjIiLCJzdWIiOiJwcGJiandtbnhQUU1PdDV5dGpoeGhMeXhwN04yIiwiaWF0IjoxNzUwNTk0NDU1LCJleHAiOjE3NTA1OTgwNTUsImVtYWlsIjoidGVzdDFAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsidGVzdDFAdGVzdC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.aw15qoR4P8ZcoClnvbP_fUitg32gRWCSJs6cWyHCo3qjZyCNysmk8ZDU_e03nFDWVMY6oClm_-sEtq_OZCA7fPxrFLNBI5akkRfaooY1wDeg5YciFi6MtyN_1aA4zWLyeouO0As3KAYE5JyzAxWcYbTG9okFII19QyvfGL84MkcMzZuys9S9XKU5SOfYwFDBj2X8NZD5K1U97KWAf1oznKDuMqu8mDCWu7aGus4VBhXj_9HtiZ8xOuD5PKjWzBzJUSwWg2_Svb-_hNXpf37h1Co2rm3yVTm3TMgeU8n7I22YRAF_oORbxRsnSz6GmhMcJm_kvbqyYNbUjlq15y4X1w
}

```

## /Bruno/User GET/folder.bru

```bru path="/Bruno/User GET/folder.bru" 
meta {
  name: User GET
}

```

## /Bruno/bruno.json

```json path="/Bruno/bruno.json" 
{
  "version": "1",
  "name": "Bruno",
  "type": "collection",
  "ignore": [
    "node_modules",
    ".git"
  ]
}
```

## /README.md

<p align="center">
  <img src="./client/src/assets/guessync.gif" alt="Guessync Demo" width="1000" />
</p>

<h1 align="center">
  <a href="https://guessync.netlify.app/">
    <img src="./client/public/wordmark.png" alt="Guessync" height="80" />
  </a>
</h1>

<p align="center">
  <b>A full-stack real-time multiplayer music guessing game</b> <br/>
  Compete with friends, guess songs, and use AI-powered hints!
</p>

---

## Features

- **Spotify playlist integration** for song sourcing  
- **YouTube audio streaming** for playback  
- **AI-powered hints** using OpenAI  
- **Real-time guessing & chat** via Socket.IO  
- **Typo-tolerant matching** with Fuse.js  
- **Secure authentication** (Firebase)  
- **Modern frontend** (React + Vite + Tailwind)  
- **Leaderboard scoring** in real time  
- **MongoDB Atlas** for scalable data storage  

---

## Tech Stack

### Frontend
- React + Vite  
- Tailwind CSS  
- Firebase Authentication  
- Socket.IO Client  

### Backend
- Node.js + Express.js  
- MongoDB (Mongoose)  
- Firebase Admin SDK (auth verification)  
- Socket.IO Server  
- Spotify API + YouTube API  
- Gemini API (AI hint generation)  
- Bruno (API testing)  

---

## Deployment

- **Frontend:** [Guessync on Netlify](https://guessync.netlify.app/)  
- **Backend:** [Guessync on Render](https://guessync.onrender.com/)  
- **Database:** MongoDB Atlas

---

## Local Development (Step-by-step)

Follow these steps to run both backend (Express) and frontend (Vite + React) locally.

- Backend runs on: http://localhost:5000
- Frontend runs on: http://localhost:5173

### 1) Prerequisites
- Node.js 18+ and npm (or yarn/pnpm)
- A MongoDB database (Atlas or local)
- Firebase project (for Auth)
- Optional, but recommended:
  - Spotify API credentials (song sourcing)
  - YouTube API key (audio)
  - Gemini/OpenAI key (AI hints)

### 2) Clone and enter the project
```bash
git clone <your-fork-or-repo-url>
cd Guessync
```

### 3) Configure environment files

Use the provided examples to create your .env files.

- macOS/Linux:
```bash
cp server/.env.example server/.env
cp client/.env.example client/.env
```

- Windows PowerShell:
```powershell
Copy-Item server/.env.example server/.env
Copy-Item client/.env.example client/.env
```

Now fill the values:

Server (.env):
- MONGO_URI: Your MongoDB connection string (Atlas or local, e.g. mongodb://127.0.0.1:27017/guessync)
- PORT: 5000 (default)
- SPOTIFY_CLIENT_ID / SPOTIFY_CLIENT_SECRET: From Spotify Developer Dashboard
- YOUTUBE_API_KEY: From Google Cloud Console
- GEMINI_API_KEY: From Google AI Studio (optional if you won’t use AI hints)
- Firebase service account:
  - In Firebase console: Project settings → Service accounts → Generate new private key
  - Paste fields into .env. Important: keep FIREBASE_PRIVATE_KEY wrapped in quotes and with escaped newlines:
    FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...your key...\n-----END PRIVATE KEY-----\n"

Client (.env):
- VITE_FIREBASE_*: From Firebase project settings → General → Your apps (Web app)

Tip: Never commit .env. It’s ignored by .gitignore already.

### 4) Install dependencies

From project root, install in both apps:

```bash
cd server && npm install
cd ../client && npm install
```

### 5) Run backend and frontend

Open two terminals:

- Terminal A (Backend):
```bash
cd server
npm run dev
# Expect: "Server running on port 5000"
```

- Terminal B (Frontend):
```bash
cd client
npm run dev
# Open the shown local URL, usually http://localhost:5173
```

### 6) Log in and try the app
- Use Email/Password or Google via Firebase
- Pick an avatar
- Create a room or join with a code
- Open a second browser window to simulate a friend joining

---

## Common Issues & Troubleshooting

- CORS error in browser console:
  - The backend only allows these origins by default:
    - http://localhost:5173
    - https://guessync.netlify.app
  - If your dev URL differs (e.g. 127.0.0.1:5173), add it in server/server.js (allowedOrigins array).

- Rate limiting (HTTP 429):
  - /api/room/create and /api/auth/login are limited to 5 requests/min (see server/server.js with express-rate-limit).
  - Wait a minute or increase limits for local dev only.

- Firebase “invalid credential” or “private key” errors:
  - Ensure FIREBASE_PRIVATE_KEY uses escaped newlines and is wrapped in quotes:
    "-----BEGIN PRIVATE KEY-----\n...lines...\n-----END PRIVATE KEY-----\n"
  - Make sure Client VITE_FIREBASE_* matches the same Firebase project.

- MongoDB connection errors:
  - For Atlas, whitelist your IP or set “Allow access from anywhere”.
  - Verify MONGO_URI is correct and the cluster is running.

- Vite port already in use:
  - Run a different port:
    npm run dev -- --port 5174

- Socket connection issues:
  - Backend must be running on http://localhost:5000.
  - Ensure your frontend’s socket base URL matches backend.

---

## Scripts

Backend (server/package.json):
- npm run dev → Nodemon on server.js

Frontend (client/package.json):
- npm run dev → Vite dev server
- npm run build → Production build

---

## Project Structure (high-level)

- server/ (Express, Socket.IO, MongoDB, Firebase Admin, routes, cron)
  - server.js (entry)
  - routes/, sockets/, config/, cronJobs/
  - .env.example
- client/ (React + Vite, Tailwind, Firebase Auth, Socket.IO client)
  - src/pages, src/components, src/assets
  - vite.config.js, tailwind config
  - .env.example

---

## Contributing

1) Fork the repo  
2) Create a feature branch  
3) Commit with clear messages  
4) Open a PR with a brief description and screenshots if relevant

Thanks for contributing!


## /client/.env.example

```example path="/client/.env.example" 
# Firebase (for Vite + Frontend)
VITE_FIREBASE_API_KEY="your_firebase_api_key"
VITE_FIREBASE_AUTH_DOMAIN="your_project.firebaseapp.com"
VITE_FIREBASE_PROJECT_ID="your_firebase_project_id"
VITE_FIREBASE_STORAGE_BUCKET="your_project.firebasestorage.app"
VITE_FIREBASE_MESSAGING_SENDER_ID="your_firebase_messaging_sender_id"
VITE_FIREBASE_APP_ID="your_firebase_app_id"


```

## /client/README.md

# React + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.


## /client/eslint.config.js

```js path="/client/eslint.config.js" 
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'

export default [
  { ignores: ['dist'] },
  {
    files: ['**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...js.configs.recommended.rules,
      ...reactHooks.configs.recommended.rules,
      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
]

```

## /client/index.html

```html path="/client/index.html" 
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/logo.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Guessync</title>

    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
    />
  </head>

  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

## /client/package.json

```json path="/client/package.json" 
{
  "name": "client",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@fontsource/montserrat": "^5.2.5",
    "@fontsource/silkscreen": "^5.2.5",
    "@fortawesome/free-solid-svg-icons": "^6.7.2",
    "@fortawesome/react-fontawesome": "^0.2.2",
    "@react-three/fiber": "^9.1.2",
    "@tailwindcss/vite": "^4.1.3",
    "colorthief": "^2.6.0",
    "firebase": "^11.6.0",
    "framer-motion": "^12.9.2",
    "lucide-react": "^0.544.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-icons": "^5.5.0",
    "react-router-dom": "^6.30.0",
    "socket.io-client": "^4.8.1",
    "three": "^0.176.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.21.0",
    "@tailwindcss/postcss": "^4.1.2",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.3.0",
    "@testing-library/user-event": "^14.6.1",
    "@types/react": "^19.0.10",
    "@types/react-dom": "^19.0.4",
    "@vitejs/plugin-react": "^4.3.4",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.21.0",
    "eslint-plugin-react-hooks": "^5.1.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^15.15.0",
    "jsdom": "^26.1.0",
    "postcss": "^8.5.3",
    "tailwindcss": "^4.1.3",
    "vite": "^6.3.5",
    "vitest": "^3.2.4"
  }
}

```

## /client/postcss.config.cjs

```cjs path="/client/postcss.config.cjs" 
const tailwindcss = require('@tailwindcss/postcss')

module.exports = {
  plugins: [
    tailwindcss(),
    require('autoprefixer'),
  ],
}

```

## /client/public/AI-p.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/AI-p.png

## /client/public/AI.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/AI.png

## /client/public/_redirects

``` path="/client/public/_redirects" 
/*  /index.html  200

```

## /client/public/avatars/1.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/1.png

## /client/public/avatars/10.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/10.png

## /client/public/avatars/11.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/11.png

## /client/public/avatars/12.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/12.png

## /client/public/avatars/2.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/2.png

## /client/public/avatars/3.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/3.png

## /client/public/avatars/4.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/4.png

## /client/public/avatars/5.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/5.png

## /client/public/avatars/6.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/6.png

## /client/public/avatars/7.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/7.png

## /client/public/avatars/8.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/8.png

## /client/public/avatars/9.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/avatars/9.png

## /client/public/copy.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/copy.png

## /client/public/dice.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/dice.png

## /client/public/film.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/film.png

## /client/public/howto/door.svg

```svg path="/client/public/howto/door.svg" 
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.9167 27.0833C23.5069 27.0833 24.0021 26.8833 24.4021 26.4833C24.8021 26.0833 25.0014 25.5889 25 25C24.9986 24.4111 24.7986 23.9167 24.4 23.5167C24.0014 23.1167 23.5069 22.9167 22.9167 22.9167C22.3264 22.9167 21.8319 23.1167 21.4333 23.5167C21.0347 23.9167 20.8347 24.4111 20.8333 25C20.8319 25.5889 21.0319 26.084 21.4333 26.4854C21.8347 26.8868 22.3292 27.0861 22.9167 27.0833ZM14.5833 43.75V39.5833L27.0833 37.5V12.4479L14.5833 10.4167V6.25L31.25 8.95833V40.9375L14.5833 43.75ZM6.25 43.75V39.5833H10.4167V6.25H39.5833V39.5833H43.75V43.75H6.25ZM14.5833 39.5833H35.4167V10.4167H14.5833V39.5833Z" fill="black"/>
</svg>

```

## /client/public/howto/majesticons_music-line.svg

```svg path="/client/public/howto/majesticons_music-line.svg" 
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 52.5C19.1421 52.5 22.5 49.1421 22.5 45C22.5 40.8579 19.1421 37.5 15 37.5C10.8579 37.5 7.5 40.8579 7.5 45C7.5 49.1421 10.8579 52.5 15 52.5Z" stroke="black" stroke-width="6.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M45 50C49.1421 50 52.5 46.6421 52.5 42.5C52.5 38.3579 49.1421 35 45 35C40.8579 35 37.5 38.3579 37.5 42.5C37.5 46.6421 40.8579 50 45 50Z" stroke="black" stroke-width="6.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22.5 45V25M22.5 25V15L52.5 7.5V17.5M22.5 25L52.5 17.5M52.5 42.5V17.5" stroke="black" stroke-width="6.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

```

## /client/public/howto/node.svg

```svg path="/client/public/howto/node.svg" 
<svg width="118" height="20" viewBox="0 0 118 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="18" height="18" rx="9" fill="black"/>
<rect x="1" y="1" width="18" height="18" rx="9" stroke="black" stroke-width="2"/>
<rect x="1" y="1" width="18" height="18" rx="9" stroke="white" stroke-width="2"/>
<rect x="5.26318" y="5.26315" width="9.47368" height="9.47368" rx="4.73684" fill="white"/>
<rect x="99" y="1" width="18" height="18" rx="9" fill="black"/>
<rect x="99" y="1" width="18" height="18" rx="9" stroke="black" stroke-width="2"/>
<rect x="99" y="1" width="18" height="18" rx="9" stroke="#FFFB00" stroke-width="2"/>
<rect x="103.263" y="5.26315" width="9.47368" height="9.47368" rx="4.73684" fill="#FFFB00"/>
<rect x="18" y="10" width="80" height="1" fill="url(#paint0_linear_132_205)"/>
<defs>
<linearGradient id="paint0_linear_132_205" x1="18" y1="10.5" x2="98" y2="10.5" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#FFFB00"/>
</linearGradient>
</defs>
</svg>

```

## /client/public/howto/question.svg

```svg path="/client/public/howto/question.svg" 
<svg width="60" height="62" viewBox="0 0 60 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 22.25V19.75C12.5 15.1087 14.3437 10.6575 17.6256 7.37563C20.9075 4.09374 25.3587 2.25 30 2.25C34.6413 2.25 39.0925 4.09374 42.3744 7.37563C45.6563 10.6575 47.5 15.1087 47.5 19.75C47.4996 22.9511 46.2281 26.021 43.965 28.285L37.93 34.32C37.0011 35.2487 36.2642 36.3513 35.7614 37.5648C35.2587 38.7783 35 40.079 35 41.3925V42.25H25V39.32C25 34.795 26.8 30.4525 30 27.25L33.2325 24.0175C33.7945 23.4582 34.24 22.7931 34.5434 22.0607C34.8467 21.3282 35.0019 20.5428 35 19.75C35 18.4239 34.4732 17.1521 33.5355 16.2145C32.5979 15.2768 31.3261 14.75 30 14.75C28.6739 14.75 27.4021 15.2768 26.4645 16.2145C25.5268 17.1521 25 18.4239 25 19.75V22.25H12.5ZM30 49.75C31.3261 49.75 32.5979 50.2768 33.5355 51.2145C34.4732 52.1522 35 53.4239 35 54.75C35 56.0761 34.4732 57.3479 33.5355 58.2855C32.5979 59.2232 31.3261 59.75 30 59.75C28.6739 59.75 27.4021 59.2232 26.4645 58.2855C25.5268 57.3479 25 56.0761 25 54.75C25 53.4239 25.5268 52.1522 26.4645 51.2145C27.4021 50.2768 28.6739 49.75 30 49.75Z" stroke="black" stroke-width="3"/>
</svg>

```

## /client/public/logo.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/logo.png

## /client/public/logo.svg

```svg path="/client/public/logo.svg" 
<svg width="63" height="66" viewBox="0 0 63 66" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_329_230)">
<g opacity="0.86" filter="url(#filter1_d_329_230)">
<path d="M47.8125 36.3906C47.8125 37.4506 47.4982 38.4868 46.9093 39.3681C46.3204 40.2495 45.4834 40.9364 44.5041 41.342C43.5248 41.7477 42.4472 41.8538 41.4076 41.647C40.3679 41.4402 39.413 40.9298 38.6635 40.1803C37.914 39.4308 37.4035 38.4758 37.1967 37.4362C36.9899 36.3966 37.0961 35.319 37.5017 34.3397C37.9074 33.3604 38.5943 32.5234 39.4756 31.9345C40.357 31.3456 41.3931 31.0312 42.4531 31.0312C43.8745 31.0312 45.2377 31.5959 46.2428 32.601C47.2479 33.6061 47.8125 34.9692 47.8125 36.3906ZM17.9531 37.1563C16.8931 37.1563 15.857 37.4706 14.9756 38.0595C14.0943 38.6484 13.4073 39.4854 13.0017 40.4647C12.5961 41.444 12.4899 42.5216 12.6967 43.5612C12.9035 44.6008 13.414 45.5558 14.1635 46.3053C14.913 47.0548 15.8679 47.5652 16.9076 47.772C17.9472 47.9788 19.0248 47.8727 20.0041 47.467C20.9834 47.0614 21.8204 46.3745 22.4093 45.4931C22.9982 44.6118 23.3125 43.5756 23.3125 42.5156C23.3125 41.0942 22.7479 39.7311 21.7428 38.726C20.7377 37.7209 19.3745 37.1563 17.9531 37.1563Z" fill="#FFFB00"/>
</g>
<path d="M46.2257 30.7469L46.3813 30.8508V30.6637V26.4375C46.3813 26.0579 46.532 25.6939 46.8005 25.4255C47.0689 25.157 47.4329 25.0063 47.8125 25.0063C48.1921 25.0063 48.5561 25.157 48.8246 25.4255C49.093 25.6939 49.2438 26.0579 49.2438 26.4375V36.3906C49.2443 37.9046 48.7389 39.3754 47.8078 40.5692C46.8768 41.7631 45.5735 42.6115 44.105 42.9798C42.6365 43.3481 41.087 43.2151 39.7028 42.6019C38.3185 41.9887 37.1789 40.9305 36.4649 39.5954C35.751 38.2603 35.5037 36.725 35.7623 35.2332C36.0209 33.7415 36.7706 32.379 37.8923 31.3621C39.014 30.3453 40.4433 29.7324 41.9532 29.621C43.463 29.5095 44.9669 29.9058 46.2257 30.7469ZM24.8195 26.0055L24.7438 26.0245V26.1025V42.5156C24.7443 44.0296 24.2389 45.5004 23.3078 46.6942C22.3768 47.8881 21.0735 48.7365 19.605 49.1048C18.1365 49.4731 16.587 49.3401 15.2028 48.7269C13.8185 48.1137 12.6789 47.0555 11.9649 45.7204C11.251 44.3853 11.0037 42.85 11.2623 41.3582C11.5209 39.8665 12.2706 38.504 13.3923 37.4871C14.514 36.4703 15.9433 35.8574 17.4532 35.746C18.963 35.6345 20.4669 36.0308 21.7257 36.8719L21.8813 36.9758V36.7887V15.7188C21.8813 15.7188 21.8813 15.7188 21.8813 15.7188C21.8813 15.3996 21.988 15.0896 22.1845 14.8381C22.3809 14.5865 22.6558 14.4078 22.9654 14.3305L22.9444 14.2464L22.9654 14.3305L33.6825 11.6512C33.6828 11.6511 33.683 11.651 33.6833 11.651C34.0487 11.5665 34.4327 11.6288 34.7527 11.8245C35.0728 12.0204 35.3033 12.3342 35.3943 12.6983C35.4853 13.0624 35.4297 13.4478 35.2393 13.7713C35.0491 14.0945 34.7396 14.3302 34.3775 14.4276C34.3772 14.4276 34.3769 14.4277 34.3767 14.4278L24.8195 16.818L24.7438 16.837V16.915V22.9443V23.0724L24.868 23.0414L38.2763 19.6902C38.2765 19.6902 38.2768 19.6901 38.2771 19.69C38.6425 19.6055 39.0265 19.6678 39.3464 19.8636C39.6666 20.0594 39.897 20.3732 39.9881 20.7374C40.0791 21.1015 40.0234 21.4868 39.8331 21.8103C39.6429 22.1336 39.3334 22.3692 38.9712 22.4666C38.9709 22.4667 38.9707 22.4668 38.9704 22.4669L24.8195 26.0055ZM46.3813 36.3906C46.3813 35.6137 46.1509 34.8543 45.7192 34.2083C45.2876 33.5623 44.6741 33.0588 43.9564 32.7615C43.2386 32.4642 42.4488 32.3864 41.6868 32.538C40.9248 32.6895 40.2249 33.0637 39.6755 33.613C39.1262 34.1624 38.752 34.8623 38.6005 35.6243C38.4489 36.3863 38.5267 37.1761 38.824 37.8939C39.1213 38.6116 39.6248 39.2251 40.2708 39.6567C40.9168 40.0884 41.6762 40.3187 42.4531 40.3187C43.4949 40.3187 44.4941 39.9049 45.2307 39.1682C45.9674 38.4316 46.3813 37.4324 46.3813 36.3906ZM21.8813 42.5156C21.8813 41.7387 21.6509 40.9793 21.2192 40.3333C20.7876 39.6873 20.1741 39.1838 19.4564 38.8865C18.7386 38.5892 17.9488 38.5114 17.1868 38.663C16.4248 38.8145 15.7249 39.1887 15.1755 39.738C14.6262 40.2874 14.252 40.9873 14.1005 41.7493C13.9489 42.5113 14.0267 43.3011 14.324 44.0189C14.6213 44.7366 15.1248 45.3501 15.7708 45.7817C16.4168 46.2134 17.1762 46.4437 17.9531 46.4437C18.9949 46.4437 19.9941 46.0299 20.7307 45.2932C21.4674 44.5566 21.8813 43.5574 21.8813 42.5156Z" fill="white" stroke="black" stroke-width="0.2"/>
</g>
<g filter="url(#filter2_d_329_230)">
<path d="M45.324 19.1976C44.9109 19.3095 44.4704 19.2557 44.0964 19.0476C43.7224 18.8395 43.4445 18.4936 43.3218 18.0835L43.1382 17.4395C42.9571 16.7724 42.9833 16.0685 43.2137 15.4188C43.444 14.7824 43.856 14.2276 44.3987 13.8232L44.6778 13.5415C44.7356 13.4421 44.773 13.3321 44.7878 13.2181C44.779 13.1083 44.7494 13.0044 44.699 12.9065C44.6854 12.7913 44.6535 12.6791 44.6042 12.5741C44.4882 12.472 44.3517 12.396 44.2038 12.3513C44.0255 12.2878 43.8355 12.2641 43.647 12.2819C43.4586 12.2997 43.2764 12.3586 43.1131 12.4543C42.9499 12.5501 42.8096 12.6805 42.7021 12.8362C42.5946 12.992 42.5226 13.1695 42.491 13.3561C42.4656 13.5727 42.3967 13.782 42.2885 13.9714C42.1802 14.1608 42.0348 14.3264 41.861 14.4582C41.6872 14.5901 41.4886 14.6855 41.2771 14.7387C41.0655 14.7919 40.8454 14.8018 40.6299 14.7679C40.4144 14.734 40.208 14.6569 40.023 14.5413C39.838 14.4256 39.6783 14.2738 39.5534 14.095C39.4285 13.9161 39.3411 13.7139 39.2962 13.5004C39.2514 13.2869 39.2502 13.0665 39.2926 12.8526C39.454 11.8672 39.9345 10.9622 40.6603 10.2765C41.3861 9.59075 42.3169 9.1623 43.3098 9.05696C43.9795 8.98306 44.6572 9.06682 45.2888 9.30155C45.8938 9.50429 46.4428 9.84598 46.8919 10.2992C47.341 10.7525 47.6775 11.3047 47.8746 11.9115C48.0369 12.465 48.0879 13.0457 48.0249 13.6208C47.9588 14.1871 47.7691 14.732 47.469 15.2168C47.1767 15.7045 46.7815 16.1226 46.3139 16.4445L46.5211 17.1717C46.5818 17.3834 46.5987 17.6052 46.5707 17.8237C46.5428 18.0422 46.4705 18.2526 46.3585 18.4422C46.2465 18.6318 46.0969 18.7966 45.9191 18.9265C45.7412 19.0563 45.5387 19.1486 45.324 19.1976ZM46.2891 22.7412C45.8758 22.859 45.4327 22.8078 45.0572 22.5988C44.6818 22.3899 44.4046 22.0404 44.2869 21.6271C44.1691 21.2139 44.2203 20.7708 44.4292 20.3953C44.6381 20.0198 44.9876 19.7427 45.4009 19.6249C45.8141 19.5071 46.2572 19.5583 46.6327 19.7673C47.0082 19.9762 47.2853 20.3257 47.4031 20.7389C47.5209 21.1522 47.4697 21.5953 47.2608 21.9708C47.0519 22.3463 46.7023 22.6234 46.2891 22.7412Z" fill="#FFFB00"/>
<path d="M45.3018 19.1001L45.3018 19.1L45.2979 19.1011C44.9099 19.2062 44.4963 19.1556 44.1451 18.9602C43.794 18.7649 43.5331 18.4403 43.4178 18.0555L43.2346 17.4127C43.0589 16.7654 43.0845 16.0827 43.3078 15.4526C43.5315 14.8347 43.9315 14.296 44.4584 13.9034L44.4645 13.8989L44.4697 13.8936L44.7488 13.6118L44.7578 13.6028L44.7642 13.5917C44.8288 13.4808 44.8705 13.3582 44.887 13.2309L44.8883 13.2205L44.8875 13.2101C44.8781 13.0932 44.8475 12.9819 44.7961 12.877C44.7802 12.7574 44.746 12.6409 44.6948 12.5316L44.6859 12.5127L44.6703 12.499C44.5441 12.388 44.3958 12.3053 44.2351 12.2563C44.0437 12.1885 43.8398 12.1632 43.6376 12.1823C43.4347 12.2015 43.2384 12.2649 43.0625 12.3681C42.8867 12.4712 42.7356 12.6117 42.6198 12.7795C42.504 12.9473 42.4264 13.1384 42.3924 13.3394L42.3923 13.3394L42.3917 13.3444C42.3679 13.5477 42.3032 13.7441 42.2016 13.9218C42.1001 14.0995 41.9637 14.2549 41.8006 14.3786C41.6375 14.5023 41.4512 14.5918 41.2527 14.6417C41.0542 14.6916 40.8476 14.701 40.6454 14.6691C40.4432 14.6373 40.2496 14.565 40.076 14.4565C39.9025 14.348 39.7526 14.2055 39.6354 14.0377C39.5182 13.8699 39.4362 13.6801 39.3941 13.4798C39.3521 13.2795 39.3509 13.0728 39.3907 12.872L39.3907 12.872L39.3912 12.8687C39.5492 11.905 40.0191 11.0198 40.729 10.3492C41.4388 9.67848 42.3492 9.25944 43.3203 9.15641L43.3207 9.15636C43.975 9.08417 44.637 9.16599 45.254 9.39529L45.254 9.39534L45.257 9.39637C45.8472 9.59414 46.3828 9.92746 46.8209 10.3696C47.2586 10.8114 47.5868 11.3496 47.7791 11.941C47.9373 12.4813 47.987 13.0481 47.9255 13.6095C47.8611 14.1611 47.6763 14.6919 47.3839 15.1642L47.3839 15.1642L47.3832 15.1654C47.0984 15.6406 46.7131 16.0482 46.2572 16.3622L46.198 16.4029L46.2177 16.472L46.425 17.1991L46.425 17.1992C46.4819 17.3979 46.4978 17.606 46.4715 17.811C46.4453 18.016 46.3775 18.2134 46.2724 18.3913C46.1673 18.5692 46.027 18.7239 45.8601 18.8457C45.6932 18.9676 45.5032 19.0541 45.3018 19.1001ZM46.2617 22.645C45.8739 22.7555 45.4582 22.7075 45.1059 22.5114C44.7536 22.3154 44.4935 21.9875 44.383 21.5997C44.2725 21.212 44.3206 20.7962 44.5166 20.4439C44.7126 20.0916 45.0406 19.8316 45.4283 19.7211C45.816 19.6106 46.2318 19.6586 46.5841 19.8546C46.9364 20.0507 47.1964 20.3786 47.3069 20.7664C47.4175 21.1541 47.3694 21.5698 47.1734 21.9222C46.9774 22.2745 46.6494 22.5345 46.2617 22.645Z" stroke="#030303" stroke-width="0.2"/>
</g>
<defs>
<filter id="filter0_d_329_230" x="4.06249" y="1.0625" width="55.3438" height="55.3463" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="3.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_329_230"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_329_230" result="shape"/>
</filter>
<filter id="filter1_d_329_230" x="0.69375" y="25.1313" width="59.0187" height="40.6437" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="6"/>
<feGaussianBlur stdDeviation="5.95"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.866667 0 0 0 0 0.858824 0 0 0 0 0.329412 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_329_230"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_329_230" result="shape"/>
</filter>
<filter id="filter2_d_329_230" x="30.3617" y="0.130884" width="26.5894" height="31.5723" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="4.45"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.985401 0 0 0 0 0 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_329_230"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_329_230" result="shape"/>
</filter>
</defs>
</svg>

```

## /client/public/music.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/music.png

## /client/public/pfp.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/pfp.png

## /client/public/rect.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/rect.png

## /client/public/twinkle.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/twinkle.png

## /client/public/vite.svg

```svg path="/client/public/vite.svg" 
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
```

## /client/public/wordmark.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/public/wordmark.png

## /client/src/App.css

```css path="/client/src/App.css" 

```

## /client/src/App.jsx

```jsx path="/client/src/App.jsx" 
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import LandingPage from "./pages/LandingPage";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import CreateRoom from './pages/CreateRoom';
import JoinRoom from "./pages/JoinRoom";
import WaitingRoom from "./pages/WaitingRoom";
import GameRoom from "./pages/GameRoom";
import Profile from "./pages/Profile";
import HowToPlay from "./pages/HowToPlay";
import HomePage from './pages/HomePage';
import AboutDev from './pages/AboutDev';

function App() {
  return (
    <Router>
      <InnerApp /> 
    </Router>
  );
}

// 👇 ADD this
function InnerApp() {

  return (
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/landing" element={<LandingPage />} />
      <Route path="/waiting-room" element={<WaitingRoom />} />
      <Route path="/login" element={<Login />} />
      <Route path="/signup" element={<Signup />} />
      <Route path="/create-room" element={<CreateRoom />} />
      <Route path="/join-room" element={<JoinRoom />} />
      <Route path="/game-room" element={<GameRoom />} />
      <Route path="/profile" element={<Profile />} />
      <Route path="/how-to-play" element={<HowToPlay />} />
      <Route path="/about" element={<AboutDev />} />
    </Routes>
  );
}

export default App;

```

## /client/src/assets/copy.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/src/assets/copy.png

## /client/src/assets/google.svg

```svg path="/client/src/assets/google.svg" 
<?xml version="1.0" encoding="utf-8"?>

<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
  <title>google</title>
  <g id="Layer_2" data-name="Layer 2">
    <g id="invisible_box" data-name="invisible box">
      <rect width="48" height="48" fill="none"/>
      <rect width="48" height="48" fill="none"/>
    </g>
    <g id="icons_Q2" data-name="icons Q2">
      <path d="M24.7,20.5v7.6H35.6a10.9,10.9,0,0,1-10.9,8,12.1,12.1,0,1,1,7.9-21.3l5.6-5.6A20,20,0,1,0,24.7,44c16.8,0,20.5-15.7,18.9-23.5Z"/>
    </g>
  </g>
</svg>
```

## /client/src/assets/guessync.gif

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/src/assets/guessync.gif

## /client/src/assets/react.svg

```svg path="/client/src/assets/react.svg" 
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
```

## /client/src/assets/send.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/src/assets/send.png

## /client/src/assets/spotify.png

Binary file available at https://raw.githubusercontent.com/yep-yogesh/Guessync/refs/heads/main/client/src/assets/spotify.png

## /client/src/components/LandingPage/AvatarSelector.jsx

```jsx path="/client/src/components/LandingPage/AvatarSelector.jsx" 
import { useEffect, useState } from "react";

const avatars = Array.from({ length: 12 }, (_, i) => `/avatars/${i + 1}.png`);

export default function AvatarSelector({ selectedAvatar = null, disabled = false }) {
  const [index, setIndex] = useState(0);
  const [fade, setFade] = useState(false);
  const [selected, setSelected] = useState(false);

  useEffect(() => {
    if (selectedAvatar) {
      const idx = avatars.indexOf(selectedAvatar);
      if (idx !== -1) {
        setIndex(idx);
        setSelected(true);
      }
    }
  }, [selectedAvatar]);

  const cycle = (dir) => {
    if (selected || disabled) return;
    setFade(true);
    setTimeout(() => {
      setIndex((prev) => (prev + dir + avatars.length) % avatars.length);
      setFade(false);
    }, 150);
  };

  const handleSelect = () => {
    localStorage.setItem("userAvatar", avatars[index]);
    setSelected(true);
  };

  const isLocked = selected || disabled;

  return (
    <div className="flex flex-col items-center px-4 sm:px-6 md:px-8">
      {/* Title */}
      <h2 className="text-2xl sm:text-3xl md:text-4xl font-black font-montserrat mb-5 mt-2 text-center">
        Choose An Avatar
      </h2>

      {/* Avatar + Arrows */}
      <div className="flex items-center gap-3 sm:gap-4">
        {/* Left button */}
        <button
          disabled={isLocked}
          onClick={() => cycle(-1)}
          className={`w-10 h-10 sm:w-[42px] sm:h-[42px] md:w-[48px] md:h-[48px] 
            text-black text-xl sm:text-2xl font-silkscreen rounded-[6px] transition 
            ${
              isLocked
                ? "bg-gray-500 opacity-50 cursor-not-allowed"
                : "bg-[#FFFB00] shadow-[0_0_18px_#FFFB00] hover:scale-110"
            }`}
        >
          &lt;
        </button>

        {/* Avatar preview */}
        <div
          className={`p-2 sm:p-3 rounded-[6px] border-[2px] border-[#FFFB00] transition-all duration-300 ${
            isLocked ? "shadow-[0_0_22px_#FFFB00]" : "shadow-[0_0_14px_#FFFB00]"
          }`}
        >
          <img
            src={avatars[index]}
            alt="avatar"
            className={`w-[80px] h-[80px] sm:w-[90px] sm:h-[90px] md:w-[100px] md:h-[100px] 
              rounded-[6px] transition-opacity duration-300 ${
              fade ? "opacity-0" : "opacity-100"
            }`}
          />
        </div>

        {/* Right button */}
        <button
          disabled={isLocked}
          onClick={() => cycle(1)}
          className={`w-10 h-10 sm:w-[42px] sm:h-[42px] md:w-[48px] md:h-[48px] 
            text-black text-xl sm:text-2xl font-silkscreen rounded-[6px] transition 
            ${
              isLocked
                ? "bg-gray-500 opacity-50 cursor-not-allowed"
                : "bg-[#FFFB00] shadow-[0_0_18px_#FFFB00] hover:scale-110"
            }`}
        >
          &gt;
        </button>
      </div>

      {/* Select button */}
      <button
        disabled={isLocked}
        onClick={handleSelect}
        className={`mt-5 font-silkscreen text-black 
          text-base sm:text-lg md:text-xl 
          py-2 sm:py-2.5 px-6 sm:px-8 rounded-[6px] transition-all duration-300 
          ${
            isLocked
              ? "bg-white shadow-[0_0_14px_white] cursor-not-allowed"
              : "bg-[#FFFB00] shadow-[0_0_18px_#FFFB00] hover:scale-105"
          }`}
      >
        {isLocked ? "SELECTED!" : "SELECT"}
      </button>
    </div>
  );
}
```

## /client/src/components/common/AvatarGrid.jsx

```jsx path="/client/src/components/common/AvatarGrid.jsx" 
import React, { useState } from "react";

const AvatarGrid = ({ selected, onSelect }) => {
  const avatarCount = 11;
  const [highlighted, setHighlighted] = useState(null);
  const [isSpinning, setIsSpinning] = useState(false);

  const handleSelect = (index) => {
    if (!isSpinning) {
      onSelect(`/avatars/${index + 1}.png`);
      setHighlighted(null);
    }
  };

  const randomize = () => {
    if (isSpinning) return;

    setHighlighted(null);
    onSelect(null);
    setIsSpinning(true);

    let totalSteps = 20 + Math.floor(Math.random() * 8);
    let current = 0;

    const spin = () => {
      setHighlighted(current % avatarCount);
      current++;

      if (current < totalSteps) {
        const delay = 80 + current * 20;
        setTimeout(spin, delay);
      } else {
        const selectedIndex = (current - 1) % avatarCount;
        onSelect(`/avatars/${selectedIndex + 1}.png`);
        setIsSpinning(false);
      }
    };

    spin();
  };

  return (
    <div className="grid grid-cols-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 gap-4 justify-items-center w-full max-w-2xl">
      {Array.from({ length: avatarCount }).map((_, i) => {
        const isSelected = selected === `/avatars/${i + 1}.png`;
        const isHighlighted = highlighted === i;
        return (
          <div
            key={i}
            onClick={() => handleSelect(i)}
            className={`w-20 h-20 sm:w-24 sm:h-24 md:w-28 md:h-28 rounded-xl overflow-hidden flex items-center justify-center cursor-pointer transition-all duration-200
              ${isSelected || isHighlighted
                ? "border-4 border-[#FFFB00] bg-[#FFFB00]/20 opacity-100 shadow-[0_0_15px_#FFFB00]"
                : "opacity-40 border-2 border-gray-600"}
              hover:opacity-100 hover:shadow-[0_0_10px_#FFFB00]`}
          >
            <img
              src={`/avatars/${i + 1}.png`}
              alt={`Avatar ${i + 1}`}
              className="w-full h-full object-cover"
            />
          </div>
        );
      })}

      {/* Dice Button */}
      <div
        onClick={randomize}
        className="w-20 h-20 sm:w-24 sm:h-24 md:w-28 md:h-28 rounded-xl flex items-center justify-center bg-black border-2 border-white cursor-pointer hover:shadow-[0_0_10px_white] transition-all"
      >
        <img src="/dice.png" alt="Random" className="w-10 sm:w-12 md:w-14" />
      </div>
    </div>
  );
};

export default AvatarGrid;
```

## /client/src/components/common/GlowingButton.jsx

```jsx path="/client/src/components/common/GlowingButton.jsx" 
export default function GlowingButton({ text, onClick, color = "white", className = "" }) {
  const colorMap = {
    white: {
      bg: "bg-white",
      text: "text-black",
      shadow: "shadow-[0_0_20px_white] hover:shadow-[0_0_30px_white]",
    },
    yellow: {
      bg: "bg-[#FFFB00]",
      text: "text-black",
      shadow: "shadow-[0_0_20px_#FFFB00] hover:shadow-[0_0_30px_#FFFB00]",
    },
    green: {
      bg: "bg-green-400",
      text: "text-black",
      shadow: "shadow-green-500 hover:shadow-green-300",
    },
  };

  const styles = colorMap[color] || colorMap.white;

  return (
    <button
      onClick={onClick}
      className={`
        px-8 py-3 rounded-[6px] font-silkscreen text-base transition-all duration-300
        ${styles.bg} ${styles.text} ${styles.shadow}
        hover:scale-105 hover:brightness-110
        ${className}
      `}
    >
      {text}
    </button>
  );
}

```

## /client/src/components/common/Navbar.jsx

```jsx path="/client/src/components/common/Navbar.jsx" 
import { useState, useEffect } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { Menu, X } from "lucide-react"; // hamburger + close icons

export default function Navbar() {
  const navigate = useNavigate();
  const location = useLocation();
  const [menuOpen, setMenuOpen] = useState(false);

  const navItems = [
    { label: "Home", path: "/landing" },
    { label: "New Room", path: "/create-room" },
    { label: "About Dev", path: "/about" },
    { label: "How to Play", path: "/how-to-play" },
  ];

  const isActive = (path) => location.pathname === path;

  // Resolve avatar from localStorage (user.avatar -> userAvatar -> default)
  const resolveAvatar = () => {
    try {
      const user = JSON.parse(localStorage.getItem("user"));
      const candidate = user?.avatar || localStorage.getItem("userAvatar");
      if (!candidate) return "/avatars/1.png";
      if (typeof candidate === "number") return `/avatars/${candidate}.png`;
      if (typeof candidate === "string") {
        if (/^https?:\/\//.test(candidate)) return candidate;           // absolute URL
        if (candidate.startsWith("/avatars/")) return candidate;        // ready path
        if (/^\d+$/.test(candidate)) return `/avatars/${candidate}.png`; // numeric string
        return candidate;                                               // fallback as-is
      }
      return "/avatars/1.png";
    } catch {
      return "/avatars/1.png";
    }
  };

  const [avatarSrc, setAvatarSrc] = useState(resolveAvatar());
  useEffect(() => {
    const update = () => setAvatarSrc(resolveAvatar());
    window.addEventListener("storage", update);     // cross-tab changes
    window.addEventListener("focus", update);       // when tab refocuses
    window.addEventListener("avatar-updated", update); // custom event support
    return () => {
      window.removeEventListener("storage", update);
      window.removeEventListener("focus", update);
      window.removeEventListener("avatar-updated", update);
    };
  }, []);

  // Only show logo on auth pages
  const isAuthPage = location.pathname === "/login" || location.pathname === "/signup";

  return (
    <nav className="w-full bg-black text-white font-montserrat shadow-md relative z-50">
      <div className="flex items-center justify-between px-6 py-4">
        {/* Logo */}
        <div
          className="flex items-center cursor-pointer"
          onClick={() => {
            setMenuOpen(false);
            navigate("/landing");
          }}
          role="button"
          aria-label="Go to Home"
          tabIndex={0}
          onKeyDown={(e) => {
            if (e.key === "Enter" || e.key === " ") {
              e.preventDefault();
              setMenuOpen(false);
              navigate("/landing");
            }
          }}
        >
          <img src="/logo.png" alt="logo" className="w-10 h-10 mt-1.5" />
          <h1 className="ml-2 text-2xl font-silkscreen">
            GUES
            <span className="text-[#FFFB00] drop-shadow-[0_0_5px_#FFFB00]">
              SYNC
            </span>
          </h1>
        </div>

        {/* Desktop Menu */}
        {!isAuthPage && (
          <div className="hidden md:flex items-center gap-8 text-lg font-medium">
            {navItems.map((item) => (
              <button
                key={item.label}
                onClick={() => navigate(item.path)}
                aria-current={isActive(item.path) ? "page" : undefined}
                className={`relative z-10 after:absolute after:left-0 after:bottom-[-5px] after:h-[2px] after:bg-[#FFFB00] after:shadow-[0_0_10px_#FFFB00] after:transition-all after:duration-300
                  ${isActive(item.path) ? "text-[#FFFB00] after:w-full" : "after:w-0 hover:after:w-full"}`}
              >
                {item.label}
              </button>
            ))}

            <img
              src={avatarSrc}
              onClick={() => navigate("/profile")}
              alt="profile"
              onError={(e) => { e.currentTarget.src = "/avatars/1.png"; }}
              className="w-10 h-10 rounded-full border-[2px] border-[#FFFB00] shadow-[0_0_10px_#FFFB00] cursor-pointer relative z-10"
            />
          </div>
        )}

        {/* Mobile Hamburger */}
        {!isAuthPage && (
          <div className="md:hidden">
            <button
              onClick={() => setMenuOpen(!menuOpen)}
              className="text-white hover:text-[#FFFB00] transition"
            >
              {menuOpen ? <X size={32} /> : <Menu size={32} />}
            </button>
          </div>
        )}
      </div>

      {/* Mobile Dropdown */}
      {!isAuthPage && (
        <div
          className={`md:hidden flex flex-col items-center bg-black text-lg font-medium gap-6 py-6 transform transition-all duration-500 ease-in-out relative z-40 ${
            menuOpen
              ? "max-h-[500px] opacity-100 scale-100"
              : "max-h-0 opacity-0 scale-95 overflow-hidden"
          }`}
        >
          {navItems.map((item) => (
            <button
              key={item.label}
              onClick={() => {
                navigate(item.path);
                setMenuOpen(false);
              }}
              aria-current={isActive(item.path) ? "page" : undefined}
              className={`relative z-10 after:absolute after:left-0 after:bottom-[-5px] after:h-[2px] after:bg-[#FFFB00] after:shadow-[0_0_10px_#FFFB00] after:transition-all after:duration-300
                ${isActive(item.path) ? "text-[#FFFB00] after:w-full" : "after:w-0 hover:after:w-full"}`}
            >
              {item.label}
            </button>
          ))}
          <img
            src={avatarSrc}
            onClick={() => {
              navigate("/profile");
              setMenuOpen(false);
            }}
            alt="profile"
            onError={(e) => { e.currentTarget.src = "/avatars/1.png"; }}
            className="w-12 h-12 rounded-full border-[2px] border-[#FFFB00] shadow-[0_0_10px_#FFFB00] cursor-pointer relative z-10"
          />
        </div>
      )}
    </nav>
  );
}
```

## /client/src/config/firebase.js

```js path="/client/src/config/firebase.js" 
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider } from 'firebase/auth';

const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_FIREBASE_APP_ID,
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const provider = new GoogleAuthProvider();

export { auth, provider };

```

## /client/src/context/AuthContext.jsx

```jsx path="/client/src/context/AuthContext.jsx" 
import React, { createContext, useState, useEffect } from 'react';

export const AuthContext = createContext();

const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null); // Firebase user
  const [isGuest, setIsGuest] = useState(false);
  const [guestName, setGuestName] = useState('');
  const [guestAvatar, setGuestAvatar] = useState('');
  const [authType, setAuthType] = useState(''); // 'user' | 'guest' | ''

  useEffect(() => {
    // Load Firebase user if exists
    const storedUser = JSON.parse(localStorage.getItem('user'));
    if (storedUser) {
      setUser(storedUser);
      setAuthType('user');
    }

    // Load guest data if exists
    const storedGuest = JSON.parse(localStorage.getItem('guest'));
    if (storedGuest && storedGuest.name) {
      setIsGuest(true);
      setGuestName(storedGuest.name);
      setGuestAvatar(storedGuest.avatar || '');
      setAuthType('guest');
    }
  }, []);

  const loginAsGuest = (name, avatar) => {
    setIsGuest(true);
    setGuestName(name);
    setGuestAvatar(avatar);
    setAuthType('guest');
    localStorage.setItem('guest', JSON.stringify({ name, avatar }));
  };

  const logoutGuest = () => {
    setIsGuest(false);
    setGuestName('');
    setGuestAvatar('');
    setAuthType('');
    localStorage.removeItem('guest');
  };

  const logout = () => {
    setUser(null);
    setAuthType('');
    localStorage.removeItem('user');
    logoutGuest(); // Also clears guest if any
  };

  return (
    <AuthContext.Provider
      value={{
        user,
        setUser,
        isGuest,
        guestName,
        guestAvatar,
        authType,
        loginAsGuest,
        logoutGuest,
        logout,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export default AuthProvider;

```

## /client/src/data/avatarList.js

```js path="/client/src/data/avatarList.js" 
const avatarList = [
    "/avatars/1.png",
    "/avatars/2.png",
    "/avatars/3.png",
    "/avatars/4.png",
    "/avatars/5.png",
    "/avatars/6.png",
    "/avatars/7.png",
    "/avatars/8.png",
    "/avatars/9.png",
    "/avatars/10.png",
    "/avatars/11.png",
    "/avatars/12.png"
  ];
  
  export default avatarList;
  
```

## /client/src/index.css

```css path="/client/src/index.css" 
@import url('https://fonts.googleapis.com/css2?family=Silkscreen:wght@400;700&display=swap');
@import "tailwindcss";


@theme {
    --font-silkscreen: "Silkscreen", sans-serif; 
  }

/* Custom Scrollbar Styles */
.custom-scrollbar {
  /* Webkit browsers */
  scrollbar-width: thin;
  scrollbar-color: #FBBF24 #374151;
}

.custom-scrollbar::-webkit-scrollbar {
  width: 8px;
}

.custom-scrollbar::-webkit-scrollbar-track {
  background: #374151;
  border-radius: 10px;
}

.custom-scrollbar::-webkit-scrollbar-thumb {
  background: #FBBF24;
  border-radius: 10px;
  border: 1px solid #374151;
}

.custom-scrollbar::-webkit-scrollbar-thumb:hover {
  background: #F59E0B;
}
```

## /client/src/main.jsx

```jsx path="/client/src/main.jsx" 
import React from "react"; 
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css"; // <- Tailwind styles here
import "@fontsource/montserrat/600.css";
import "@fontsource/montserrat/900.css";
import "@fontsource/silkscreen";

ReactDOM.createRoot(document.getElementById("root")).render(
  // REMOVE <React.StrictMode> during development
  <App />
);
 
```

## /client/src/pages/AboutDev.jsx

```jsx path="/client/src/pages/AboutDev.jsx" 
import { useEffect, useState } from "react";
import Navbar from "../components/common/Navbar";

const AboutDev = () => {
  const [displayText, setDisplayText] = useState("");
  const fullText = "Hey, I'm Yogesh!👋";

  useEffect(() => {
    let i = 0;
    const interval = setInterval(() => {
      if (i < fullText.length) {
        setDisplayText(fullText.slice(0, i + 1));
        i++;
      } else {
        clearInterval(interval);
      }
    }, 100);

    return () => clearInterval(interval);
  }, []);

  const skills = [
    { name: "Python", icon: "fab fa-python" },
    { name: "JavaScript", icon: "fab fa-js" },
    { name: "HTML5", icon: "fab fa-html5" },
    { name: "CSS3", icon: "fab fa-css3-alt" },
    { name: "React", icon: "fab fa-react" },
    { name: "Node.js", icon: "fab fa-node-js" },
    { name: "Express.js", icon: "fas fa-server" },
    { name: "Git", icon: "fab fa-git-alt" },
    { name: "MongoDB", icon: "fas fa-database" },
    { name: "GitHub", icon: "fab fa-github" },
    { name: "Firebase", icon: "fas fa-fire" },
    { name: "Socket.io", icon: "fas fa-plug" },
    { name: "Tailwind CSS", icon: "fab fa-css3" },
  ];

  return (
    <div className="h-screen bg-black text-white font-sans relative flex flex-col overflow-y-auto lg:overflow-hidden">
      <style>
        {`
          .social-icon {
            color: #9CA3AF;
            font-size: 1.6rem;
            transition: color 0.3s, transform 0.3s;
          }
          .social-icon:hover {
            color: #FFFB00;
            text-shadow: 0 0 15px #FFFB00, 0 0 25px #FFFB00;
            transform: translateY(-3px);
          }
          .typing-cursor::after {
            content: '|';
            animation: blink 1s infinite;
            margin-left: 2px;
          }
          @keyframes blink {
            0%, 100% { opacity: 1; }
            50% { opacity: 0; }
          }
          .skill-box {
            background-color: rgba(255, 255, 255, 0.05);
            border: 1px solid rgba(255, 255, 255, 0.1);
            border-radius: 8px;
            padding: 6px 12px;
            color: #9CA3AF;
            font-size: 0.85rem;
            font-weight: 500;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            margin: 4px;
            transition: all 0.3s ease;
            position: relative;
            overflow: hidden;
            height: 38px;
          }
          .skill-name {
            transition: all 0.3s ease;
            z-index: 1;
          }
          .skill-icon {
            position: absolute;
            font-size: 1.2rem;
            color: black;
            opacity: 0;
            transition: all 0.3s ease;
            z-index: 2;
          }
          .skill-box:hover {
            background-color: #FFFB00;
            border-color: #FFFB00;
            box-shadow: 0 0 10px #FFFB00, 0 0 20px #FFFB00;
            transform: translateY(-2px);
          }
          .skill-box:hover .skill-name {
            opacity: 0;
            transform: translateY(10px);
          }
          .skill-box:hover .skill-icon {
            opacity: 1;
          }
        `}
      </style>

      {/* Fixed Navbar */}
      <Navbar />

      {/* Main Content */}
      <div className="flex-1 flex flex-col md:flex-row justify-center items-center gap-1 sm:gap-5 lg:gap-14 px-4 sm:px-8 lg:px-12 pt-0 pb-2 sm:py-6">
        {/* Profile Image */}
        <div className="relative flex justify-center items-center">
          <img
            src="/pfp.png"
            alt="Profile"
            className="w-40 h-40 sm:w-52 sm:h-52 md:w-64 md:h-64 lg:w-[450px] lg:h-[450px] object-contain rounded-full z-10"
          />
        </div>

        {/* Info Card */}
        <div className="relative bg-[#0A0A0A] rounded-2xl border-2 sm:border-4 border-[#FFFB00] shadow-lg px-5 sm:px-8 lg:px-10 py-6 sm:py-8 w-full max-w-[550px] lg:max-w-[700px] lg:min-h-[400px] flex flex-col justify-between">
          <div className="text-left">
            <h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold text-[#FFFB00] mb-2 tracking-wide typing-cursor">
              {displayText}
            </h1>

            <h2 className="text-sm sm:text-base lg:text-lg mt-2 text-white mb-3 tracking-wider flex items-center gap-2">
              <i className="fas fa-laptop-code text-[#FFFB00]"></i> Developer of{" "}
              <span className="font-silkscreen">Gues<span className="text-white">sync</span></span>
            </h2>

            <p className="text-gray-300 text-sm sm:text-base lg:text-lg leading-relaxed">
              I'm a full-stack developer who loves crafting playful, real-time web apps.
              Guessync was built pixel-by-pixel to bring that retro arcade magic back to life,
              blending music, fast guessing, and teamwork into pure fun. 🚀
            </p>

            <div className="mt-4">
              <h3 className="text-lg sm:text-xl font-bold text-white mb-3 tracking-wider">My Tech Stack</h3>
              <div className="flex flex-wrap gap-2">
                {skills.map((skill, index) => (
                  <div key={index} className="skill-box">
                    <span className="skill-name">{skill.name}</span>
                    <i className={`skill-icon ${skill.icon}`}></i>
                  </div>
                ))}
              </div>
            </div>
          </div>

          {/* Social + CTA */}
          <div className="flex flex-col sm:flex-row justify-between items-center gap-6 sm:gap-0 mt-6">
            <div className="flex gap-5">
              <a href="https://yogeshwaran-manivannan.netlify.app/" target="_blank" rel="noopener noreferrer" className="social-icon">
                <i className="fas fa-globe"></i>
              </a>
              <a href="https://github.com/yep-yogesh" target="_blank" rel="noopener noreferrer" className="social-icon">
                <i className="fab fa-github"></i>
              </a>
              <a href="https://www.linkedin.com/in/yogeshwaran-m-3b19452a9/" target="_blank" rel="noopener noreferrer" className="social-icon">
                <i className="fab fa-linkedin"></i>
              </a>
              <a href="https://www.instagram.com/wtfisyogesh" target="_blank" rel="noopener noreferrer" className="social-icon">
                <i className="fab fa-instagram"></i>
              </a>
              <a href="https://leetcode.com/u/yogeshhhhhh/" target="_blank" rel="noopener noreferrer" className="social-icon">
                <i className="fas fa-code"></i>
              </a>
            </div>
            <a
              href="https://buymeacoffee.com/yogeshh"
              target="_blank"
              rel="noopener noreferrer"
              className="w-full sm:w-auto bg-[#FFFB00] text-black font-bold px-6 sm:px-8 py-2 rounded-lg hover:bg-black hover:text-[#FFFB00] border-2 border-transparent hover:border-[#FFFB00] transition-all duration-300 text-center"
            >
              Tip the Dev
            </a>
          </div>
        </div>
      </div>
    </div>
  );
};

export default AboutDev;
```

## /client/src/pages/CreateRoom.jsx

```jsx path="/client/src/pages/CreateRoom.jsx" 
import React, { useEffect, useState, useRef } from "react";
import Navbar from "../components/common/Navbar";
import { useNavigate } from "react-router-dom";
import socket from "../socket";
import sendIcon from "../assets/send.png";
import spotcon from "../assets/spotify.png";
import { auth } from "../config/firebase"; // <-- add


const DEFAULT_PLAYLISTS = {
  Tamil: "316WvxScpeCbfvWVrTHfPa",
  Hindi: "4stlIpoPS7uKCsmUA7D8KZ",
  English: "2clGinIH6s1bj2TclAgKzW",
  Malayalam: "5tU4P1GOBDBs9nwfok79yD",
  Telugu: "1llHjtjECBo12ChwOGe38L",
  Kannada: "6utix5lfPoZkBirWlRujqa"
};

const CreateRoom = () => {
  const [roomCode, setRoomCode] = useState("");
  const [players, setPlayers] = useState(1);
  const [rounds, setRounds] = useState(1);
  const [duration, setDuration] = useState(30);
  const [spotifyInputVisible, setSpotifyInputVisible] = useState(false);
  const [spotifyValue, setSpotifyValue] = useState("");
  const [spotifyConfirmed, setSpotifyConfirmed] = useState(false);
  const [showModal, setShowModal] = useState(false);
  const [selectedLanguage, setSelectedLanguage] = useState("");
  const [isCreating, setIsCreating] = useState(false);
  const navigate = useNavigate();

  useEffect(() => {
  const handleResize = () => {
    if (window.innerWidth >= 1024) {
      document.body.style.overflow = "hidden"; // lock scroll
    } else {
      document.body.style.overflow = "auto";   // allow scroll
    }
  };

  handleResize(); // run on mount
  window.addEventListener("resize", handleResize);

  return () => {
    document.body.style.overflow = "auto"; // cleanup
    window.removeEventListener("resize", handleResize);
  };
}, []);


  useEffect(() => {
    window.scrollTo(0, 0);
    const code = Math.floor(100000 + Math.random() * 900000);
    setRoomCode(code);
  }, []);

  const toggleLanguage = (lang) => {
    setSelectedLanguage((prev) => (prev === lang ? "" : lang));
  };

  // Smooth moving blobs overlay (theme-matched)
  const LoadingOverlay = ({ text = "Loading..." }) => {
    const [movingBlobs, setMovingBlobs] = useState([]);
    const rafRef = useRef(null);

    useEffect(() => {
      const genBlobs = () => {
        const blobs = [];
        const numBlobs = Math.floor(Math.random() * 70) + 20;
        for (let i = 0; i < numBlobs; i++) {
          const size = Math.random() * 160 + 180;
          const speedX = (Math.random() * 2 - 1) * 6.5;
          const speedY = (Math.random() * 2 - 1) * 6.5;
          const x = Math.random() * window.innerWidth;
          const y = Math.random() * window.innerHeight;
          const opacity = Math.random() * 0.05 + 0.07;
          blobs.push({ id: i, size, x, y, speedX, speedY, opacity, color: "#FFFB00" });
        }
        setMovingBlobs(blobs);
      };

      const animate = () => {
        setMovingBlobs((prev) =>
          prev.map((b) => {
            let x = b.x + b.speedX;
            let y = b.y + b.speedY;
            if (x < 0 || x > window.innerWidth - b.size) b.speedX *= -1, (x = b.x + b.speedX);
            if (y < 0 || y > window.innerHeight - b.size) b.speedY *= -1, (y = b.y + b.speedY);
            return { ...b, x, y };
          })
        );
        rafRef.current = requestAnimationFrame(animate);
      };

      genBlobs();
      animate();
      return () => {
        if (rafRef.current) cancelAnimationFrame(rafRef.current);
      };
    }, []);

    return (
      <div className="fixed inset-0 z-50 bg-black text-white flex items-center justify-center">
        {/* Background floating blobs */}
        {movingBlobs.map((blob) => (
          <div
            key={blob.id}
            className="absolute rounded-full blur-3xl"
            style={{
              width: `${blob.size}px`,
              height: `${blob.size}px`,
              left: `${blob.x}px`,
              top: `${blob.y}px`,
              backgroundColor: blob.color,
              opacity: blob.opacity,
            }}
          />
        ))}
        {/* Center spinner + text */}
        <div className="relative z-10 flex flex-col items-center font-silkscreen">
          <div className="w-12 h-12 border-4 border-[#FFFB00] border-t-transparent rounded-full animate-spin mb-6"></div>
          <h2 className="text-2xl font-bold">{text}</h2>
        </div>
      </div>
    );
  };

  const handleCreateRoom = async () => {
    // const token = localStorage.getItem("token");
    // const user = JSON.parse(localStorage.getItem("user"));
    const currentUser = auth.currentUser;
    if (!currentUser) {
      alert("Please log in first!");
      navigate("/login");
      return;
    }

    // show lazy loader immediately after auth check
    setIsCreating(true);

    const getToken = async (force = false) => {
      const t = await currentUser.getIdToken(force);
      localStorage.setItem("token", t); // keep localStorage in sync for other consumers
      return t;
    };

    const storedUser = JSON.parse(localStorage.getItem("user")) || {
      name: currentUser.displayName || "Player",
      uid: currentUser.uid,
      avatar: localStorage.getItem("userAvatar") || currentUser.photoURL || "/avatars/1.png",
    };
    const token = await getToken();

    let playlistId = null;
    let useSpotify = false;

    if (selectedLanguage && DEFAULT_PLAYLISTS[selectedLanguage]) {
      playlistId = DEFAULT_PLAYLISTS[selectedLanguage];
      useSpotify = true;
    } else if (spotifyConfirmed && spotifyValue.trim()) {
      const extracted = spotifyValue.split("playlist/")[1]?.split("?")[0];
      if (!extracted) {
        alert("❌ Invalid Spotify Playlist URL");
        setIsCreating(false);
        return;
      }
      playlistId = extracted;
      useSpotify = true;
    }

    await sendRequest();

    async function sendRequest() {
      const payload = {
        uid: storedUser.uid,
        name: storedUser.name,
        avatar: storedUser.avatar || "https://i.imgur.com/placeholder.png",
        useSpotify,
        playlistId,
        players,
        rounds,
        duration,
        language: selectedLanguage,
        code: roomCode,
      };

      try {
        const doFetch = async (tok) =>
          fetch("https://guessync.onrender.com/api/room/create", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              Authorization: `Bearer ${tok}`,
            },
            body: JSON.stringify(payload),
          });

        let res = await doFetch(token);
        if (res.status === 401 || res.status === 403) {
          const refreshed = await getToken(true);
          res = await doFetch(refreshed);
        }

        const data = await res.json();

        if (res.ok) {
          const room = {
            code: data.code,
            hostUID: storedUser.uid,
            players: [{ uid: storedUser.uid, name: storedUser.name, avatar: storedUser.avatar }],
          };
          localStorage.setItem("room", JSON.stringify(room));
          socket.auth = { uid: storedUser.uid };
          if (socket.disconnected) socket.connect();
          socket.emit("join-room", { roomCode: data.code, user: storedUser });
          navigate("/waiting-room");
        } else {
          alert("❌ Failed to create room: " + (data.message || "Unauthorized"));
          setIsCreating(false);
        }
      } catch (err) {
        console.error("Error creating room:", err);
        alert("❌ Something went wrong.");
        setIsCreating(false);
      }
    }
  };

  const showCreateBtn = selectedLanguage || spotifyConfirmed;

  // New: animated “COPIED” view support
  const [displayChars, setDisplayChars] = useState(["_", "_", "_", "_", "_", "_"]);
  const [showCopyLabel, setShowCopyLabel] = useState(false);
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
  const timeoutsRef = useRef([]);

  // Helper to clear animation timers
  const clearAllTimers = () => {
    timeoutsRef.current.forEach((t) => clearTimeout(t));
    timeoutsRef.current = [];
  };

  // Letter-by-letter reveal animation
  const revealWord = (word, stepDelay = 80) => {
    const arr = Array(6).fill("_");
    setDisplayChars(arr);
    word.split("").forEach((ch, idx) => {
      const t = setTimeout(() => {
        setDisplayChars((prev) => {
          const next = [...prev];
          next[idx] = ch;
          return next;
        });
      }, idx * stepDelay);
      timeoutsRef.current.push(t);
    });
  };

  // Initialize display chars with reveal animation starting from underscores
  useEffect(() => {
    if (!roomCode) return;
    const codeStr = String(roomCode).padStart(6, "0");
    // Start with underscores and reveal digits
    setDisplayChars(Array(6).fill("_"));
    
    // Add a small delay before starting the reveal
    const initialT = setTimeout(() => {
      revealWord(codeStr, 120);
    }, 300);
    timeoutsRef.current.push(initialT);
  }, [roomCode]);

  // Copy handler with progressive underscore transition then progressive "COPIED" reveal
  const handleCopyCode = async () => {
    try {
      await navigator.clipboard.writeText(String(roomCode));
    } catch {}
    clearAllTimers();

    // Progressive change to underscores (left to right)
    displayChars.forEach((_, idx) => {
      const t = setTimeout(() => {
        setDisplayChars((prev) => {
          const next = [...prev];
          next[idx] = "_";
          return next;
        });
      }, idx * 80);
      timeoutsRef.current.push(t);
    });

    // After all underscores are shown, progressively reveal "COPIED"
    const startCopiedReveal = setTimeout(() => {
      "COPIED".split("").forEach((ch, idx) => {
        const t = setTimeout(() => {
          setDisplayChars((prev) => {
            const next = [...prev];
            next[idx] = ch;
            return next;
          });
        }, idx * 100);
        timeoutsRef.current.push(t);
      });
    }, displayChars.length * 80 + 200);
    timeoutsRef.current.push(startCopiedReveal);

    // Revert back to original code
    const revertT = setTimeout(() => {
      const codeStr = String(roomCode).padStart(6, "0");
      // Progressive change back to underscores
      "COPIED".split("").forEach((_, idx) => {
        const t = setTimeout(() => {
          setDisplayChars((prev) => {
            const next = [...prev];
            next[idx] = "_";
            return next;
          });
        }, idx * 60);
        timeoutsRef.current.push(t);
      });
      
      // Then reveal the original code
      const finalReveal = setTimeout(() => {
        revealWord(codeStr, 80);
      }, 6 * 60 + 100);
      timeoutsRef.current.push(finalReveal);
    }, 2200);
    timeoutsRef.current.push(revertT);
  };

  const handleMouseMove = (e) => {
    setMousePos({ x: e.clientX, y: e.clientY });
  };

  useEffect(() => {
    return () => clearAllTimers();
  }, []);

  return (
    <div className="bg-black min-h-screen flex flex-col text-white font-montserrat">
      {/* Sticky copy label */}
      {showCopyLabel && (
        <div
          className="fixed z-50 pointer-events-none bg-[#FFFB00] text-black px-2 py-1 rounded text-sm font-silkscreen transform -translate-x-1/2 -translate-y-8"
          style={{ left: mousePos.x, top: mousePos.y }}
        >
          Copy
        </div>
      )}

      {isCreating && <LoadingOverlay text="Creating Room..." />}
      {!isCreating && <Navbar />}

      <div className="flex-1 flex flex-col items-center px-4 py-6 sm:px-6 md:px-8 lg:justify-center lg:scale-125">

        {/* Title */}
        <h1 className="text-2xl sm:text-3xl md:text-4xl font-black mb-3 sm:mb-4 drop-shadow-[0_0_5px_#111] mt-6 sm:mt-10 text-center">
          Create A New Room
        </h1>

        {/* Room Code Section */}
        <div className="flex items-center gap-2 sm:gap-3 mb-4 sm:mb-5 relative justify-center">
          <span className="font-silkscreen text-xl sm:text-xl md:text-2xl tracking-widest drop-shadow-[0_0_5px_#fff]">
            ROOM CODE:
          </span>

          {/* Code + Copy */}
          <div className="flex items-center gap-2 sm:gap-3 relative">
            {/* Per-character rendering with adjusted spacing */}
            <div className="flex items-center">
              {displayChars.map((ch, i) => (
                <span
                  key={i}
                  className="font-silkscreen text-2xl sm:text-xl md:text-2xl text-[#FFFB00] drop-shadow-[0_0_5px_#FFFB00]"
                  style={{ 
                    width: "1ch", 
                    textAlign: "center", 
                    display: "inline-block",
                    marginLeft: i === 1 ? "0.15ch" : "0" // Move "i" in COPIED slightly right
                  }}
                >
                  {ch || "_"}
                </span>
              ))}
            </div>

            {/* Paste-style copy button with hover label */}
            <button
              type="button"
              onClick={handleCopyCode}
              onMouseEnter={() => setShowCopyLabel(true)}
              onMouseLeave={() => setShowCopyLabel(false)}
              onMouseMove={handleMouseMove}
              className="p-2 rounded-md hover:bg-[#FFFB00]/10 focus:outline-none focus:ring-2 focus:ring-[#FFFB00]/40 shrink-0 flex-none transition-colors duration-200 cursor-pointer"
              aria-label="Copy room code"
              title="Copy room code"
            >
              {/* Paste/clipboard SVG */}
              <svg
                width="28"
                height="28"
                viewBox="0 0 24 24"
                className="text-white hover:text-[#FFFB00] transition-colors duration-200"
                fill="none"
                xmlns="http://www.w3.org/2000/svg"
              >
                <rect x="8" y="2" width="8" height="4" rx="1" stroke="currentColor" strokeWidth="1.5" />
                <path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2" stroke="currentColor" strokeWidth="1.5" />
                <path d="M9 12h6M9 16h6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
              </svg>
            </button>
          </div>
        </div>

        {/* Custom Controls */}
        <div className="w-full max-w-2xl mx-auto text-center mb-6 sm:mb-8">
          {/* Large screens: heading only (removed Upload Rules button) */}
          <div className="hidden lg:block mb-6">
            <h2 className="text-white font-black text-[1.53rem] drop-shadow-[0_0_5px_#111]">
              CUSTOM CONTROLS
            </h2>
          </div>

          {/* Small screens: heading (unchanged) */}
          <div className="lg:hidden mb-6">
            <h2 className="text-white font-black text-xl sm:text-[1.53rem] drop-shadow-[0_0_5px_#111]">
              CUSTOM CONTROLS
            </h2>
          </div>

          {/* Players and Rounds Controls */}
          {[
            { label: "NO. OF PLAYERS", value: players, set: setPlayers, max: 12 },
            { label: "NO. OF ROUNDS", value: rounds, set: setRounds, max: 20 },
          ].map((ctrl, i) => (
            <div key={i} className="flex flex-col sm:flex-row justify-center items-center mb-4 sm:mb-6 gap-3 sm:gap-8">
              <label className="w-full sm:w-[255px] font-silkscreen text-base sm:text-[1.275rem] whitespace-nowrap drop-shadow-[0_0_5px_#fff] text-center sm:text-left">
                {ctrl.label}
              </label>
              <div className="flex items-center gap-3 sm:gap-5">
                <button
                  className="bg-[#FFFB00] w-10 h-10 sm:w-[51px] sm:h-[51px] font-silkscreen rounded-lg text-black text-xl sm:text-[1.53rem] drop-shadow-[0_0_7px_#FFFB00] cursor-pointer"
                  onClick={() => ctrl.set(Math.max(1, ctrl.value - 1))}
                >
                  -
                </button>
                <span className="bg-white w-10 h-10 sm:w-[51px] sm:h-[51px] text-black rounded-lg font-silkscreen text-xl sm:text-[1.53rem] drop-shadow-[0_0_7px_#fff] flex justify-center items-center">
                  {ctrl.value}
                </span>
                <button
                  className="bg-[#FFFB00] w-10 h-10 sm:w-[51px] sm:h-[51px] font-silkscreen rounded-lg text-black text-xl sm:text-[1.53rem] drop-shadow-[0_0_7px_#FFFB00] cursor-pointer"
                  onClick={() => ctrl.set(Math.min(ctrl.max, ctrl.value + 1))}
                >
                  +
                </button>
              </div>
            </div>
          ))}

          {/* Duration Control */}
          <div className="flex flex-col sm:flex-row justify-center items-center mb-4 sm:mb-6 gap-3 sm:gap-10">
            <label className="w-full sm:w-[255px] font-silkscreen text-base sm:text-[1.275rem] whitespace-nowrap drop-shadow-[0_0_5px_#fff] text-center sm:text-left">
              DURATION OF SONG
            </label>
            <div className="flex gap-3 sm:gap-5">
              {[30, 60].map((val) => (
                <button
                  key={val}
                  onClick={() => setDuration(val)}
                  className={`duration-btn font-silkscreen px-4 py-2 sm:px-6 sm:py-2 rounded-lg border-[3px] text-sm sm:text-[17px] transition cursor-pointer ${
                    duration === val
                      ? "border-[#FFFB00] text-[#FFFB00] bg-[#FFFB00]/20 shadow-[0_0_15px_#FFFB00]"
                      : "border-gray-600 text-gray-500 bg-gray-500/20 hover:border-[#FFFB00] hover:text-[#FFFB00] hover:bg-[#FFFB00]/10 hover:shadow-[0_0_15px_#111]"
                  }`}
                >
                  {val}
                </button>
              ))}
            </div>
          </div>
        </div>

        {/* Action Buttons - Centered and enlarged for larger devices */}
        <div className="flex flex-col sm:flex-row gap-4 sm:gap-8 mb-6 sm:mb-10 justify-center items-center w-full max-w-2xl px-4">
          {!spotifyConfirmed && !showCreateBtn && (
            <button
              onClick={() => setShowModal(true)}
              className="font-silkscreen w-full md:w-[280px] py-3 md:py-4 rounded-lg bg-[#FFFB00] text-black text-base md:text-[1.3rem] drop-shadow-[0_0_7px_#FFFB00] hover:drop-shadow-[0_0_10px_#FFFB00] cursor-pointer"
            >
              CHOOSE LANGUAGE
            </button>
          )}

          {!selectedLanguage && !showCreateBtn && (
            <div className="relative w-full md:w-[280px] h-12 md:h-14">
              {!spotifyInputVisible ? (
                <button
                  className="w-full h-full bg-[#1ED760] text-black font-silkscreen py-2 px-4 rounded-lg text-sm md:text-[1.1rem] drop-shadow-[0_0_7px_#1ED760] hover:drop-shadow-[0_0_10px_#1ED760] flex items-center justify-center gap-2 cursor-pointer"
                  onClick={() => setSpotifyInputVisible(true)}
                >
                  <img src={spotcon} alt="spotify" className="h-4 md:h-6" />
                  CONNECT SPOTIFY
                </button>
              ) : (
                <div className="absolute top-0 left-0 w-full h-full bg-white flex items-center justify-between px-3 rounded-lg border-[3px] border-[#1ED760]">
                  <input
                    type="text"
                    className="bg-transparent w-full text-black font-silkscreen text-xs md:text-base placeholder:text-gray-500 outline-none"
                    placeholder="Enter playlist link"
                    value={spotifyValue}
                    onChange={(e) => setSpotifyValue(e.target.value)}
                  />
                  <img
                    src={sendIcon}
                    alt="send"
                    className="w-4 h-4 md:w-5 md:h-5 cursor-pointer"
                    onClick={() => {
                      if (spotifyValue.trim()) setSpotifyConfirmed(true);
                    }}
                  />
                </div>
              )}
            </div>
          )}

          {selectedLanguage && (
            <div className="text-[#FFFB00] font-silkscreen text-base md:text-xl text-center">
              Using {selectedLanguage} playlist
            </div>
          )}

          {showCreateBtn && (
            <button
              onClick={handleCreateRoom}
              disabled={isCreating}
              className={`bg-[#FFFB00] text-black font-silkscreen px-6 py-3 rounded-lg text-base md:text-xl drop-shadow-[0_0_7px_#FFFB00] hover:drop-shadow-[0_0_10px_#FFFB00] w-full md:w-[240px] md:h-14 ${isCreating ? "opacity-60 cursor-not-allowed hover:drop-shadow-none" : "cursor-pointer"}`}
            >
              {isCreating ? "CREATING..." : "CREATE ROOM"}
            </button>
          )}
        </div>

        {/* Language Selection Modal */}
        {showModal && (
          <div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
            <div className="bg-black border-4 border-[#FFFB00] p-4 sm:p-6 md:p-8 rounded-lg w-full max-w-md sm:max-w-lg md:max-w-xl drop-shadow-[0_0_10px_#111] text-center relative animate-fade-in">
              <button
                className="absolute -top-3 -left-3 w-12 h-12 bg-[#FFFB00] text-black rounded-full shadow-lg hover:scale-110 hover:bg-white transition-all duration-300 flex items-center justify-center group border-2 border-black cursor-pointer"
                onClick={() => setShowModal(false)}
                aria-label="Close modal"
              >
                <svg
                  width="20"
                  height="20"
                  viewBox="0 0 24 24"
                  className="transform group-hover:-translate-x-0.5 transition-transform duration-300"
                  fill="none"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path
                    d="M19 12H5M12 19l-7-7 7-7"
                    stroke="currentColor"
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              </button>
              <h2 className="text-white font-black text-xl sm:text-2xl md:text-3xl mb-4">
                Pick Language
              </h2>
              <p className="font-silkscreen text-[#FFFB00] text-base sm:text-lg md:text-xl mb-4 sm:mb-6">
                Select one language
              </p>
              <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 mb-4 sm:mb-6">
                {Object.keys(DEFAULT_PLAYLISTS).map((lang) => (
                  <button
                    key={lang}
                    onClick={() => toggleLanguage(lang)}
                    className={`py-2 px-3 sm:py-3 sm:px-4 rounded-lg font-silkscreen text-base sm:text-[1.1rem] border-[3px] transition cursor-pointer ${
                      selectedLanguage === lang
                        ? "text-[#FFFB00] border-[#FFFB00] bg-[#FFFB00]/5 drop-shadow-[0_0_7px_#FFFB00]"
                        : "text-gray-500 bg-gray-500/20 border-gray-500 hover:border-[#FFFB00] hover:text-[#FFFB00] hover:drop-shadow-[0_0_7px_#111]"
                    }`}
                  >
                    {lang}
                  </button>
                ))}
              </div>
              <button
                className="bg-white text-black font-silkscreen px-6 py-2 sm:px-8 sm:py-3 rounded-lg text-base sm:text-[1.2rem] drop-shadow-[0_0_7px_111] hover:drop-shadow-[0_0_10px_#111] cursor-pointer"
                onClick={() => {
                  if (selectedLanguage) {
                    setShowModal(false);
                  } else {
                    alert("Please select a language.");
                  }
                }}
              >
                Done
              </button>
            </div>
          </div>
        )}

        {/* Add CSS animations */}
        <style jsx>{`
          @keyframes fadeIn {
            from {
              opacity: 0;
              transform: scale(0.95) translateY(-10px);
            }
            to {
              opacity: 1;
              transform: scale(1) translateY(0);
            }
          }
          .animate-fade-in {
            animation: fadeIn 0.3s ease-out forwards;
          }
        `}</style>
        </div> 
      </div>
    // </div>
  );
};

export default CreateRoom;
```

## /client/src/pages/GameRoom.jsx

```jsx path="/client/src/pages/GameRoom.jsx" 
import React, { useEffect, useState, useRef } from "react";
import socket from "../socket";
import ColorThief from "colorthief";
import Navbar from "../components/common/Navbar";


const GameRoom = () => {
  const [song, setSong] = useState(null);
  const [guess, setGuess] = useState("");
  const [chat, setChat] = useState([]);
  const [round, setRound] = useState(1);
  const [timer, setTimer] = useState(0);
  const [gameOver, setGameOver] = useState(false);
  const [leaderboard, setLeaderboard] = useState([]);
  const [hintRevealed, setHintRevealed] = useState({
    movie: false,
    composer: false,
    cover: false,
    aiHint: "",
  });
  const [players, setPlayers] = useState([]);
  const [voteCounts, setVoteCounts] = useState({
    movie: 0,
    composer: 0,
    ai: 0,
  });
  const [loadingNext, setLoadingNext] = useState(false);
  const [correctGuess, setCorrectGuess] = useState(false);
  const [roundEnded, setRoundEnded] = useState(false);
  const [countdown, setCountdown] = useState(5);
  const [hintsUsed, setHintsUsed] = useState({
    movie: false,
    composer: false,
    ai: false,
  });
  const [currentSongDetails, setCurrentSongDetails] = useState(null);
  const [showPlayButton, setShowPlayButton] = useState(false);
  const [isFirstRound, setIsFirstRound] = useState(true);

  const audioRef = useRef(null);
  const blobCanvasRef = useRef(null);
  const blobsRef = useRef([]);
  const animationRef = useRef(null);
  const targetColorsRef = useRef([
    [255, 251, 0],
    [78, 78, 78],
    [40, 40, 40],
    [255, 200, 0],
  ]);
  const currentColorsRef = useRef([
    [255, 251, 0],
    [78, 78, 78],
    [40, 40, 40],
    [255, 200, 0],
  ]);
  const chatEndRef = useRef(null);
  const gameStartedRef = useRef(false);

  const user = JSON.parse(localStorage.getItem("user"));
  const room = JSON.parse(localStorage.getItem("room"));
  const roomCode = room?.code;

  class Blob {
    constructor(x, y, radius, colorIndex, speed, drift) {
      this.x = x;
      this.y = y;
      this.radius = radius;
      this.colorIndex = colorIndex;
      this.speed = speed;
      this.angle = Math.random() * Math.PI * 2;
      this.drift = drift;
      this.alpha = 0.7 + Math.random() * 0.3;
    }

    update(canvas) {
      this.angle += this.drift;
      this.x += Math.cos(this.angle) * this.speed;
      this.y += Math.sin(this.angle) * this.speed;
      if (this.x < -this.radius) this.x = canvas.width + this.radius;
      if (this.x > canvas.width + this.radius) this.x = -this.radius;
      if (this.y < -this.radius) this.y = canvas.height + this.radius;
      if (this.y > canvas.height + this.radius) this.y = -this.radius;
    }

    draw(ctx) {
      const [r, g, b] =
        currentColorsRef.current[
          this.colorIndex % currentColorsRef.current.length
        ];
      ctx.beginPath();
      ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${this.alpha})`;
      ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  const initBlobCanvas = () => {
    const canvas = blobCanvasRef.current;
    if (!canvas) return () => {};

    const ctx = canvas.getContext("2d");

    const resizeCanvas = () => {
      if (!canvas || !canvas.parentElement) return;
      canvas.width = canvas.parentElement.offsetWidth;
      canvas.height = canvas.parentElement.offsetHeight;
    };
    resizeCanvas();

    const generateBlobs = () => {
      if (blobsRef.current.length > 0) return;

      const blobCount = 65 + Math.floor(Math.random() * 16);

      for (let i = 0; i < blobCount; i++) {
        blobsRef.current.push(
          new Blob(
            Math.random() * canvas.width,
            Math.random() * canvas.height,
            20 + Math.random() * 100,
            i % targetColorsRef.current.length,
            0.3 + Math.random() * 0.7,
            (Math.random() - 0.5) * 0.02
          )
        );
      }
    };

    const updateColors = () => {
      if (!currentColorsRef.current || !targetColorsRef.current) return;
      
      for (let i = 0; i < currentColorsRef.current.length; i++) {
        if (!currentColorsRef.current[i] || !targetColorsRef.current[i]) continue;
        
        for (let j = 0; j < 3; j++) {
          currentColorsRef.current[i][j] +=
            (targetColorsRef.current[i][j] - currentColorsRef.current[i][j]) *
            0.1;
          if (
            Math.abs(
              currentColorsRef.current[i][j] - targetColorsRef.current[i][j]
            ) < 1
          ) {
            currentColorsRef.current[i][j] = targetColorsRef.current[i][j];
          }
        }
      }
    };

    const animate = () => {
      if (!canvas) return;

      ctx.clearRect(0, 0, canvas.width, canvas.height);
      updateColors();
      blobsRef.current.forEach((blob) => {
        blob.update(canvas);
        blob.draw(ctx);
      });
      animationRef.current = requestAnimationFrame(animate);
    };

    const handleResize = () => {
      resizeCanvas();
      blobsRef.current.forEach((blob) => {
        blob.x = Math.max(
          blob.radius,
          Math.min(canvas.width - blob.radius, blob.x)
        );
        blob.y = Math.max(
          blob.radius,
          Math.min(canvas.height - blob.radius, blob.y)
        );
      });
    };

    generateBlobs();
    animate();
    window.addEventListener("resize", handleResize);

    return () => {
      window.removeEventListener("resize", handleResize);
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    };
  };

  const extractAlbumColors = (imageUrl) => {
    const img = new Image();
    img.crossOrigin = "Anonymous";
    img.src = imageUrl;

    img.onload = () => {
      try {
        const colorThief = new ColorThief();
        const palette = colorThief.getPalette(img, 4);
        targetColorsRef.current = palette;
      } catch (err) {
        console.error("Color extraction failed:", err);
      }
    };

    img.onerror = () => {
      console.error("Failed to load album image");
    };
  };

  const scrollChatToBottom = () => {
    chatEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  useEffect(() => {
    scrollChatToBottom();
  }, [chat]);

  const handlePlayFirstSong = () => {
    if (audioRef.current && song) {
      audioRef.current.src = `https://www.youtube.com/embed/${song.videoId}?autoplay=1&start=0&end=${timer}`;
      setShowPlayButton(false);
      setIsFirstRound(false);
    }
  };

  useEffect(() => {
    const cleanupBlobCanvas = initBlobCanvas();

    socket.on("start-round", ({ song, round, duration, players }) => {
      setSong(song);
      setCurrentSongDetails(song);
      setRound(round);
      setTimer(duration);
      setChat([]);
      setHintRevealed({
        movie: false,
        composer: false,
        cover: false,
        aiHint: "",
      });
      setVoteCounts({ movie: 0, composer: 0, ai: 0 });
      setPlayers(players || []);
      setLoadingNext(false);
      setCorrectGuess(false);
      setRoundEnded(false);
      setCountdown(3);

      if (isFirstRound) {
        setShowPlayButton(true);
      }

      if (song.cover) {
        extractAlbumColors(song.cover);
      }

      if (!gameStartedRef.current) {
        gameStartedRef.current = true;
      }
    });

    socket.on("preload-next-song", ({ song, duration }) => {
      if (audioRef.current) {
        audioRef.current.src = `https://www.youtube.com/embed/${song.videoId}?autoplay=1&start=0&end=${duration+3}`;
      }
    });

    socket.on("room-updated", ({ players, hintsUsed: serverHintsUsed }) => {
      setPlayers(players);
      if (serverHintsUsed) {
        setHintsUsed(serverHintsUsed);
      }
    });

    socket.on("new-guess", ({ user, text, correct }) => {
      setChat((prev) => [...prev, { user, text, correct }]);
    });

    socket.on("correct-guess", ({ user: guesser }) => {
      const isCurrentUser = guesser.uid === user.uid;
      setChat((prev) => [
        ...prev,
        { system: true, type: "crct-guess", text: `✅ ${guesser.name} guessed it right!` },
      ]);

      if (isCurrentUser) {
        setCorrectGuess(true);
        setHintRevealed((prev) => ({
          ...prev,
          movie: true,
          composer: true,
          cover: true,
        }));
      }
    });

    socket.on("reveal-hint", ({ hintType, aiHint }) => {
      if (hintType === "movie") {
        setHintRevealed((prev) => ({
          ...prev,
          movie: true,
          cover: true,
        }));
        setHintsUsed((prev) => ({ ...prev, movie: true }));
      } else if (hintType === "composer") {
        setHintRevealed((prev) => ({
          ...prev,
          composer: true,
        }));
        setHintsUsed((prev) => ({ ...prev, composer: true }));
      } else if (hintType === "ai" && aiHint) {
        setHintRevealed((prev) => ({
          ...prev,
          aiHint,
        }));
        setHintsUsed((prev) => ({ ...prev, ai: true }));
        setChat((prev) => [
          ...prev,
          {
            system: true,
            type: "ai-hint",
            text: (
              <span className="flex items-center gap-1">
                <img
                  src="/AI-p.png"
                  alt="AI"
                  className="w-4 h-4 inline-block"
                />
                <span>IS.AI: {aiHint}</span>
              </span>
            ),
          },
        ]);        
      }
    });

    socket.on("hint-vote-count", ({ hintType, votes }) => {
      setVoteCounts((prev) => ({ ...prev, [hintType]: votes }));
    });

    socket.on("game-over", ({ leaderboard }) => {
      setGameOver(true);
      setLeaderboard(leaderboard);
    });

    socket.on("loading-next-round", () => {
      setLoadingNext(true);
    });

    socket.on("round-ended", () => {
      setRoundEnded(true);
      setHintRevealed((prev) => ({
        ...prev,
        movie: true,
        composer: true,
        cover: true,
      }));

      let counter = 3;
      setCountdown(counter);
      const interval = setInterval(() => {
        counter -= 1;
        setCountdown(counter);
        if (counter <= 0) {
          clearInterval(interval);
        }
      }, 1000);
    });

    return () => {
      socket.off("start-round");
      socket.off("room-updated");
      socket.off("new-guess");
      socket.off("correct-guess");
      socket.off("reveal-hint");
      socket.off("hint-vote-count");
      socket.off("game-over");
      socket.off("loading-next-round");
      socket.off("round-ended");
      socket.emit("leave-room", { roomCode, user });
      cleanupBlobCanvas();
    };
  }, [roomCode, user, isFirstRound]);

  useEffect(() => {
    let interval;
    if (timer > 0 && !roundEnded) {
      interval = setInterval(() => {
        setTimer((prev) => {
          if (prev <= 1) {
            clearInterval(interval);
            return 0;
          }
          return prev - 1;
        });
      }, 1000);
    }
    return () => clearInterval(interval);
  }, [timer, roundEnded]);

  const handleGuessSubmit = (e) => {
    e.preventDefault();
    if (guess.trim() && !correctGuess && !roundEnded) {
      socket.emit("submit-guess", {
        roomCode,
        user,
        text: guess.trim(),
      });
      setGuess("");
    }
  };

  const handleVote = (type) => {
    if (!hintsUsed[type] && !roundEnded) {
      socket.emit("vote-hint", { roomCode, uid: user.uid, hintType: type });
    }
  };

  const LoadingOverlay = ({ text = "Starting Game..." }) => {
    const [movingBlobs, setMovingBlobs] = useState([]);
    const rafRef = useRef(null);

    useEffect(() => {
      const blobs = [];
      const numBlobs = Math.floor(Math.random() * 70) + 20;
      for (let i = 0; i < numBlobs; i++) {
        const size = Math.random() * 160 + 180;
        const speedX = (Math.random() * 2 - 1) * 6.5;
        const speedY = (Math.random() * 2 - 1) * 6.5;
        const x = Math.random() * window.innerWidth;
        const y = Math.random() * window.innerHeight;
        const opacity = Math.random() * 0.05 + 0.07;
        blobs.push({ id: i, size, x, y, speedX, speedY, opacity, color: "#FFFB00" });
      }
      setMovingBlobs(blobs);

      const animate = () => {
        setMovingBlobs((prev) =>
          prev.map((b) => {
            let x = b.x + b.speedX;
            let y = b.y + b.speedY;
            if (x < 0 || x > window.innerWidth - b.size) b.speedX *= -1, (x = b.x + b.speedX);
            if (y < 0 || y > window.innerHeight - b.size) b.speedY *= -1, (y = b.y + b.speedY);
            return { ...b, x, y };
          })
        );
        rafRef.current = requestAnimationFrame(animate);
      };
      animate();
      return () => rafRef.current && cancelAnimationFrame(rafRef.current);
    }, []);

    return (
      <div className="h-screen w-full bg-black text-white flex flex-col items-center justify-center font-silkscreen relative overflow-hidden">
        {movingBlobs.map((blob) => (
          <div
            key={blob.id}
            className="absolute rounded-full blur-3xl"
            style={{
              width: `${blob.size}px`,
              height: `${blob.size}px`,
              left: `${blob.x}px`,
              top: `${blob.y}px`,
              backgroundColor: blob.color,
              opacity: blob.opacity,
            }}
          />
        ))}
        <div className="relative z-10 flex flex-col items-center">
          <div className="w-12 h-12 border-4 border-[#FFFB00] border-t-transparent rounded-full animate-spin mb-6"></div>
          <h2 className="text-2xl font-bold text-white">{text}</h2>
        </div>
      </div>
    );
  };

  if (!song) {
    return <LoadingOverlay text="Starting Game..." />;
  }

if (gameOver) {
  return (
    <div className="h-screen bg-black text-white">
      <Navbar />

      <div className="flex flex-col items-center justify-center h-[calc(100vh-4rem)]">
        <h2 className="text-3xl font-bold mb-8">Game Over!</h2>

        <div className="w-64 bg-[#1a1a1ab8] rounded-[20px] p-6 shadow-2xl">
          <h3 className="text-xl ml-5 mb-4 text-[#FFFB00] font-silkscreen glow-yellow">
            LEADERBOARD
          </h3>
          {leaderboard.map((p, i) => (
            <div key={i} className="flex justify-between mb-2 items-center">
              <div className="flex items-center">
                <div className="w-6 h-6 bg-[#2d2d2d69] rounded-full mr-2"></div>
                <span className={p.uid === user.uid ? "text-[#FFFB00]" : ""}>
                  {p.name}
                </span>
              </div>
              <span className="font-bold text-[#FFFB00]">{p.score} pts</span>
            </div>
          ))}
        </div>

        <div className="mt-8 flex gap-10">
          <a
            href="/landing"
            className="border-2 border-[#FFFB00] text-white font-bold py-2 px-6 rounded-xl hover:scale-105 transition-transform"
          >
            Go to Home
          </a>
          <a
            href="/create-room"
            className="bg-[#FFFB00] text-black font-bold py-2 px-6 rounded-xl hover:scale-105 transition-transform"
          >
            Play Again
          </a>
        </div>
      </div>
    </div>
  );
}



  return (
    <div className="h-screen w-screen bg-black text-white pt-5.5 flex overflow-hidden font-montserrat">
      <div className="w-1/4 h-full mr-0.5 ml-2 mt-3 mb-3 bg-[#1a1a1ab8] p-6 flex flex-col rounded-[20px] shadow-2xl overflow-hidden">
        <div className="mb-8">
          <h2 className="font-silkscreen text-[#FFFB00] text-xl mb-4 glow-yellow">
            LEADERBOARD 
          </h2>
          <div className="space-y-3 overflow-y-auto max-h-[40vh] pr-2">
            {players
              .sort((a, b) => (b.score || 0) - (a.score || 0))
              .map((p, i) => (
                <div
                  key={p.uid}
                  className="flex items-center justify-between bg-[#2d2d2d58] p-3 rounded-lg"
                >
                  <div className="flex items-center">
                    <div className="w-8 h-8 bg-[#2d2d2d69] rounded-full mr-3"></div>
                    <span
                      className={
                        p.uid === user.uid ? "text-[#FFFB00]" : "text-white"
                      }
                    >
                      {p.name}
                    </span>
                  </div>
                  <span className="text-[#FFFB00] font-bold">
                    {p.score || 0}
                  </span>
                </div>
              ))}
          </div>
        </div>

        <div className="mt-auto">
          <div className="flex items-center space-x-2 pl-3 mb-2">
            <img src="/AI.png" alt="AI Icon" className="w-6 h-6" />
            <h2 className="font-silkscreen text-[#FFFB00] text-xl glow-yellow">
              IS.AI
            </h2>
          </div>

          <div className="space-y-4 bg-[#2d2d2d58] p-4 rounded-lg shadow-inner">
            {["movie", "composer", "ai"].map((type) => {
              const icons = {
                movie: "/film.png",
                composer: "/music.png",
                ai: "/AI.png",
              };
              const labels = {
                movie: "REVEAL MOVIE NAME",
                composer: "REVEAL ARTIST NAME",
                ai: "ASK AI FOR HINT",
              };

              return (
                <button
                  key={type}
                  onClick={() => handleVote(type)}
                  disabled={hintsUsed[type] || roundEnded}
                  className={`w-full flex items-center justify-between p-3 rounded-lg transition-all duration-300 ${
                    voteCounts[type] > 0
                      ? "bg-[#FFFB00] text-black shadow-glow-yellow"
                      : hintsUsed[type] || roundEnded
                      ? "bg-[#3d3d3d90] cursor-not-allowed opacity-50"
                      : "bg-[#3d3d3d47] hover:shadow-[0_0_10px_2px_#FFFB00]"
                  }`}
                >
                  <span className="flex items-center space-x-3">
                    <img
                      src={icons[type]}
                      alt={`${type} icon`}
                      className="w-5 h-5 object-contain"
                    />
                    <span>{labels[type]}</span>
                  </span>
                  {voteCounts[type] > 0 && (
                    <span className="text-xs px-2 py-0.5 bg-[#ffffff] rounded-md text-black font-bold">
                      {voteCounts[type]}
                    </span>
                  )}
                </button>
              );
            })}
          </div>
        </div>
      </div>

      <div className="w-2/4 h-full mr-2.5 ml-2.5 mt-3 mb-3 flex flex-col rounded-[20px] items-center justify-center relative overflow-hidden">
        <canvas
          ref={blobCanvasRef}
          className="absolute top-0 left-0 w-full h-full z-0 opacity-70 mix-blend-screen"
          style={{ filter: "blur(90px)" }}
        />

        <div className="relative z-10 w-full flex flex-col items-center">
          {showPlayButton && (
<div className="absolute inset-0 flex items-center justify-center z-20">
  <button
    onClick={handlePlayFirstSong}
    className="mt-[40px] bg-[#FFFB00] hover:bg-[#FFFB00CC] text-black px-4 py-2 rounded-md text-[10px] font-bold shadow-[0_0_10px_3px_rgba(255,251,0,0.4)] transition-all"
  >
    PLAY SONG
  </button>
</div>
)}

          <div className="bg-[#000000AA]/45 px-6 py-2 rounded-md mb-6">
            <h2 className="text-[#FFFB00] text-xl font-bold">
              {roundEnded
                ? `Round Over - Next in ${countdown}s`
                : `Round ${round} - ${timer}s left`}
            </h2>

          </div>
<div
  className={`bg-[#2D2D2D]/45 p-4 rounded-lg mb-5 transition-all duration-500 text-center ${
    roundEnded ? "" : "filter blur-md"
  }`}
>
  <div className="flex justify-center items-center space-x-2 text-[#FFFB00]">
    <img src="/film.png" alt="Movie" className="w-4 h-4" />
    <span>
      {roundEnded ? `${song.song}` : "Hidden"}
      {/* {roundEnded && (
        <span className="text-white italic">{song.movie}</span>
      )} */}
    </span>
  </div>
</div>

          <div className="relative mb-8 group">
            <div
              className={`absolute inset-0 transition-all rounded-md duration-500 ${
                hintRevealed.cover ? "opacity-0" : "opacity-100 blur-xl"
              }`}
            >
              <img
                src={song?.cover}
                className="w-[180px] h-[180px] object-cover grayscale"
                alt="Blurred cover"
              />
            </div>
            <img
              src={song?.cover}
              alt="Album cover"
              crossOrigin="anonymous"
              className={`w-[180px] h-[180px] object-cover shadow-2xl rounded-md transition-all duration-500 ${
                hintRevealed.cover ? "grayscale-0 blur-0" : "grayscale blur-md"
              }`}
            />
            <div className="absolute inset-0 border-2 border-white rounded-md opacity-60 pointer-events-none" />
          </div>

          <div className="w-full max-w-md space-y-3">
            <div
              className={`bg-[#2D2D2D]/45 p-4 rounded-lg transition-all duration-500 ${
                hintRevealed.movie ? "" : "filter blur-md"
              }`}
            >
              <div className="flex items-center space-x-2 text-[#FFFB00]">
                <img src="/film.png" alt="Movie" className="w-4 h-4" />
<span>
  {hintRevealed.movie
    ? song.movie
        .replace(/\s*[\--]?\s*\(.*?(original motion picture soundtrack|ost|from.*?)\)/gi, "") // remove entire (Original Motion Picture Soundtrack)
        .replace(/\s*[\--]?\s*(original motion picture soundtrack|ost|from.*)/gi, "") // fallback for non-parentheses versions
        .replace(/\s*\)+$/, "") // remove leftover closing parenthesis
        .trim()
    : "Hidden"}
</span>


              </div>
            </div>

            <div
              className={`bg-[#2D2D2D]/45 p-4 rounded-lg transition-all duration-500 ${
                hintRevealed.composer ? "" : "filter blur-md"
              }`}
            >
              <div className="flex items-center space-x-2 text-white">
                <img src="/music.png" alt="Composer" className="w-4 h-4" />
                <span>{hintRevealed.composer ? song?.composer : "Hidden"}</span>
              </div>
            </div>
            {hintRevealed.aiHint && (
              <div className="bg-purple-900/50 p-4 rounded-lg border border-purple-400/30">
                <div className="flex items-center space-x-2 text-purple-300 italic text-sm">
                  <img src="/AI-p.png" alt="AI Hint" className="w-4 h-4" />
                  <span>{hintRevealed.aiHint}</span>
                </div>
              </div>
            )}
          </div>
        </div>
      </div>

      <div className="w-1/4 h-full mr-2 ml-0.5 mt-3 mb-3 bg-[#1a1a1ab8] p-6 flex flex-col rounded-[20px] shadow-2xl overflow-hidden">
        <h2 className="font-silkscreen text-[#FFFB00] text-xl mb-4 border-b border-[#FFFB0030] pb-2 glow-yellow">
          GUESS — BOX
        </h2>

        <div className="flex-1 overflow-y-auto pr-2 space-y-3 mb-4">
          {chat.map((message, i) => (
            <div
              key={i}
              className={`p-3 rounded-lg ${
                message.system
                ? message.type === "crct-guess"
                  ? "text-green-400 border border-green-400 bg-green-400/10 shadow-[0_0_10px_2px_rgba(34,197,94,0.5)]"             
                    : message.type === "ai-hint"
                    ? "text-purple-300 italic bg-[#2D2D2D]"
                    : "text-gray-400 italic"
                  : message.correct
                  ? "text-green-400 bg-[#2D2D2D]"
                  : "bg-[#2D2D2D]"
              }`}
            >
              {message.system
                ? message.text
                : `${message.user?.name}: ${message.text}`}
            </div>
          ))}
          <div ref={chatEndRef} />
        </div>

        <form onSubmit={handleGuessSubmit} className="mt-auto">
          <input
            type="text"
            value={guess}
            onChange={(e) => setGuess(e.target.value)}
            placeholder={correctGuess ? "✅ Correct!" : "Guess the song..."}
            disabled={correctGuess || roundEnded}
            className="w-full bg-[#2d2d2d58] text-white px-4 py-3 rounded-xl border-2 border-[#FFFB00] mb-3 focus:outline-none focus:ring-2 focus:ring-[#FFFB00]"
          />
          <button
            type="submit"
            disabled={correctGuess || roundEnded}
            className={`w-full bg-[#FFFB00] text-black font-bold py-3 rounded-xl transition-all ${
              correctGuess || roundEnded
                ? "opacity-50 cursor-not-allowed"
                : "hover:bg-[#FFFB00CC] shadow-glow-yellow"
            }`}
          >
            {correctGuess ? "GUESSED!" : "SUBMIT"}
          </button>
        </form>
      </div>

      <iframe
        ref={audioRef}
        width="0"
        height="0"
        allow="autoplay"
        title="YouTube Audio"
        className="hidden"
      ></iframe>
    </div>
  );
};

export default GameRoom;
```

## /client/src/pages/HomePage.jsx

```jsx path="/client/src/pages/HomePage.jsx" 
import React, { useEffect, useRef, useState } from "react";
import { useNavigate, Navigate } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faRightToBracket,
  faEarListen,
  faBolt,
  faTrophy,
} from "@fortawesome/free-solid-svg-icons";

const HomePage = () => {
  const navigate = useNavigate();
  const blobRef = useRef(null);
  const [mousePosition, setMousePosition] = useState({
    x: window.innerWidth / 2,
    y: window.innerHeight / 2,
  });
  const [letters, setLetters] = useState(Array(8).fill(""));
  const targetWord = "GUESSYNC";
  const [movingBlobs, setMovingBlobs] = useState([]);
  const containerRef = useRef(null);

  const getRandomChar = () => {
    const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
    return chars.charAt(Math.floor(Math.random() * chars.length));
  };

  useEffect(() => {
    const spinDuration = 2000;
    const letterDelay = 200;
    const framesPerLetter = 15;
    let timeoutIds = [];

    targetWord.split("").forEach((targetLetter, index) => {
      timeoutIds.push(
        setTimeout(() => {
          for (let i = 0; i < framesPerLetter; i++) {
            timeoutIds.push(
              setTimeout(() => {
                setLetters((prev) => {
                  const newLetters = [...prev];
                  newLetters[index] = getRandomChar();
                  return newLetters;
                });
              }, i * (spinDuration / framesPerLetter))
            );
          }

          timeoutIds.push(
            setTimeout(() => {
              setLetters((prev) => {
                const newLetters = [...prev];
                newLetters[index] = targetLetter;
                return newLetters;
              });
            }, spinDuration)
          );
        }, index * letterDelay)
      );
    });

    return () => timeoutIds.forEach((id) => clearTimeout(id));
  }, []);

  useEffect(() => {
    const generateMovingBlobs = () => {
      const newBlobs = [];
      const colors = ["#FFFB00"];
      const numBlobs = Math.floor(Math.random() * 40) + 20;

      for (let i = 0; i < numBlobs; i++) {
        const size = Math.random() * 60 + 80;
        const speedX = (Math.random() * 2 - 1) * 6.5;
        const speedY = (Math.random() * 2 - 1) * 6.5;
        const initialX = Math.random() * (window.innerWidth - size);
        const initialY = Math.random() * (window.innerHeight - size);
        const opacity = Math.random() * 0.05 + 0.07;

        newBlobs.push({
          id: i,
          size,
          x: initialX,
          y: initialY,
          color: colors[0],
          opacity,
          speedX,
          speedY,
        });
      }
      setMovingBlobs(newBlobs);
    };

    generateMovingBlobs();

    const animateBlobs = () => {
      setMovingBlobs((prevBlobs) =>
        prevBlobs.map((blob) => {
          let newX = blob.x + blob.speedX;
          let newY = blob.y + blob.speedY;

          if (newX < 0 || newX > window.innerWidth - blob.size) {
            blob.speedX *= -1;
            newX = blob.x + blob.speedX;
          }
          if (newY < 0 || newY > window.innerHeight - blob.size) {
            blob.speedY *= -1;
            newY = blob.y + blob.speedY;
          }

          return { ...blob, x: newX, y: newY };
        })
      );
      requestAnimationFrame(animateBlobs);
    };

    animateBlobs();
  }, []);

  useEffect(() => {
    const handleMouseMove = (e) => {
      setMousePosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener("mousemove", handleMouseMove);
    return () => window.removeEventListener("mousemove", handleMouseMove);
  }, []);

  const blobStyle = {
    top: `${mousePosition.y - 150}px`,
    left: `${mousePosition.x - 150}px`,
    transform: "translate(-50%, -50%) scale(1)",
    filter: "blur(120px)",
    opacity: 0.26,
    zIndex: 0,
    position: "absolute",
    width: "400px",
    height: "400px",
    borderRadius: "50%",
    backgroundColor: "#FFFB00",
    pointerEvents: "none",
    animation: "morph 8s infinite alternate ease-in-out",
  };

  // Smooth redirect: if session token exists, go straight to /landing
  const token =
    typeof window !== "undefined" ? localStorage.getItem("token") : null;
  if (token) {
    return <Navigate to="/landing" replace />;
  }

  return (
    <div
      className="relative w-full h-screen overflow-hidden bg-black text-white flex flex-col justify-center items-center px-4 sm:px-6 md:px-10"
      ref={containerRef}
    >
      <style>
        {`
          @keyframes morph {
            0%, 100% {
              border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
            }
            30% {
              border-radius: 30% 70% 40% 60% / 50% 60% 30% 40%;
            }
            60% {
              border-radius: 70% 30% 60% 40% / 30% 70% 40% 60%;
            }
            80% {
              border-radius: 40% 60% 70% 30% / 40% 30% 50% 70%;
            }
          }
        `}
      </style>

      {/* Mouse-follow blob */}
      <div ref={blobRef} style={blobStyle} className="absolute" />

      {/* Background floating blobs */}
      {movingBlobs.map((blob) => (
        <div
          key={blob.id}
          className="absolute rounded-full blur-3xl"
          style={{
            width: `${blob.size}px`,
            height: `${blob.size}px`,
            left: `${blob.x}px`,
            top: `${blob.y}px`,
            backgroundColor: blob.color,
            opacity: blob.opacity,
          }}
        />
      ))}

      {/* Main Content */}
      <div className="z-10 text-center space-y-6 w-full">
        {/* Logo + Title */}
        <div className="flex flex-col sm:flex-row items-center justify-center gap-2 sm:gap-3">
          <img
            src="/logo.png"
            alt="logo"
            className="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 mt-4 sm:mt-6"
          />
          <h1 className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl font-silkscreen text-white">
            {letters.map((letter, index) => (
              <span
                key={index}
                className={`transition-all duration-100 ${
                  index >= 4
                    ? "text-[#FFFB00] drop-shadow-[0_0_5px_#FFFB00]"
                    : ""
                }`}
              >
                {letter}
              </span>
            ))}
          </h1>
        </div>

        {/* Tagline */}
        <p className="text-base sm:text-lg md:text-xl text-white max-w-md sm:max-w-lg md:max-w-2xl mx-auto leading-relaxed px-2">
          Fast-paced multiplayer music guessing game.
          <br className="hidden sm:block" />
          Play with friends, guess songs in seconds, and race to the top of the leaderboard.
        </p>
        <div className="flex flex-wrap justify-center items-center gap-3 mt-4">
          <div className="skill-box hover-trigger">
            <span className="skill-name">Listen</span>
            <FontAwesomeIcon icon={faEarListen} className="skill-icon" />
          </div>
          <div className="skill-box hover-trigger">
            <span className="skill-name">Guess</span>
            <FontAwesomeIcon icon={faBolt} className="skill-icon" />
          </div>
          <div className="skill-box hover-trigger">
            <span className="skill-name">Win</span>
            <FontAwesomeIcon icon={faTrophy} className="skill-icon" />
          </div>
        </div>

        <style>
          {`
    /* Default skill-box styles (desktop hover works as before) */
    .skill-box {
      background-color: rgba(255, 255, 255, 0.09);
      border: 1px solid rgba(255, 255, 255, 0.1);
      border-radius: 8px;
      padding: 8px 16px;
      font-size: 1rem;
      font-weight: 500;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      margin: 8px;
      transition: all 0.3s ease;
      position: relative;
      overflow: hidden;
      height: 48px;
      width: 120px;
    }
    .skill-name {
      transition: all 0.3s ease;
      z-index: 1;
    }
    .skill-icon {
      position: absolute;
      font-size: 1.2rem;
      color: black;
      opacity: 0;
      transition: all 0.3s ease;
      z-index: 2;
    }
    .skill-box:hover {
      background-color: #FFFB00;
      border-color: #FFFB00;
      box-shadow: 0 0 10px #FFFB00, 0 0 20px #FFFB00;
      transform: translateY(-2px);
      color: black;
    }
    .skill-box:hover .skill-name {
      opacity: 0;
      transform: translateY(10px);
    }
    .skill-box:hover .skill-icon {
      opacity: 1;
    }

    /* 📱 Mobile: force hover state always */
    @media (max-width: 768px) {
      .hover-trigger {
        background-color: #FFFB00 !important;
        border-color: #FFFB00 !important;
        box-shadow: 0 0 10px #FFFB00, 0 0 20px #FFFB00 !important;
        transform: translateY(-2px);
        color: black !important;
      }
      .hover-trigger .skill-name {
        opacity: 0 !important;
        transform: translateY(10px) !important;
      }
      .hover-trigger .skill-icon {
        opacity: 1 !important;
      }
    }
  `}
        </style>

        {/* Login Button */}
        <div className="flex justify-center items-center w-full mt-8 sm:mt-10">
          <button
            onClick={() => navigate("/login")}
            className="glow-btn flex items-center gap-4 sm:gap-6 bg-[#FFFB00] text-black px-6 sm:px-8 md:px-10 py-2 sm:py-3 rounded-lg font-silkscreen text-lg sm:text-xl md:text-2xl"
          >
            <FontAwesomeIcon icon={faRightToBracket} size="lg" />
            Login
          </button>
        </div>
      </div>
    </div>
  );
};

export default HomePage;
```

## /client/src/pages/HowToPlay.jsx

```jsx path="/client/src/pages/HowToPlay.jsx" 
// Added by Reshma - How To Play Page
import Navbar from "../components/common/Navbar";
import { useEffect, useState } from "react";

const HowToPlay = () => {
  const [visibleSteps, setVisibleSteps] = useState([]);

  useEffect(() => {
    // Staggered animation for steps
    const timeouts = [];
    [0, 1, 2].forEach((index) => {
      timeouts.push(
        setTimeout(() => {
          setVisibleSteps(prev => [...prev, index]);
        }, index * 300)
      );
    });

    return () => timeouts.forEach(clearTimeout);
  }, []);

  return (
    <div className="min-h-screen bg-black text-white overflow-x-hidden flex flex-col">
      <style>
        {`
          @keyframes fadeInUp {
            from { opacity: 0; transform: translateY(40px); }
            to { opacity: 1; transform: translateY(0); }
          }

          @keyframes float {
            0%, 100% { transform: translateY(0px); }
            50% { transform: translateY(-3px); }
          }

          .step-container {
            opacity: 0;
            animation: fadeInUp 0.8s ease-out forwards;
          }

          .step-number {
            transition: all 0.3s ease;
          }

          .step-icon {
            transition: all 0.3s ease;
            animation: float 4s ease-in-out infinite;
          }

          .connector-line {
            transition: all 0.3s ease;
          }

          .step-content {
            transition: all 0.3s ease;
          }

          .main-heading {
            animation: fadeInUp 1s ease-out;
          }

          @media (max-width: 640px) {
            .step-icon {
              animation: float 3s ease-in-out infinite;
            }
          }
        `}
      </style>

      <Navbar />

      {/* Wrapper makes content centered on large screens */}
      <div className="flex-1 flex items-start lg:items-center justify-center">
        <section className="pt-10 lg:pt-0 px-4 sm:px-6 lg:px-12 pb-12 text-center w-full max-w-[1200px] mx-auto">
          {/* Heading */}
          <h1 className="main-heading text-3xl sm:text-4xl lg:text-5xl font-black font-montserrat">
            How to Play
            <span className="text-[#FFFB00] drop-shadow-[0_0_15px_#FFFB00]"> ?</span>
          </h1>

          {/* Steps Wrapper */}
          <div className="mt-12 flex flex-col gap-16 sm:gap-20 lg:gap-24 items-center w-full">
            
            {/* Step 1 */}
            <div className={`step-container flex flex-col sm:flex-row items-center justify-center gap-6 sm:gap-10 lg:gap-14 w-full ${visibleSteps.includes(0) ? 'opacity-100' : 'opacity-0'}`}>
              <div className="step-number w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 bg-[#FFFB00] shadow-[0_0_25px_#FFFB00] flex items-center justify-center text-black text-3xl sm:text-4xl lg:text-5xl font-black">
                1
              </div>
              <img
                src="/howto/node.svg"
                className="connector-line w-12 sm:w-16 lg:w-20 h-12 sm:h-16 lg:h-20 rotate-90 sm:rotate-0"
              />
              <div className="step-icon relative w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 flex items-center justify-center">
                <div className="absolute w-full h-full bg-white shadow-[0_0_30px_white] transition-all duration-300" />
                <img
                  src="/howto/door.svg"
                  className="relative z-10 w-8 sm:w-10 lg:w-12 h-8 sm:h-10 lg:h-12 transition-transform duration-300"
                />
              </div>
              <img
                src="/howto/node.svg"
                className="connector-line w-12 sm:w-16 lg:w-20 h-12 sm:h-16 lg:h-20 rotate-90 sm:rotate-0"
              />
              <div className="step-content text-center sm:text-left max-w-md">
                <h2 className="text-[#FFFB00] text-xl sm:text-2xl lg:text-3xl font-black drop-shadow-[0_0_20px_#FFFB00] mb-2 transition-all duration-300">
                  Join or Create a Room
                </h2>
                <p className="text-sm sm:text-base lg:text-lg font-medium leading-relaxed transition-all duration-300">
                  Enter a unique room code to join an existing match.
                  <br />
                  Generate a new room and share the code with friends.
                </p>
              </div>
            </div>

            {/* Step 2 */}
            <div className={`step-container flex flex-col sm:flex-row-reverse items-center justify-center gap-6 sm:gap-10 lg:gap-14 w-full ${visibleSteps.includes(1) ? 'opacity-100' : 'opacity-0'}`}>
              <div className="step-number w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 bg-[#FFFB00] shadow-[0_0_25px_#FFFB00] flex items-center justify-center text-black text-3xl sm:text-4xl lg:text-5xl font-black">
                2
              </div>
              <img
                src="/howto/node.svg"
                className="connector-line w-12 sm:w-16 lg:w-20 h-12 sm:h-16 lg:h-20 rotate-90 sm:rotate-0"
              />
              <div className="step-icon relative w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 flex items-center justify-center">
                <div className="absolute w-full h-full bg-white shadow-[0_0_30px_white] transition-all duration-300" />
                <img
                  src="/howto/majesticons_music-line.svg"
                  className="relative z-10 w-8 sm:w-10 lg:w-12 h-8 sm:h-10 lg:h-12 transition-transform duration-300"
                />
              </div>
              <img
                src="/howto/node.svg"
                className="connector-line w-12 sm:w-16 lg:w-20 h-12 sm:h-16 lg:h-20 rotate-90 sm:rotate-0"
              />
              <div className="step-content text-center sm:text-right max-w-lg">
                <h2 className="text-[#FFFB00] text-xl sm:text-2xl lg:text-3xl font-black drop-shadow-[0_0_20px_#FFFB00] mb-2 transition-all duration-300">
                  Listen & Guess the Song
                </h2>
                <p className="text-sm sm:text-base lg:text-lg font-medium leading-relaxed transition-all duration-300">
                  30 or 60 second snippet of song plays with a countdown timer.
                  <br />
                  Submit your answer before time runs out.
                </p>
              </div>
            </div>

            {/* Step 3 */}
            <div className={`step-container flex flex-col sm:flex-row items-center justify-center gap-6 sm:gap-10 lg:gap-14 w-full ${visibleSteps.includes(2) ? 'opacity-100' : 'opacity-0'}`}>
              <div className="step-number w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 bg-[#FFFB00] shadow-[0_0_25px_#FFFB00] flex items-center justify-center text-black text-3xl sm:text-4xl lg:text-5xl font-black">
                3
              </div>
              <img
                src="/howto/node.svg"
                className="connector-line w-12 sm:w-16 lg:w-20 h-12 sm:h-16 lg:h-20 rotate-90 sm:rotate-0"
              />
              <div className="step-icon relative w-16 h-16 sm:w-20 sm:h-20 lg:w-24 lg:h-24 flex items-center justify-center">
                <div className="absolute w-full h-full bg-white shadow-[0_0_30px_white] transition-all duration-300" />
                <img
                  src="/howto/question.svg"
                  className="relative z-10 w-8 sm:w-10 lg:w-12 h-8 sm:h-10 lg:h-12 transition-transform duration-300"
                />
              </div>
              <img
                src="/howto/node.svg"
                className="connector-line w-12 sm:w-16 lg:w-20 h-12 sm:h-16 lg:h-20 rotate-90 sm:rotate-0"
              />
              <div className="step-content text-center sm:text-left max-w-lg">
                <h2 className="text-[#FFFB00] text-xl sm:text-2xl lg:text-3xl font-black drop-shadow-[0_0_20px_#FFFB00] mb-2 transition-all duration-300">
                  See Hints & Earn Points
                </h2>
                <p className="text-sm sm:text-base lg:text-lg font-medium leading-relaxed transition-all duration-300">
                  Faster guesses within 30/60 seconds earn more points.
                  <br />
                  If most players agree, a hint (movie/artist) is revealed.
                </p>
              </div>
            </div>

          </div>
        </section>
      </div>
    </div>
  );
};

export default HowToPlay;
```

## /client/src/pages/JoinRoom.jsx

```jsx path="/client/src/pages/JoinRoom.jsx" 
import React, { useState, useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import Navbar from "../components/common/Navbar";
import socket from "../socket";
import { auth } from "../config/firebase"; // <-- add

export default function JoinRoom() {
  const [code, setCode] = useState("");
  const [error, setError] = useState("");
  const [activeButton, setActiveButton] = useState(null);
  const navigate = useNavigate();
  const buttonRefs = useRef({});

  // Tooltip + paste support
  const [showPasteTip, setShowPasteTip] = useState(false);
  const [pasteTip, setPasteTip] = useState({ text: "Click to paste code", type: "info" });
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
  const tipTimeoutRef = useRef(null);

  // New: paste animation support
  const pasteTimersRef = useRef([]);
  const [pasteAnimating, setPasteAnimating] = useState(false);
  const clearPasteTimers = () => {
    pasteTimersRef.current.forEach((t) => clearTimeout(t));
    pasteTimersRef.current = [];
  };

  const clearTipTimeout = () => {
    if (tipTimeoutRef.current) clearTimeout(tipTimeoutRef.current);
    tipTimeoutRef.current = null;
  };
  const showTip = (text, type = "info", persistMs = 0) => {
    clearTipTimeout();
    setPasteTip({ text, type });
    setShowPasteTip(true);
    if (persistMs > 0) {
      tipTimeoutRef.current = setTimeout(() => {
        setPasteTip({ text: "Click to paste code", type: "info" });
        setShowPasteTip(false);
      }, persistMs);
    }
  };

  // Animate filling digits one-by-one
  const animatePaste = (target) => {
    clearPasteTimers();
    setPasteAnimating(true);
    setCode("");
    setError("");
    showTip("Code pasted", "info", 900);

    target.split("").forEach((ch, idx) => {
      const t = setTimeout(() => {
        setCode((prev) => (prev + ch).slice(0, 6));
        if (idx === target.length - 1) {
          const endT = setTimeout(() => setPasteAnimating(false), 150);
          pasteTimersRef.current.push(endT);
        }
      }, idx * 100); // 100ms per digit
      pasteTimersRef.current.push(t);
    });
  };

  const tryExtractAndAnimate = (txt) => {
    const match = (txt || "").match(/\d{6}/);
    if (match) {
      animatePaste(match[0]);
    } else {
      showTip("Paste 6-digit code", "error", 1400);
    }
  };

  const handleDisplayClick = async () => {
    try {
      const txt = await navigator.clipboard.readText();
      tryExtractAndAnimate(txt);
    } catch {
      showTip("Allow clipboard permission", "error", 1600);
    }
  };

  // Handle Ctrl/Cmd+V directly over the display
  const handleDisplayPaste = (e) => {
    e.preventDefault();
    const txt = e.clipboardData?.getData("text") || "";
    tryExtractAndAnimate(txt);
  };

  const handleDisplayMouseEnter = () => showTip("Click to paste code", "info", 0);
  const handleDisplayMouseLeave = () => {
    if (pasteTip.type !== "error") setShowPasteTip(false);
  };
  const handleDisplayMouseMove = (e) => setMousePos({ x: e.clientX, y: e.clientY });

  const handleDigit = (digit) => {
    if (code.length < 6) {
      setCode((prev) => prev + digit);
      setError("");
    }
    setActiveButton(digit);
    setTimeout(() => setActiveButton(null), 150);
  };

  const handleBackspace = () => {
    setCode((prev) => prev.slice(0, -1));
    setError("");
    setActiveButton("backspace");
    setTimeout(() => setActiveButton(null), 150);
  };

  const handleJoin = async () => {
    if (code.length !== 6) {
      setError("Please enter a 6-digit code");
      return;
    }

    setActiveButton("join");
    setTimeout(() => setActiveButton(null), 150);

    const currentUser = auth.currentUser;
    if (!currentUser) {
      setError("Session expired. Please log in again.");
      navigate("/login");
      return;
    }

    const getToken = async (force = false) => {
      const t = await currentUser.getIdToken(force);
      localStorage.setItem("token", t);
      return t;
    };

    const token = await getToken();
    const storedUser = JSON.parse(localStorage.getItem("user")) || {
      name: currentUser.displayName || "Player",
      uid: currentUser.uid,
      avatar: localStorage.getItem("userAvatar") || currentUser.photoURL || "/avatars/1.png",
    };

    try {
      const doFetch = async (tok) =>
        fetch("https://guessync.onrender.com/api/room/join", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${tok}`,
          },
          body: JSON.stringify({
            code,
            uid: storedUser.uid,
            name: storedUser.name,
            avatar: storedUser.avatar,
          }),
        });

      let res = await doFetch(token);
      if (res.status === 401 || res.status === 403) {
        const refreshed = await getToken(true);
        res = await doFetch(refreshed);
      }

      const data = await res.json();

      if (res.ok) {
        localStorage.setItem("room", JSON.stringify(data.room));

        socket.auth = { uid: storedUser.uid };
        if (socket.disconnected) socket.connect();

        socket.emit("join-room", { roomCode: code, user: storedUser });

        socket.once("room-updated", () => {
          navigate("/waiting-room");
        });

        const timeout = setTimeout(() => {
          socket.off("room-updated");
          setError("Connection timeout. Please try again.");
        }, 5000);

        socket.once("room-updated", () => {
          clearTimeout(timeout);
        });
      } else {
        setError(data.message || "Room not found");
      }
    } catch (err) {
      console.error("Join error:", err);
      setError("Something went wrong. Try again.");
    }
  };

  useEffect(() => {
    const handleKeyDown = (e) => {
      if (e.key >= "0" && e.key <= "9") handleDigit(e.key);
      else if (e.key === "Backspace") handleBackspace();
      else if (e.key === "Enter") handleJoin();
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [code]);

  useEffect(() => {
    return () => {
      clearTipTimeout();
      clearPasteTimers();
    };
  }, []);

  const getButtonActiveClass = (buttonValue) => {
    return activeButton === buttonValue.toString()
      ? "translate-x-[3px] translate-y-[3px] shadow-none"
      : "";
  };

  return (
    <div className="bg-black min-h-screen text-white flex flex-col">
      {/* Fixed black backdrop to avoid white gaps on large screens */}
      <div className="fixed inset-0 -z-10 bg-black" aria-hidden="true" />

      {/* Floating themed tooltip following cursor */}
      {showPasteTip && (
        <div
          className={`fixed z-50 pointer-events-none px-2 py-1 rounded font-silkscreen text-xs border-2 ${
            pasteTip.type === "error"
              ? "bg-red-600 text-white border-red-300 shadow-[0_0_10px_#f87171]"
              : "bg-[#FFFB00] text-black border-black shadow-[0_0_10px_#FFFB00]"
          }`}
          style={{ left: mousePos.x + 12, top: mousePos.y - 24 }}
        >
          {pasteTip.text}
        </div>
      )}

      <Navbar />
      {/* Centered main area without extra margins; no scroll when content fits */}
      <main className="flex-1 flex items-center justify-center">
        {/* The join room card */}
        <div className="bg-[#FFFB00] px-8 py-6 rounded-lg scale-100 lg:scale-130 sm:scale-90 md:scale-120  border-4 border-black shadow-[0_0_60px_#FFFB00] -translate-y-1 md:translate-y-0">
          <div
            className={`relative bg-gray-300 text-black text-2xl px-6 py-6 mb-1 text-center border-4 border-black w-[270px] tracking-widest font-silkscreen min-h-[75px] cursor-copy transition-all duration-200 ${
              pasteAnimating ? "ring-4 ring-[#FFFB00] scale-[1.02]" : ""
            }`}
            onClick={handleDisplayClick}
            onMouseEnter={handleDisplayMouseEnter}
            onMouseLeave={handleDisplayMouseLeave}
            onMouseMove={handleDisplayMouseMove}
            onPaste={handleDisplayPaste}
            title="Click to paste room code"
            aria-label="Room code display - click to paste"
          >
            <div className="mt-3 sm:mt-6 mb-6 absolute top-1 left-1/2 transform -translate-x-1/2 text-xs text-gray-500 whitespace-nowrap">
              Enter Room Code
            </div>
            <div className="mt-3 sm:mt-6">{code.padEnd(6, "_").split("").join(" ")}</div>

            {/* Mobile-only subtle hint inside display */}
            <div className="absolute inset-x-0 bottom-1 sm:hidden pointer-events-none text-[10px] text-black/50 font-silkscreen">
              Tap to paste code
            </div>

            {error && (
              <div className="text-red-600 text-[8px] text-center mt-3">{error}</div>
            )}
          </div>

          <div className="grid grid-cols-3 gap-2 mb-2">
            {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((digit) => (
              <button
                key={digit}
                ref={(el) => (buttonRefs.current[digit] = el)}
                onClick={() => handleDigit(digit.toString())}
                className={`bg-gray-200 text-black text-xl font-silkscreen py-5 px-0 border-4 border-black shadow-[4px_4px_0_#000] hover:bg-gray-300 transition-all duration-150 ${getButtonActiveClass(
                  digit
                )} w-[80px] cursor-pointer`}
              >
                {digit}
              </button>
            ))}

            <button
              ref={(el) => (buttonRefs.current["0"] = el)}
              onClick={() => handleDigit("0")}
              className={`bg-gray-200 text-black text-xl py-5 px-0 border-4 border-black font-silkscreen shadow-[4px_4px_0_#000] hover:bg-gray-300 transition-all duration-150 ${getButtonActiveClass(
                "0"
              )} w-[80px] cursor-pointer`}
            >
              0
            </button>

            <button
              ref={(el) => (buttonRefs.current["join"] = el)}
              onClick={handleJoin}
              className={`w-[177px] col-span-2 bg-black text-white text-lg py-0 font-silkscreen border-4 border-black shadow-[4px_4px_0_#000] hover:bg-gray-200 hover:text-black transition-all duration-150 ${getButtonActiveClass(
                "join"
              )} cursor-pointer`}
            >
              JOIN 🏠
            </button>
          </div>

          <p className="mt-4 text-black text-sm text-center">
            No Code?{" "}
            <span
              onClick={() => navigate("/create-room")}
              className="font-bold cursor-pointer relative group inline-block"
            >
              Create A New Room 🏡
              <span className="absolute left-0 -bottom-1 w-0 h-[2px] bg-black transition-all duration-300 group-hover:w-full"></span>
            </span>
          </p>
        </div>
      </main>
    </div>
  );
}
```

## /client/src/pages/LandingPage.jsx

```jsx path="/client/src/pages/LandingPage.jsx" 
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import AvatarSelector from "../components/LandingPage/AvatarSelector";
import GlowingButton from "../components/common/GlowingButton";
import Navbar from "../components/common/Navbar";

export default function LandingPage() {
  const navigate = useNavigate();
  const userData = JSON.parse(localStorage.getItem("user"));

  const [name, setName] = useState(
    userData?.name || localStorage.getItem("userName") || ""
  );
  const [avatar, setAvatar] = useState(
    userData?.avatar || localStorage.getItem("userAvatar") || null
  );
  const [greeted, setGreeted] = useState(!!userData || (!!name && !!avatar));
  const [isSignedUp, setIsSignedUp] = useState(!!userData);

  useEffect(() => {
    if (name.trim()) localStorage.setItem("userName", name);
    if (avatar) localStorage.setItem("userAvatar", avatar);
  }, [name, avatar]);

  const handleEnter = () => {
    if (name.trim() && avatar) {
      const formatted =
        name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
      setName(formatted);
      setGreeted(true);
      localStorage.setItem("userName", formatted);
      localStorage.setItem("userAvatar", avatar);
    }
  };

  return (
    <div className="bg-black text-white font-montserrat h-screen w-screen overflow-hidden flex flex-col">
      <Navbar />

      {/* Scale landing body */}
      <div className="flex-1 flex items-start sm:items-center justify-center px-4 pt-3 sm:pt-0">
        <div className="flex flex-col justify-center items-center w-full scale-[1.05] sm:scale-100 md:scale-[1.25]">
          {/* Greeting */}
          <h1 className="text-3xl sm:text-4xl md:text-5xl font-black mb-6 text-center">
            Hello{greeted ? `, ${name}!` : "!"}
          </h1>

          {/* Mobile Input & Button separated */}
          <div className="flex flex-col items-center gap-3 sm:hidden mb-6">
            <div className="flex items-center bg-[#FFFB00] px-2 py-1 rounded-[8px] shadow-[0_0_20px_#FFFB00] w-[220px]">
              <input
                type="text"
                placeholder="Enter Your Name..."
                value={name}
                onChange={(e) => setName(e.target.value)}
                disabled={isSignedUp}
                className="px-2 py-2 w-full text-sm border-2 border-black rounded-[6px] text-black placeholder-black font-silkscreen bg-white outline-black disabled:opacity-50 disabled:cursor-not-allowed"
              />
            </div>
            <button
              onClick={handleEnter}
              disabled={isSignedUp}
              className="bg-[#FFFB00] w-[140px] h-9 text-black font-silkscreen border border-black text-sm px-4 py-2 rounded-[6px] hover:scale-105 transition cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
            >
              ENTER
            </button>
          </div>

          {/* Desktop Input + Button inside border */}
          <div className="hidden sm:flex flex-row items-center gap-2 bg-[#FFFB00] px-2 py-1 rounded-[8px] shadow-[0_0_25px_#FFFB00] mb-6 w-full max-w-[500px]">
            <input
              type="text"
              placeholder="Enter Your Name..."
              value={name}
              onChange={(e) => setName(e.target.value)}
              disabled={isSignedUp}
              className="px-4 py-3 w-full text-base border-2 border-black rounded-[6px] text-black placeholder-black font-silkscreen bg-white outline-black disabled:opacity-50 disabled:cursor-not-allowed"
            />
            <button
              onClick={handleEnter}
              disabled={isSignedUp}
              className="bg-[#FFFB00] w-auto h-11 text-black font-silkscreen border border-black text-sm px-6 py-[9px] rounded-[6px] hover:scale-105 transition cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
            >
              ENTER
            </button>
          </div>

          {/* Avatar Selector */}
          <div className="scale-[1.05] sm:scale-100">
            <AvatarSelector
              selectedAvatar={avatar}
              onSelect={(a) => {
                setAvatar(a);
                localStorage.setItem("userAvatar", a);
              }}
              disabled={isSignedUp}
            />
          </div>

          {/* Join/Create Buttons */}
          <div className="flex flex-col md:flex-row gap-6 md:gap-12 mt-8 w-full items-center justify-center">
            <div className="flex flex-col items-center w-full sm:w-auto">
              <p className="text-sm sm:text-base font-bold mb-2">
                Have A Room-Code?
              </p>
              <GlowingButton
                text="JOIN A ROOM"
                color="white"
                className="w-[200px] sm:w-[220px] md:w-[240px] lg:w-[280px] font-silkscreen text-sm sm:text-base md:text-lg"
                onClick={() => navigate("/join-room")}
                disabled={!name.trim() || !avatar}
              />
            </div>
            <div className="flex flex-col items-center w-full sm:w-auto">
              <p className="text-sm sm:text-base font-bold mb-2">
                Want to Host a game?
              </p>
              <GlowingButton
                text="CREATE A ROOM"
                color="yellow"
                className="w-[200px] sm:w-[220px] md:w-[240px] lg:w-[280px] font-silkscreen text-sm sm:text-base md:text-lg"
                onClick={() => navigate("/create-room")}
                disabled={!name.trim() || !avatar}
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
```

## /client/src/pages/Login.jsx

```jsx path="/client/src/pages/Login.jsx" 
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { signInWithPopup, signInWithEmailAndPassword } from "firebase/auth";
import googleIcon from "../assets/google.svg";
import { auth, provider } from "../config/firebase";
import AvatarGrid from "../components/common/AvatarGrid";
import Navbar from "../components/common/Navbar";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [selectedAvatar, setSelectedAvatar] = useState(null);
  const [errors, setErrors] = useState({});
  const navigate = useNavigate();

  const validateEmailLogin = () => {
    const newErrors = {};
    if (!email.trim()) newErrors.email = "Enter your email";
    if (!password.trim()) newErrors.password = "Enter your password";
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleEmailLogin = async () => {
    if (!validateEmailLogin()) return;

    try {
      const result = await signInWithEmailAndPassword(auth, email, password);
      const token = await result.user.getIdToken();

      localStorage.setItem("token", token);
      localStorage.setItem(
        "user",
        JSON.stringify({
          name: result.user.displayName || email,
          uid: result.user.uid,
          avatar: result.user.photoURL || "",
        })
      );

      window.location.href = "/landing";
    } catch (err) {
      console.error("❌ Email login failed:", err.message);
      let newErrors = {};

      switch (err.code) {
        case "auth/invalid-email":
          newErrors.email = "Invalid email format";
          break;
        case "auth/user-not-found":
          newErrors.email = "Email not registered";
          break;
        case "auth/wrong-password":
          newErrors.password = "Incorrect password";
          break;
        default:
          newErrors.email = "Login failed. Try again.";
          break;
      }

      setErrors(newErrors);
    }
  };

  const handleGoogleLogin = async () => {
    if (!selectedAvatar) {
      setErrors({ avatar: "Choose an avatar before continuing" });
      return;
    }

    try {
      const result = await signInWithPopup(auth, provider);
      const token = await result.user.getIdToken();
      const existingUser = JSON.parse(localStorage.getItem("user"));

      localStorage.setItem("token", token);
      localStorage.setItem(
        "user",
        JSON.stringify({
          name: result.user.displayName || "Google User",
          uid: result.user.uid,
          avatar:
            existingUser?.uid === result.user.uid
              ? existingUser.avatar
              : selectedAvatar,
        })
      );

      window.location.href = "/landing";
    } catch (err) {
      console.error("❌ Google login failed:", err.message);
    }
  };

  return (
    <div className="min-h-screen bg-black text-white flex flex-col">
      <Navbar />
      <div className="flex flex-col lg:flex-row items-center justify-center flex-1 gap-10 px-6 py-10">
        {/* Login Form */}
        <div className="bg-[#FFFB00] p-6 sm:p-8 rounded-xl shadow-xl text-black w-full sm:w-[320px] md:w-[380px] lg:w-[420px]">
          <h1 className="text-2xl sm:text-3xl font-black text-center mb-6 font-montserrat">
            Login
          </h1>

          <label className="block text-xs sm:text-sm font-silkscreen mb-1">
            ENTER YOUR EMAIL ADDRESS
          </label>
          <input
            type="email"
            className={`w-full rounded px-3 py-2 mb-1 text-black bg-white border-2 transition ${
              errors.email ? "border-red-500" : "border-transparent"
            }`}
            value={email}
            placeholder="Email ID / UserID"
            onChange={(e) => setEmail(e.target.value)}
          />
          {errors.email && (
            <p className="text-red-500 text-xs font-silkscreen mb-2">
              {errors.email}
            </p>
          )}

          <label className="block text-xs sm:text-sm font-silkscreen mb-1">
            ENTER YOUR PASSWORD
          </label>
          <input
            type="password"
            className={`w-full rounded px-3 py-2 mb-1 text-black bg-white border-2 transition ${
              errors.password ? "border-red-500" : "border-transparent"
            }`}
            value={password}
            placeholder="Password"
            onChange={(e) => setPassword(e.target.value)}
          />
          {errors.password && (
            <p className="text-red-500 text-xs font-silkscreen mb-2">
              {errors.password}
            </p>
          )}

          <button
            className="bg-black text-white w-full py-2 rounded mb-3 font-silkscreen hover:scale-105 transition"
            onClick={handleEmailLogin}
          >
            LOGIN!
          </button>

          <p className="text-sm text-center text-black font-montserrat">
            No Account?{" "}
            <span
              className="relative cursor-pointer font-semibold transition-all group hover:text-black"
              onClick={() => navigate("/signup")}
            >
              Sign Up!
              <span className="absolute left-0 -bottom-1 w-0 h-[2px] bg-black rounded-full transition-all duration-300 group-hover:w-full"></span>
            </span>
          </p>

          <div className="flex gap-2 mt-4">
            <button
              onClick={handleGoogleLogin}
              className="flex-1 flex justify-center items-center gap-3 bg-white text-black px-4 py-3 rounded text-sm sm:text-base font-silkscreen border-2 border-transparent hover:border-black transition-all duration-200"
            >
              <img src={googleIcon} alt="G" className="w-5 h-5" />
              Sign Up With Google
            </button>
          </div>
        </div>

        {/* Avatar Picker */}
        <div className="flex flex-col items-center gap-4 w-full max-w-xl">
          <div
            className={`bg-[#FFFB00]/35 text-[#FFFB00] whitespace-nowrap w-fit px-6 sm:px-10 py-2 rounded border-2 border-[#FFFB00] shadow-[0_0_12px_#FFFB00] font-silkscreen text-base sm:text-lg text-center ${
              errors.avatar
                ? "border-red-500 text-red-500 bg-red-400/25 shadow-[0_0_12px_red]"
                : ""
            }`}
          >
            PICK YOUR AVATAR
          </div>
          <AvatarGrid
            selected={selectedAvatar}
            onSelect={(avatar) => {
              setSelectedAvatar(avatar);
              setErrors((prev) => ({ ...prev, avatar: null }));
            }}
          />
          {errors.avatar && (
            <p className="text-red-500 text-xs font-silkscreen -mt-2">
              {errors.avatar}
            </p>
          )}
        </div>
      </div>
    </div>
  );
};

export default Login;
```

## /client/src/pages/Profile.jsx

```jsx path="/client/src/pages/Profile.jsx" 
// Added by Varsha - Profile Page
import React from "react";
import { useNavigate } from "react-router-dom";

const Profile = () => {
  const navigate = useNavigate();

  // Reusable tape band (now supports bottom, opacity, and z-index)
  const TapeBand = ({ top, bottom, angle = -12, speed = 16, reverse = false, opacity = 1, z = 1 }) => {
    const phrase = "UNDER CONSTRUCTION ⚠ WARNING ⚠";
    const Row = () => (
      <>
        {Array.from({ length: 14 }).map((_, i) => (
          <span key={i} className="mx-4 sm:mx-6">
            {phrase}
          </span>
        ))}
      </>
    );

    const posStyle = bottom != null ? { bottom } : { top: top ?? "20%" };

    return (
      <div
        className="tape"
        style={{
          ...posStyle,
          transform: `rotate(${angle}deg)`,
          opacity,
          zIndex: z,
        }}
      >
        <div className="relative bg-[#FFFB00] text-black uppercase font-silkscreen tracking-widest border-y-4 border-black py-2 sm:py-3">
          <div
            className="absolute inset-0 opacity-30 pointer-events-none"
            style={{
              backgroundImage:
                "repeating-linear-gradient(45deg, rgba(0,0,0,0.75) 0 12px, transparent 12px 24px)",
            }}
          />
          <div className="overflow-hidden">
            <div
              className="marquee-track"
              style={{
                animation: `${reverse ? "scrollReverse" : "scroll"} ${speed}s linear infinite`,
              }}
            >
              <div className="marquee-content">
                <Row />
              </div>
              <div className="marquee-content" aria-hidden="true">
                <Row />
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  };

  return (
    <div className="relative min-h-screen bg-black text-white font-silkscreen overflow-hidden">
      {/* faint background tapes behind the text */}
      <TapeBand top="12%" angle={-8} speed={24} opacity={0.08} z={0} />
      <TapeBand top="28%" angle={12} speed={26} reverse opacity={0.06} z={0} />
      <TapeBand top="46%" angle={-10} speed={22} opacity={0.07} z={0} />

      {/* centered text content (kept) */}
      <div className="relative z-10 flex flex-col items-center justify-center min-h-[60vh] text-center px-4 sm:px-6">
        <div className="text-4xl sm:text-5xl md:text-6xl animate-pulse text-[#FFFB00] drop-shadow-[0_0_15px_#FFFB00] mb-4 sm:mb-6">
          🚧
        </div>
        <h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold mb-3 sm:mb-4 tracking-wide sm:tracking-widest">
          UNDER <span className="text-[#FFFB00] drop-shadow-[0_0_5px_#FFFB00]">Development</span>
        </h1>
        <p className="text-sm sm:text-base md:text-lg text-gray-300 max-w-xs sm:max-w-md font-montserrat leading-relaxed">
          We're cookin' something awesome here. Your profile page will be live very soon!
        </p>
        {/* Moved CTA below the paragraph */}
        <button
          onClick={() => navigate("/landing")}
          className="mt-5 sm:mt-6 bg-[#FFFB00] text-black px-4 sm:px-6 py-2 sm:py-3 rounded-md shadow-[0_0_14px_#FFFB00] hover:scale-105 transition pointer-events-auto"
        >
          Go Back Home
        </button>
      </div>

      {/* bright, scrolling caution tapes at the bottom, behind the text */}
      <TapeBand bottom="24%" angle={-12} speed={14} z={1} />
      <TapeBand bottom="14%" angle={10} speed={18} reverse z={1} />
      <TapeBand bottom="8%" angle={-14} speed={16} z={1} />
      <TapeBand bottom="0%" angle={9} speed={20} reverse z={1} />

      {/* Local styles for marquee + glow (unchanged) */}
      <style>{`
        .tape {
          position: absolute;
          left: -25vw;
          width: 150vw;
          filter: drop-shadow(0 0 16px #FFFB00);
          will-change: transform;
          pointer-events: none; /* let buttons be clickable */
        }
        @keyframes scroll { 
          0% { transform: translateX(0); } 
          100% { transform: translateX(-50%); } 
        }
        @keyframes scrollReverse { 
          0% { transform: translateX(0); } 
          100% { transform: translateX(50%); } 
        }
        .marquee-track {
          display: flex;
          width: 200%;
          will-change: transform;
        }
        .marquee-content {
          flex: 0 0 50%;
          display: flex;
          align-items: center;
          padding: 0 1rem;
          white-space: nowrap;
        }
        .marquee-content span {
          font-weight: 700;
          letter-spacing: 0.12em;
          text-shadow: 0 0 12px rgba(255,251,0,0.85);
        }
      `}</style>
    </div>
  );
};

export default Profile;
```

## /client/src/pages/Signup.jsx

```jsx path="/client/src/pages/Signup.jsx" 
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import googleIcon from "../assets/google.svg";
import {
  createUserWithEmailAndPassword,
  updateProfile,
  signInWithPopup,
} from "firebase/auth";
import { auth, provider } from "../config/firebase";
import AvatarGrid from "../components/common/AvatarGrid";
import Navbar from "../components/common/Navbar";

const Signup = () => {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [selectedAvatar, setSelectedAvatar] = useState(null);
  const [errors, setErrors] = useState({});
  const navigate = useNavigate();

  const validateFields = () => {
    const newErrors = {};
    if (!name.trim()) newErrors.name = "Enter your name";
    if (!email.trim()) newErrors.email = "Enter your email";
    if (!password.trim()) newErrors.password = "Enter your password";
    if (!selectedAvatar) newErrors.avatar = "Choose an avatar";
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleEmailSignup = async () => {
    if (!validateFields()) return;

    try {
      const result = await createUserWithEmailAndPassword(auth, email, password);
      await updateProfile(result.user, { displayName: name });
      const token = await result.user.getIdToken();

      localStorage.setItem("token", token);
      localStorage.setItem(
        "user",
        JSON.stringify({
          name,
          uid: result.user.uid,
          avatar: selectedAvatar,
        })
      );

      window.location.href = "/landing";
    } catch (err) {
      console.error("❌ Signup failed:", err.message);
      const newErrors = {};

      if (err.code === "auth/email-already-in-use") {
        newErrors.email = "Email already in use";
      } else if (err.code === "auth/invalid-email") {
        newErrors.email = "Invalid email address";
      } else if (err.code === "auth/weak-password") {
        newErrors.password = "Password should be at least 6 characters";
      } else {
        newErrors.general = "Something went wrong. Try again.";
      }

      setErrors((prev) => ({ ...prev, ...newErrors }));
    }
  };

  const handleGoogleSignup = async () => {
    if (!selectedAvatar) {
      setErrors({ avatar: "Choose an avatar before continuing" });
      return;
    }

    try {
      const result = await signInWithPopup(auth, provider);
      const token = await result.user.getIdToken();

      localStorage.setItem("token", token);
      localStorage.setItem(
        "user",
        JSON.stringify({
          name: result.user.displayName || "Google User",
          uid: result.user.uid,
          avatar: selectedAvatar,
        })
      );

      window.location.href = "/landing";
    } catch (err) {
      console.error("Google signup failed:", err.message);
      setErrors((prev) => ({
        ...prev,
        general: "Google signup failed. Try again.",
      }));
    }
  };

  return (
    <div className="min-h-screen bg-black text-white flex flex-col">
      <Navbar />
      <div className="flex flex-col lg:flex-row items-center justify-center flex-1 gap-10 px-6 py-10">
        {/* Signup Form */}
        <div className="bg-[#FFFB00] p-6 sm:p-8 rounded-xl shadow-xl text-black w-full sm:w-[320px] md:w-[380px] lg:w-[420px]">
          <h1 className="text-2xl sm:text-3xl font-black text-center mb-6 font-montserrat">
            Sign-Up
          </h1>

          {/* Name */}
          <label className="block text-xs sm:text-sm font-silkscreen mb-1">
            ENTER YOUR NAME
          </label>
          <input
            type="text"
            className={`w-full rounded px-3 py-2 mb-1 text-black bg-white border-2 transition ${
              errors.name ? "border-red-500" : "border-transparent"
            }`}
            value={name}
            placeholder="Name"
            onChange={(e) => setName(e.target.value)}
          />
          {errors.name && (
            <p className="text-red-500 text-xs font-silkscreen mb-2">
              {errors.name}
            </p>
          )}

          {/* Email */}
          <label className="block text-xs sm:text-sm font-silkscreen mb-1">
            ENTER YOUR EMAIL ADDRESS
          </label>
          <input
            type="email"
            className={`w-full rounded px-3 py-2 mb-1 text-black bg-white border-2 transition ${
              errors.email ? "border-red-500" : "border-transparent"
            }`}
            value={email}
            placeholder="Email"
            onChange={(e) => setEmail(e.target.value)}
          />
          {errors.email && (
            <p className="text-red-500 text-xs font-silkscreen mb-2">
              {errors.email}
            </p>
          )}

          {/* Password */}
          <label className="block text-xs sm:text-sm font-silkscreen mb-1">
            ENTER YOUR PASSWORD
          </label>
          <input
            type="password"
            className={`w-full rounded px-3 py-2 mb-1 text-black bg-white border-2 transition ${
              errors.password ? "border-red-500" : "border-transparent"
            }`}
            value={password}
            placeholder="Password"
            onChange={(e) => setPassword(e.target.value)}
          />
          {errors.password && (
            <p className="text-red-500 text-xs font-silkscreen mb-2">
              {errors.password}
            </p>
          )}

          <button
            className="bg-black text-white w-full py-2 rounded mb-3 font-silkscreen hover:scale-105 transition"
            onClick={handleEmailSignup}
          >
            SIGN-UP!
          </button>

          {errors.general && (
            <p className="text-red-500 text-xs text-center font-silkscreen mb-2">
              {errors.general}
            </p>
          )}

          <p className="text-sm text-center text-black font-montserrat">
            Already Have an Account?{" "}
            <span
              className="relative cursor-pointer font-semibold transition-all group hover:text-black"
              onClick={() => navigate("/login")}
            >
              Login!
              <span className="absolute left-0 -bottom-1 w-0 h-[2px] bg-black rounded-full transition-all duration-300 group-hover:w-full"></span>
            </span>
          </p>

          <div className="flex gap-2 mt-4">
            <button
              onClick={handleGoogleSignup}
              className="flex-1 flex justify-center items-center gap-3 bg-white text-black px-4 py-3 rounded text-sm sm:text-base font-silkscreen border-2 border-transparent hover:border-black transition-all duration-200"
            >
              <img src={googleIcon} alt="G" className="w-5 h-5" />
              Sign Up With Google
            </button>
          </div>
        </div>

        {/* Avatar Picker */}
        <div className="flex flex-col items-center gap-4 w-full max-w-xl">
          <div
            className={`bg-[#FFFB00]/35 text-[#FFFB00] whitespace-nowrap w-fit px-6 sm:px-10 py-2 rounded border-2 border-[#FFFB00] shadow-[0_0_12px_#FFFB00] font-silkscreen text-base sm:text-lg text-center ${
              errors.avatar
                ? "border-red-500 text-red-500 bg-red-400/25 shadow-[0_0_12px_red]"
                : ""
            }`}
          >
            PICK YOUR AVATAR
          </div>

          <AvatarGrid
            selected={selectedAvatar}
            onSelect={(avatar) => {
              setSelectedAvatar(avatar);
              setErrors((prev) => ({ ...prev, avatar: null }));
            }}
          />

          {errors.avatar && (
            <p className="text-red-500 text-xs font-silkscreen -mt-2">
              {errors.avatar}
            </p>
          )}
        </div>
      </div>
    </div>
  );
};

export default Signup;
```

## /client/src/pages/WaitingRoom.jsx

```jsx path="/client/src/pages/WaitingRoom.jsx" 
import React, { useEffect, useState, useRef } from "react";
import socket from "../socket";
import { useNavigate } from "react-router-dom";

const WaitingRoom = () => {
  const [players, setPlayers] = useState([]);
  const [roomCode, setRoomCode] = useState("");
  const [hostUID, setHostUID] = useState("");
  const [currentUID, setCurrentUID] = useState("");
  const [isMovingToGame, setIsMovingToGame] = useState(false);
  const navigate = useNavigate();

  // Copy tooltip state for middle section
  const [showCopyTip, setShowCopyTip] = useState(false);
  const [copyTipText, setCopyTipText] = useState("Click to copy code");
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
  const tipTimeoutRef = useRef(null);
  const showTip = (text, ms = 0) => {
    if (tipTimeoutRef.current) clearTimeout(tipTimeoutRef.current);
    setCopyTipText(text);
    setShowCopyTip(true);
    if (ms > 0) {
      tipTimeoutRef.current = setTimeout(() => {
        setCopyTipText("Click to copy code");
        setShowCopyTip(false);
      }, ms);
    }
  };

  // Arrival glow state for new players
  const [arrivalGlow, setArrivalGlow] = useState({});
  const arrivalTimersRef = useRef({});
  const prevPlayersRef = useRef(new Set());
  const firstUpdateRef = useRef(true);

  useEffect(() => {
    const user = JSON.parse(localStorage.getItem("user"));
    const roomData = JSON.parse(localStorage.getItem("room"));

    if (!user || !roomData) {
      navigate("/");
      return;
    }

    setCurrentUID(user.uid);
    setRoomCode(roomData.code);
    setHostUID(roomData.hostUID);

    const joinRoom = () => {
      socket.emit("join-room", { roomCode: roomData.code, user });
    };

    if (!socket.connected) {
      socket.once("connect", joinRoom);
      if (socket.disconnected) socket.connect();
    } else {
      joinRoom();
    }

    socket.on("room-updated", (data) => {
      const incomingSet = new Set(data.players.map((p) => p.uid));
      if (firstUpdateRef.current) {
        firstUpdateRef.current = false;
        prevPlayersRef.current = incomingSet;
        setPlayers(data.players);
        return;
      }
      // Detect new players
      const newOnes = data.players.filter((p) => !prevPlayersRef.current.has(p.uid));
      if (newOnes.length) {
        setArrivalGlow((old) => {
          const next = { ...old };
          newOnes.forEach((p) => {
            next[p.uid] = true;
            if (arrivalTimersRef.current[p.uid]) clearTimeout(arrivalTimersRef.current[p.uid]);
            arrivalTimersRef.current[p.uid] = setTimeout(() => {
              setArrivalGlow((curr) => {
                const n = { ...curr };
                delete n[p.uid];
                return n;
              });
              delete arrivalTimersRef.current[p.uid];
            }, 1200);
          });
          return next;
        });
      }
      prevPlayersRef.current = incomingSet;
      setPlayers(data.players);
    });

    socket.on("move-to-game-room", () => {
      setIsMovingToGame(true);
      setTimeout(() => navigate("/game-room"), 500);
    });

    return () => {
      socket.off("room-updated");
      socket.off("move-to-game-room");
      socket.off("connect");
      Object.values(arrivalTimersRef.current).forEach((t) => clearTimeout(t));
      arrivalTimersRef.current = {};
      if (tipTimeoutRef.current) clearTimeout(tipTimeoutRef.current);
    };
  }, [navigate]);

  const isHost = currentUID === hostUID;

  // Resolve avatar value (number, "/avatars/x.png", "3", or external URL) to a valid img src
  const getAvatarSrc = (avatar) => {
    if (!avatar) return "/avatars/1.png";
    if (typeof avatar === "number") return `/avatars/${avatar}.png`;
    if (typeof avatar === "string") {
      if (/^https?:\/\//.test(avatar)) return avatar;         // absolute URL
      if (avatar.startsWith("/avatars/")) return avatar;       // already a path
      if (/^\d+$/.test(avatar)) return `/avatars/${avatar}.png`; // numeric string
      return avatar; // fallback as-is
    }
    return "/avatars/1.png";
  };

  const handleStartGame = () => {
    socket.emit("start-game", { roomCode });
    setIsMovingToGame(true);
  };

  // Theme-matched smooth blob loading screen
  const LoadingOverlay = ({ text = "Loading..." }) => {
    const [movingBlobs, setMovingBlobs] = useState([]);
    const rafRef = useRef(null);

    useEffect(() => {
      const blobs = [];
      const numBlobs = Math.floor(Math.random() * 70) + 20;
      for (let i = 0; i < numBlobs; i++) {
        const size = Math.random() * 160 + 180;
        const speedX = (Math.random() * 2 - 1) * 6.5;
        const speedY = (Math.random() * 2 - 1) * 6.5;
        const x = Math.random() * window.innerWidth;
        const y = Math.random() * window.innerHeight;
        const opacity = Math.random() * 0.05 + 0.07;
        blobs.push({ id: i, size, x, y, speedX, speedY, opacity, color: "#FFFB00" });
      }
      setMovingBlobs(blobs);

      const animate = () => {
        setMovingBlobs((prev) =>
          prev.map((b) => {
            let x = b.x + b.speedX;
            let y = b.y + b.speedY;
            if (x < 0 || x > window.innerWidth - b.size) b.speedX *= -1, (x = b.x + b.speedX);
            if (y < 0 || y > window.innerHeight - b.size) b.speedY *= -1, (y = b.y + b.speedY);
            return { ...b, x, y };
          })
        );
        rafRef.current = requestAnimationFrame(animate);
      };
      animate();
      return () => rafRef.current && cancelAnimationFrame(rafRef.current);
    }, []);

    return (
      <div className="h-screen w-full bg-black text-white flex flex-col items-center justify-center font-silkscreen relative overflow-hidden">
        {movingBlobs.map((blob) => (
          <div
            key={blob.id}
            className="absolute rounded-full blur-3xl"
            style={{
              width: `${blob.size}px`,
              height: `${blob.size}px`,
              left: `${blob.x}px`,
              top: `${blob.y}px`,
              backgroundColor: blob.color,
              opacity: blob.opacity,
            }}
          />
        ))}
        <div className="relative z-10 flex flex-col items-center">
          <div className="w-12 h-12 border-4 border-[#FFFB00] border-t-transparent rounded-full animate-spin mb-6"></div>
          <h2 className="text-2xl font-bold">{text}</h2>
        </div>
      </div>
    );
  };

  if (isMovingToGame) {
    return <LoadingOverlay text="Starting Game..." />;
  }

  return (
    <div className="h-screen w-full bg-black text-white flex flex-col lg:flex-row overflow-hidden font-silkscreen p-2 sm:p-4 gap-2 sm:gap-3 lg:gap-4">
      {/* Floating copy tooltip */}
      {showCopyTip && (
        <div
          className="fixed z-50 pointer-events-none bg-[#FFFB00] text-black border-2 border-black px-2 py-1 rounded text-xs shadow-[0_0_10px_#FFFB00]"
          style={{ left: mousePos.x + 12, top: mousePos.y - 24 }}
        >
          {copyTipText}
        </div>
      )}

      {/* Players Section */}
      <div className="w-full lg:w-[22%] bg-[#1a1a1ab8] rounded-xl p-3 sm:p-4 lg:p-5 flex flex-col order-1 lg:order-none h-[45vh] lg:h-full overflow-hidden">
        <div className="flex-1">
          <h2 className="text-white text-base sm:text-lg lg:text-xl mb-2">Players ({players.length})</h2>
          <ul className="h-full overflow-y-auto custom-scrollbar space-y-1 sm:space-y-2 lg:space-y-3">
            {players.map((p) => (
              <li
                key={p.uid}
                className={`flex items-center gap-2 bg-[#2d2d2d58] p-2 rounded-lg transition-all duration-300 ${
                  arrivalGlow[p.uid] ? "animate-neon-punch ring-2 ring-[#FFFB00]/70 shadow-[0_0_18px_#FFFB00]" : ""
                }`}
              >
                <img
                  src={getAvatarSrc(p.avatar)}
                  alt={p.name}
                  className="w-4 h-4 sm:w-5 sm:h-5 lg:w-6 lg:h-6 rounded-sm"
                  onError={(e) => { e.currentTarget.src = "/avatars/1.png"; }}
                />
                <span className={`text-xs sm:text-sm lg:text-base ${p.uid === currentUID ? "text-[#FFFB00]" : ""}`}>
                  {p.name}
                  {p.uid === hostUID && (
                    <i className="fa-solid fa-crown text-[#FFFB00] ml-3" aria-label="Host" title="Host" />
                  )}
                </span>
              </li>
            ))}
          </ul>
        </div>

        {isHost && (
          <button
            onClick={handleStartGame}
            className="mt-2 sm:mt-3 lg:mt-4 py-2 sm:py-2 lg:py-3 rounded-md font-bold bg-[#FFFB00] text-black hover:brightness-110 shadow-[0_0_10px_#FFFB00] text-xs sm:text-sm lg:text-base"
          >
            START GAME
          </button>
        )}
      </div>

      {/* Main Waiting Room Section */}
      <div
        className="w-full lg:w-[56%] bg-[#1a1a1ab8] rounded-xl flex flex-col items-center justify-center order-2 lg:order-none h-[40%] lg:h-full cursor-pointer"
        onMouseEnter={() => showTip("Click to copy code")}
        onMouseLeave={() => setShowCopyTip(false)}
        onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}
        onClick={async () => {
          try {
            if (roomCode) {
              await navigator.clipboard.writeText(String(roomCode));
              showTip("Copied!", 900);
            }
          } catch {
            showTip("Copy failed", 1200);
          }
        }}
      >
        <h1 className="text-xl sm:text-2xl lg:text-3xl font-bold text-white mb-3 sm:mb-4 text-center">Waiting Room</h1>
        <p className="text-gray-300 text-sm sm:text-base lg:text-lg mb-2 text-center px-4">
          Room Code: <span className="text-white font-mono text-base sm:text-lg lg:text-xl">{roomCode}</span>
        </p>
        {!isHost && (
          <div className="mt-3 sm:mt-4 lg:mt-6 text-[#FFFB00] text-center text-sm sm:text-base lg:text-lg px-4 flex items-center justify-center gap-2">
            <span
              className="inline-block w-4 h-4 sm:w-5 sm:h-5 border-2 border-[#FFFB00] border-t-transparent rounded-full animate-spin drop-shadow-[0_0_6px_#FFFB00]"
              aria-hidden="true"
            />
            <span>Waiting for host to start the game...</span>
          </div>
        )}
      </div>

      {/* Chat Section */}
      <div className="w-full lg:w-[22%] bg-[#1a1a1ab8] rounded-xl p-3 sm:p-4 lg:p-5 flex flex-col order-3 lg:order-none h-[30%] lg:h-full">
        <h2 className="text-white text-base sm:text-lg lg:text-xl mb-2 sm:mb-3">Chat</h2>
        <div className="flex-1 bg-[#2d2d2d58] rounded-lg p-2 sm:p-2 lg:p-3 overflow-y-auto">
          <p className="text-gray-400 italic text-xs sm:text-xs lg:text-sm">System: Waiting for game to start...</p>
        </div>
      </div>

      {/* Neon punch animation style */}
      <style>{`
        @keyframes neonPunch {
          0% { box-shadow: 0 0 0 transparent; transform: scale(0.98); background-color: rgba(255, 251, 0, 0.10); }
          40% { box-shadow: 0 0 18px #FFFB00; transform: scale(1.01); background-color: rgba(255, 251, 0, 0.14); }
          100% { box-shadow: 0 0 0 transparent; transform: scale(1); background-color: rgba(45, 45, 45, 0.35); }
        }
        .animate-neon-punch {
          animation: neonPunch 1.1s ease-out;
        }
      `}</style>
    </div>
  );
};

export default WaitingRoom;

```

## /client/src/setupTests.js

```js path="/client/src/setupTests.js" 

```

## /client/src/socket.js

```js path="/client/src/socket.js" 
import { io } from "socket.io-client";

const socket = io("https://guessync.onrender.com/", {
  autoConnect: false,
  reconnection: true,
  reconnectionAttempts: Infinity,
  reconnectionDelay: 1000,
  reconnectionDelayMax: 5000,
  randomizationFactor: 0.5,
  timeout: 20000,

  transports: ["websocket"],
  upgrade: false,
  forceNew: true,
  withCredentials: true,

  query: {
    clientType: "player",
    version: "1.0"
  }
});

socket
  .on("connect", () => {
    console.log("⚡ Socket connected:", socket.id);
  })
  .on("connect_error", (err) => {
    console.error("Connection error:", err.message);
    setTimeout(() => socket.connect(), 1000 + Math.random() * 2000);
  })
  .on("reconnect_attempt", (attempt) => {
    console.log(`Reconnect attempt #${attempt}`);
  })
  .on("reconnect_failed", () => {
    console.error("Reconnection failed");
  });

export default socket;
```

## /client/src/styles/tailwind.css

```css path="/client/src/styles/tailwind.css" 
@tailwind base;
@tailwind components;
@tailwind utilities;

```

## /client/src/tests/JoinRoom.test.jsx

```jsx path="/client/src/tests/JoinRoom.test.jsx" 
import { render, screen, fireEvent } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import JoinRoom from "../pages/JoinRoom";
import CreateRoom from "../pages/CreateRoom";
import Signup from "../pages/Signup";

const renderWithRouter = (ui) => render(<BrowserRouter>{ui}</BrowserRouter>);

describe("JoinRoom Page", () => {
  test("1. Shows error on Join without 6-digit code", () => {
    renderWithRouter(<JoinRoom />);
    const joinBtn = screen.getByRole("button", { name: /join/i });
    fireEvent.click(joinBtn);
    expect(screen.getByText(/please enter a 6-digit code/i)).toBeInTheDocument();
  });

  test("2. Displays typed number on button click", () => {
    renderWithRouter(<JoinRoom />);
    const btn = screen.getByRole("button", { name: "5" });
    fireEvent.click(btn);
    expect(screen.getByText("5 _ _ _ _ _")).toBeInTheDocument();
  });
});

describe("CreateRoom Page", () => {
  test("3. Room code is auto-generated and visible", () => {
    renderWithRouter(<CreateRoom />);
    const roomText = screen.getByText(/room code/i);
    expect(roomText).toBeInTheDocument();
  });

  test("4. Allows increasing number of players", () => {
    renderWithRouter(<CreateRoom />);
    const plusButton = screen.getAllByText("+")[0];
    fireEvent.click(plusButton);
    const updatedCount = screen.getAllByText("2");
    expect(updatedCount.length).toBeGreaterThan(0); // Should show 2
  });
});

describe("Signup Page", () => {
  test("5. Shows validation error if no name entered", () => {
    renderWithRouter(<Signup />);
    const signupBtn = screen.getByText("SIGN-UP!");
    fireEvent.click(signupBtn);
    expect(screen.getByText("Enter your name")).toBeInTheDocument();
  });

  test("6. Shows avatar error if none selected for Google signup", () => {
    renderWithRouter(<Signup />);
    const googleBtn = screen.getByText(/Sign Up With Google/i);
    fireEvent.click(googleBtn);
    expect(screen.getByText("Choose an avatar before continuing")).toBeInTheDocument();
  });
});

```

## /client/tailwind.config.cjs

```cjs path="/client/tailwind.config.cjs" 
// tailwind.config.cjs
module.exports = {
  content: ["./index.html", "./src/**/*.{js,jsx}"],
  theme: {
    extend: {
      colors: {
        primary: '#FFFB00',
        secondary: '#4E4E4E',
      },
      fontFamily: {
        montserrat: ["Montserrat", "sans-serif"],
        silkscreen: ["Silkscreen", "cursive"],
      },
    },
  },
  plugins: [],
};

```

## /client/vite.config.js

```js path="/client/vite.config.js" 
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.js',
  },
});

```

## /netlify.toml

```toml path="/netlify.toml" 
[build]
  base = "client"
  publish = "dist"
  command = "npm install && npm run build"

```

## /server/.env.example

```example path="/server/.env.example" 
# MongoDB
MONGO_URI="your_mongo_connection_string"

# Server
PORT=5000

# Spotify API
SPOTIFY_CLIENT_ID="your_spotify_client_id"
SPOTIFY_CLIENT_SECRET="your_spotify_client_secret"

# YouTube API
YOUTUBE_API_KEY="your_youtube_api_key"

# Gemini API
GEMINI_API_KEY="your_gemini_api_key"

# Firebase
FIREBASE_PROJECT_ID="your_firebase_project_id"
FIREBASE_PRIVATE_KEY_ID="your_firebase_private_key_id"
FIREBASE_PRIVATE_KEY="your_firebase_private_key"
FIREBASE_CLIENT_EMAIL="your_firebase_client_email"
FIREBASE_CLIENT_ID="your_firebase_client_id"
FIREBASE_AUTH_URI="https://accounts.google.com/o/oauth2/auth"
FIREBASE_TOKEN_URI="https://oauth2.googleapis.com/token"
FIREBASE_AUTH_PROVIDER_CERT_URL="https://www.googleapis.com/oauth2/v1/certs"
FIREBASE_CLIENT_CERT_URL="your_firebase_client_cert_url"
FIREBASE_UNIVERSE_DOMAIN="googleapis.com"

```

## /server/Dockerfile

``` path="/server/Dockerfile" 
FROM node:18

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 5000

CMD ["npm", "start"]

```

## /server/config/db.js

```js path="/server/config/db.js" 
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();

const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGO_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log(`MongoDB connected: ${conn.connection.host}`);
  } catch (err) {
    console.error('MongoDB connection failed:', err.message);
    process.exit(1);
  }
};

export default connectDB;

```

## /server/config/firebase-adminsdk.js

```js path="/server/config/firebase-adminsdk.js" 
// ✅ This file uses ESM syntax and works with process.env
const serviceAccount = {
  type: "service_account",
  project_id: process.env.FIREBASE_PROJECT_ID,
  private_key_id: process.env.FIREBASE_PRIVATE_KEY_ID,
  private_key: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
  client_email: process.env.FIREBASE_CLIENT_EMAIL,
  client_id: process.env.FIREBASE_CLIENT_ID,
  auth_uri: process.env.FIREBASE_AUTH_URI,
  token_uri: process.env.FIREBASE_TOKEN_URI,
  auth_provider_x509_cert_url: process.env.FIREBASE_AUTH_PROVIDER_CERT_URL,
  client_x509_cert_url: process.env.FIREBASE_CLIENT_CERT_URL,
  universe_domain: process.env.FIREBASE_UNIVERSE_DOMAIN,
};

export default serviceAccount;

```

## /server/config/firebase.js

```js path="/server/config/firebase.js" 
import admin from 'firebase-admin';
import serviceAccount from './firebase-adminsdk.js'; // ✅ this is now JS

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});

export const verifyFirebaseToken = async (req, res, next) => {
  try {
    const token = req.headers.authorization?.split('Bearer ')[1];

    if (token === "guest") {
      req.uid = "guest";
      return next();
    }

    const decoded = await admin.auth().verifyIdToken(token);
    req.uid = decoded.uid;
    next();
  } catch (err) {
    return res.status(401).json({ message: 'Unauthorized' });
  }
};

```

## /server/controllers/roomController.js

```js path="/server/controllers/roomController.js" 
import Room from "../models/Room.js";
import Song from "../models/Song.js";
import User from "../models/User.js";
import { generateRoomCode } from "../utils/generateCode.js";
import { getSpotifyAccessToken } from "../utils/getSpotifyAccessToken.js";
import { getSongsFromSpotifyPlaylist } from "../utils/fillPlaylistFromSpotify.js";

export const createRoom = async (req, res) => {
  const {
    uid,
    name,
    avatar,
    useSpotify,
    playlistId,
    code,
    rounds,
    duration,
    languages,
    rules
  } = req.body;

  console.log("Creating room with code:", code);
  console.log("Host:", uid, name);

  try {
    let playlist = [];

    if (useSpotify && playlistId) {
      const accessToken = await getSpotifyAccessToken();
      playlist = await getSongsFromSpotifyPlaylist(playlistId, accessToken,rounds);
    } else {
      playlist = await Song.aggregate([{ $sample: { size: 10 } }]);
    }

    const newRoom = new Room({
      code,
      hostUID: uid,
      players: [{ uid, name, avatar }],
settings: {
  rounds,
  duration,
  language: req.body.language || "tamil",
},

      playlist,
      rules
    });
    
    console.log("Attempting to save new room...");
    await newRoom.save();
    
    // Update relationship: Add room to user's roomsCreated
    await User.findOneAndUpdate(
      { uid },
      { $addToSet: { roomsCreated: code } },
      { new: true }
    );
    
    console.log("Room saved to MongoDB successfully!");
    res.status(201).json({ message: "Room created", code });
  } catch (err) {
    console.error("Room create error:", err);
    res.status(500).json({ message: "Error creating room" });
  }
};

export const joinRoom = async (req, res) => {
  const { code, uid, name, avatar } = req.body;

  console.log("Join request for room code:", code);
  console.log("Joining user:", uid, name);

  try {
    const room = await Room.findOne({ code });
    if (!room) {
      console.warn("Room not found");
      return res.status(404).json({ message: "Room not found" });
    }

    const alreadyInRoom = room.players.find((p) => p.uid === uid);
    if (alreadyInRoom) {
      console.warn("⚠️ Already in room");
      return res.status(400).json({ message: "Already joined" });
    }

    room.players.push({ uid, name, avatar });
    await room.save();

    // Update relationship: Add room to user's roomsJoined
    await User.findOneAndUpdate(
      { uid },
      { $addToSet: { roomsJoined: code } },
      { new: true }
    );

    console.log("Player added to room and saved.");
    res.json({ message: "Joined room", room });
  } catch (err) {
    console.error("Join Room error:", err);
    res.status(500).json({ message: "Error joining room" });
  }
};

export const updateRoom = async (req, res) => {
  const { code } = req.params;
  const { rounds, duration, language, rules } = req.body;

  console.log("Updating room:", code);

  try {
    const room = await Room.findOne({ code });

    if (!room) {
      return res.status(404).json({ message: "Room not found" });
    }

    if (rounds !== undefined) room.settings.rounds = rounds;
    if (duration !== undefined) room.settings.duration = duration;
    if (language !== undefined) room.settings.language = language;
    if (rules !== undefined) room.rules = rules;

    await room.save();
    console.log("Room updated successfully");

    res.status(200).json({ message: "Room updated", room });
  } catch (err) {
    console.error("Room update error:", err);
    res.status(500).json({ message: "Error updating room" });
  }
};

```

## /server/controllers/userController.js

```js path="/server/controllers/userController.js" 
import User from '../models/User.js';

export const createOrUpdateUser = async (req, res) => {
  const { uid, name, avatar } = req.body;

  try {
    let user = await User.findOne({ uid });

    if (!user) {
      user = new User({ uid, name, avatar });
      await user.save();
      return res.status(201).json({ message: "User created", user });
    }

    // update existing user (e.g. name/avatar changed)
    user.name = name;
    user.avatar = avatar;
    await user.save();

    res.json({ message: "User updated", user });
  } catch (err) {
    console.error("createOrUpdateUser error:", err);
    res.status(500).json({ message: "Error saving user" });
  }
};

export const getUserProfile = async (req, res) => {
  const { uid } = req.params;

  try {
    const user = await User.findOne({ uid });
    if (!user) return res.status(404).json({ message: "User not found" });

    res.json({ user });
  } catch (err) {
    console.error("getUserProfile error:", err);
    res.status(500).json({ message: "Error fetching user" });
  }
};

```

## /server/cronJobs/roomCleanup.js

```js path="/server/cronJobs/roomCleanup.js" 
// server/cronJobs/roomCleanup.js

import cron from "node-cron";
import Room from "../models/Room.js";

export const scheduleRoomCleanup = () => {
  cron.schedule("* * * * *", async () => {
    const twentyMinsAgo = new Date(Date.now() - 20 * 60 * 1000);

    try {
      const result = await Room.deleteMany({ createdAt: { $lt: twentyMinsAgo } });

      if (result.deletedCount > 0) {
        console.log(`Deleted ${result.deletedCount} expired room at ${new Date().toLocaleTimeString()}`);
      }
    } catch (err) {
      console.error("Error during room cleanup:", err.message);
    }
  });
};

```

## /server/models/Room.js

```js path="/server/models/Room.js" 
import mongoose from 'mongoose';

const RoomSchema = new mongoose.Schema({
  code: { type: String, required: true, unique: true },
  hostUID: { type: String, required: true },

  hintVotes: {
    movie: { type: [String], default: [] },
    composer: { type: [String], default: [] },
    cover: { type: [String], default: [] },
    ai: { type: [String], default: [] } 
  },

  hintUsed: {
    movie: { type: Boolean, default: false },
    composer: { type: Boolean, default: false },
    cover: { type: Boolean, default: false },
    ai: { type: Boolean, default: false } // ✅ Prevent reuse
  },

  players: [
    {
      uid: String,
      name: String,
      avatar: String,
      score: { type: Number, default: 0 },
    }
  ],

  settings: {
    rounds: Number,
    duration: Number,
    language: String,
  },

  playlist: [
    {
      song: String,
      movie: String,
      composer: String,
      cover: String,
      videoId: String
    }
  ],

  currentRound: { type: Number, default: 1 },
  currentSongIndex: { type: Number, default: 0 },

  guesses: [
    {
      uid: String,
      text: String,
      correct: Boolean
    }
  ],

  guessedCorrectlyThisRound: { type: [String], default: [] }, // ✅ NEW FIELD
  rules: { type: String, default: "" },
  isActive: { type: Boolean, default: false }

}, { timestamps: true });

export default mongoose.model("Room", RoomSchema);

```

## /server/models/Song.js

```js path="/server/models/Song.js" 
import mongoose from "mongoose";

const songSchema = new mongoose.Schema({
  song: String,
  movie: String,
  composer: String,
  cover: String,
  videoId: String 
});

export default mongoose.model("Song", songSchema);

```

## /server/models/User.js

```js path="/server/models/User.js" 
import mongoose from 'mongoose';


const MatchSchema = new mongoose.Schema({
  date: Date,
  partyName: String,
  place: String,  
  points: Number
}, { _id: false });

const UserSchema = new mongoose.Schema({
  uid: { type: String, required: true, unique: true },  // Firebase UID or custom
  name: { type: String, required: true },
  avatar: { type: String, default: "" },
  wins: { type: Number, default: 0 },
  gamesPlayed: { type: Number, default: 0 },
  songsGuessed: { type: Number, default: 0 },
  streak: { type: Number, default: 0 },
  fastestGuess: { type: Number, default: null }, // in seconds
  
  matchHistory: [MatchSchema],
  
  roomsCreated: { type: [String], default: [] }, // Relationship: User -> Rooms Created
  roomsJoined: { type: [String], default: [] } // Relationship: User -> Rooms Joined
  
}, { timestamps: true });

// on process user and profile part not yet completed
export default mongoose.model("User", UserSchema);

```

## /server/package.json

```json path="/server/package.json" 
{
  "name": "server",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@fontsource/montserrat": "^5.2.5",
    "@fontsource/silkscreen": "^5.2.5",
    "@google/generative-ai": "^0.24.1",
    "@langchain/core": "^0.3.61",
    "@langchain/google-genai": "^0.2.13",
    "axios": "^1.8.4",
    "cors": "^2.8.5",
    "dotenv": "^16.4.7",
    "express": "^5.1.0",
    "express-rate-limit": "^7.5.1",
    "firebase-admin": "^13.2.0",
    "fuse.js": "^7.1.0",
    "langchain": "^0.3.29",
    "mongoose": "^8.13.2",
    "node-cron": "^4.1.1",
    "node-fetch": "^3.3.2",
    "openai": "^4.91.1",
    "razorpay": "^2.9.6",
    "socket.io": "^4.8.1",
    "spotify-web-api-node": "^5.0.2"
  },
  "devDependencies": {
    "nodemon": "^3.1.9"
  }
}

```

## /server/routes/authRoutes.js

```js path="/server/routes/authRoutes.js" 
import express from 'express';
import { verifyFirebaseToken } from '../config/firebase.js';

const router = express.Router();

router.get('/me', verifyFirebaseToken, (req, res) => {
  res.json({ uid: req.uid });
});

export default router;

```

## /server/routes/roomRoutes.js

```js path="/server/routes/roomRoutes.js" 
import express from 'express';
import { createRoom, joinRoom,updateRoom} from '../controllers/roomController.js';
import { verifyFirebaseToken } from '../config/firebase.js';

const router = express.Router();

router.post('/create', verifyFirebaseToken, createRoom);
router.post('/join', verifyFirebaseToken, joinRoom);
router.put('/update/:code', verifyFirebaseToken, updateRoom);

export default router;

```

## /server/routes/songRoutes.js

```js path="/server/routes/songRoutes.js" 
import express from "express";
import Room from "../models/Room.js";

const router = express.Router();

router.get("/by-index", async (req, res) => {
  const roomCode = req.query.roomCode;
  const index = parseInt(req.query.index);

  console.log("GET /by-index", roomCode, index);

  try {
    const room = await Room.findOne({ code: roomCode });
    if (!room) {
      console.log("Room not found");
      return res.status(404).json({ message: "Room not found" });
    }

    if (!room.playlist || !room.playlist[index]) {
      console.log("Song not found at index:", index, "in room:", roomCode);
      return res.status(404).json({ message: "Song not found" });
    }
    res.json({ song: room.playlist[index] });
  } catch (err) {
    console.error("Error fetching song by index:", err);
    res.status(500).json({ message: "Error fetching song by index" });
  }
});

export default router;

```

## /server/routes/userRoutes.js

```js path="/server/routes/userRoutes.js" 
import express from 'express';
import { createOrUpdateUser, getUserProfile } from '../controllers/userController.js';

const router = express.Router();

router.post("/sync", createOrUpdateUser);
router.get("/profile/:uid", getUserProfile);

export default router;

```

## /server/scripts/fetchSpotifySongs.js

```js path="/server/scripts/fetchSpotifySongs.js" 
import fetch from 'node-fetch';
import getYoutubeVideoId from './getYoutubeVideoId.js';

export async function getSongsFromSpotifyPlaylist(playlistId, accessToken) {
  let songs = [];
  let nextUrl = `https://api.spotify.com/v1/playlists/${playlistId}/tracks?limit=100`;

  while (nextUrl) {
    const res = await fetch(nextUrl, {
      headers: { Authorization: `Bearer ${accessToken}` }
    });

    const data = await res.json();
    const items = data.items || [];

    for (let item of items) {
      const track = item.track;
      if (!track) continue;

      const songName = track.name;
      const artistName = track.artists?.[0]?.name || "";
      const composer = track.artists.map(a => a.name).join(", ");
      const movie = track.album.name;
      const cover = track.album.images?.[0]?.url || "";

      const videoId = await getYoutubeVideoId(`${songName} ${artistName}`);

      songs.push({
        song: songName,
        movie,
        composer,
        cover,
        videoId
      });
    }

    nextUrl = data.next;
  }
  const shuffled = songs.sort(() => 0.5 - Math.random());
  return shuffled.slice(0, 10);
}

```

## /server/scripts/fetchYouTubeIds.js

```js path="/server/scripts/fetchYouTubeIds.js" 
import dotenv from "dotenv";
import axios from "axios";
import mongoose from "mongoose";
import Song from "../models/Song.js";
import connectDB from "../config/db.js";

dotenv.config();
await connectDB();

const API_KEY = process.env.YOUTUBE_API_KEY;

const searchYouTubeVideoId = async (query) => {
  const url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&maxResults=1&q=${encodeURIComponent(
    query
  )}&key=${API_KEY}`;

  const res = await axios.get(url);
  return res.data.items?.[0]?.id?.videoId || null;
};

try {
  const songs = await Song.find({ videoId: { $exists: false } });

  for (const song of songs) {
    const searchQuery = `${song.song} ${song.movie} song`;
    console.log(`🔍 Searching for: ${searchQuery}`);

    const videoId = await searchYouTubeVideoId(searchQuery);

    if (videoId) {
      song.videoId = videoId;
      await song.save();
      console.log(`Updated: ${song.song} → ${videoId}`);
    } else {
      console.warn(`No video found for: ${searchQuery}`);
    }
  }

  console.log("YouTube videoId update done.");
  process.exit(0);
} catch (err) {
  console.error(" Error fetching YouTube IDs:", err.message);
  process.exit(1);
}

```

## /server/server.js

```js path="/server/server.js" 
import express from 'express';
import http from 'http';
import { Server } from 'socket.io';
import dotenv from 'dotenv';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import connectDB from './config/db.js';
import socketHandler from './sockets/socketHandler.js';
import authRoutes from './routes/authRoutes.js';
import roomRoutes from './routes/roomRoutes.js';
import songRoutes from './routes/songRoutes.js';
import userRoutes from './routes/userRoutes.js';
import { scheduleRoomCleanup } from "./cronJobs/roomCleanup.js";

dotenv.config();
connectDB();

const app = express();
const server = http.createServer(app);
const allowedOrigins = [
  'http://localhost:5173',
  'https://guessync.netlify.app'
];
const io = new Server(server, {
  cors: {
    origin: allowedOrigins,
    methods: ['GET', 'POST'],
    credentials: true
  }
});
const corsOptions = {
    origin: allowedOrigins,
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    credentials: true
};
app.use(cors(corsOptions));
app.use(express.json());

const apiLimiter = rateLimit({
    windowMs: 1 * 60 * 1000,
    max: 5,
    message: { error: 'Too many requests, please try again in a minute.' },
    standardHeaders: true,
    legacyHeaders: false,
});

app.use('/api/room/create', apiLimiter);
app.use('/api/auth/login', apiLimiter);

app.use('/api/auth', authRoutes);
app.use("/api/user", userRoutes);
app.use('/api/room', roomRoutes);
app.use('/api/song', songRoutes);

app.get('/', (req, res) => res.send('API Running'));
scheduleRoomCleanup();
socketHandler(io);

const PORT = process.env.PORT || 5000;
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));

```

## /server/sockets/socketHandler.js

```js path="/server/sockets/socketHandler.js" 
import Room from "../models/Room.js";
import Fuse from "fuse.js";
import { getAIHint } from "../utils/getAIHint.js";

const activeTimers = new Map();

const fuzzyMatch = (actual, guess) => {
  const fuse = new Fuse([{ name: actual }], {
    keys: ["name"],
    includeScore: true,
    threshold: 0.4,
  });
  return fuse.search(guess).length > 0 && fuse.search(guess)[0].score <= 0.4;
};

const socketHandler = (io) => {
  io.on("connection", (socket) => {
    console.log(`User connected: ${socket.id}`);

    socket.on("join-room", async ({ roomCode, user }) => {
      try {
        roomCode = String(roomCode);
        console.log(`${socket.id} joined room ${roomCode}`);

        let room = await Room.findOne({ code: roomCode });
        if (!room) {
          socket.emit("room-not-found");
          return;
        }

        const alreadyJoined = room.players.find((p) => p.uid === user.uid);
        if (!alreadyJoined) {
          room.players.push({ ...user, score: 0 });
          await room.save();
          console.log(`+ ${user.name} added to room ${roomCode}`);
        }

        await socket.join(roomCode);

        process.nextTick(() => {
          io.to(roomCode).emit("room-updated", {
            players: room.players,
            hintsUsed: room.hintUsed || {},
          });
        });        
      } catch (err) {
        console.error("Join-room error:", err);
      }
    });

    socket.on("start-game", async ({ roomCode }) => {
      try {
        roomCode = String(roomCode);
        const room = await Room.findOne({ code: roomCode });
        if (!room) return;

        room.isActive = true;
        room.currentRound = 1;
        room.currentSongIndex = 0;
        room.guesses = [];
        room.guessedCorrectlyThisRound = [];
        room.hintVotes = { movie: [], composer: [], cover: [], ai: [] };
        room.hintUsed = { movie: false, composer: false, cover: false, ai: false };
        await room.save();

        io.to(roomCode).emit("move-to-game-room");

        setTimeout(() => startRound(roomCode, io), 3000);
      } catch (err) {
        console.error("Start-game error:", err);
      }
    });

    socket.on("submit-guess", async ({ roomCode, user, text }) => {
      try {
        roomCode = String(roomCode);

        const room = await Room.findOne({ code: roomCode });
        if (!room || !room.isActive) return;

        const song = room.playlist[room.currentSongIndex];
        const correct = fuzzyMatch(song.song.trim().toLowerCase(), text.trim().toLowerCase());

        room.guesses.push({ uid: user.uid, text, correct });
        const player = room.players.find((p) => p.uid === user.uid);

        if (correct && player) {
          player.score += 10;
          if (!room.guessedCorrectlyThisRound.includes(user.uid)) {
            room.guessedCorrectlyThisRound.push(user.uid);
          }
          await room.save();
          io.to(roomCode).emit("correct-guess", { user, score: player.score });

          if (room.guessedCorrectlyThisRound.length === room.players.length) {
            endRound(roomCode, io);
          }
        } else {
          await room.save();
          io.to(roomCode).emit("new-guess", { user, text });
        }
      } catch (err) {
        console.error("Submit-guess error:", err);
      }
    });

    socket.on("vote-hint", async ({ roomCode, uid, hintType }) => {
      try {
        roomCode = String(roomCode);
        const room = await Room.findOne({ code: roomCode });
        if (!room || !room.isActive) return;

        room.hintVotes ??= { movie: [], composer: [], cover: [], ai: [] };
        room.hintUsed ??= { movie: false, composer: false, cover: false, ai: false };

        if (room.hintUsed[hintType] || room.hintVotes[hintType].includes(uid)) return;

        room.hintVotes[hintType].push(uid);
        await room.save();

        const totalPlayers = room.players.length;
        const votes = room.hintVotes[hintType].length;

        io.to(roomCode).emit("hint-vote-count", { hintType, votes, totalPlayers });

        if (votes >= Math.ceil(totalPlayers / 2)) {
          room.hintUsed[hintType] = true;
          await room.save();

          if (hintType === "ai") {
            const aiHint = await getAIHint(room.playlist[room.currentSongIndex]?.song);
            io.to(roomCode).emit("reveal-hint", { hintType: "ai", aiHint });
          } else {
            io.to(roomCode).emit("reveal-hint", { hintType });
          }
        }
      } catch (err) {
        console.error("Vote-hint error:", err);
      }
    });

    socket.on("disconnect", () => {
      console.log(`User disconnected: ${socket.id}`);
    });
  });
};

const startRound = async (roomCode, io) => {
  try {
    roomCode = String(roomCode);
    const room = await Room.findOne({ code: roomCode });
    if (!room) return;

    room.guessedCorrectlyThisRound = [];
    await room.save();

    const song = room.playlist[room.currentSongIndex];
    io.to(roomCode).emit("start-round", {
      round: room.currentRound,
      song,
      duration: room.settings.duration,
      players: room.players,
    });

    activeTimers.set(
      roomCode,
      setTimeout(() => endRound(roomCode, io), room.settings.duration * 1000)
    );
  } catch (err) {
    console.error("Start-round error:", err);
  }
};

const endRound = async (roomCode, io) => {
  try {
    roomCode = String(roomCode);
    const room = await Room.findOne({ code: roomCode });
    if (!room) return;

    clearTimeout(activeTimers.get(roomCode));
    activeTimers.delete(roomCode);

    io.to(roomCode).emit("round-ended");
    io.to(roomCode).emit("loading-next-round");

    const nextSongIndex = room.currentSongIndex + 1;
    if (
      nextSongIndex < room.playlist.length &&
      room.currentRound < room.settings.rounds
    ) {
      const nextSong = room.playlist[nextSongIndex];
      io.to(roomCode).emit("preload-next-song", {
        song: nextSong,
        duration: room.settings.duration,
      });
    }

    activeTimers.set(
      roomCode,
      setTimeout(() => nextRound(roomCode, io), 5000)
    );
  } catch (err) {
    console.error("End-round error:", err);
  }
};

const nextRound = async (roomCode, io) => {
  try {
    roomCode = String(roomCode);
    const room = await Room.findOne({ code: roomCode });
    if (!room) return;

    room.currentSongIndex += 1;
    room.currentRound += 1;
    room.guesses = [];
    room.guessedCorrectlyThisRound = [];
    room.hintVotes = { movie: [], composer: [], cover: [], ai: [] };
    room.hintUsed = { movie: false, composer: false, cover: false, ai: false };

    if (room.currentSongIndex >= room.playlist.length || room.currentRound > room.settings.rounds) {
      await room.save();
      io.to(roomCode).emit("game-over", {
        leaderboard: room.players.sort((a, b) => b.score - a.score),
      });
      return;
    }

    await room.save();
    startRound(roomCode, io);
  } catch (err) {
    console.error("Next-round error:", err);
  }
};

export default socketHandler;

```

## /server/utils/fillPlaylistFromSpotify.js

```js path="/server/utils/fillPlaylistFromSpotify.js" 
import fetch from 'node-fetch';
import getYoutubeVideoId from './getYoutubeVideoId.js';

function shuffleArray(array) {
  return array
    .map(value => ({ value, sort: Math.random() }))
    .sort((a, b) => a.sort - b.sort)
    .map(({ value }) => value);
}

export async function getSongsFromSpotifyPlaylist(playlistId, accessToken, count = 15) {
  const allTracks = [];
  let offset = 0;
  const limit = 100;

  while (true) {
    const url = `https://api.spotify.com/v1/playlists/${playlistId}/tracks?limit=${limit}&offset=${offset}`;
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${accessToken}` }
    });

    const data = await res.json();
    const items = data.items || [];

    allTracks.push(...items);

    if (items.length < limit) break;

    offset += limit;
  }

  const shuffled = shuffleArray(allTracks).slice(0, count);

  const songs = await Promise.all(
    shuffled.map(async (item) => {
      const track = item.track;
      const songName = track.name;
      const artistName = track.artists?.[0]?.name || "";
      const composer = track.artists.map(a => a.name).join(", ");
      const movie = track.album.name;
      const cover = track.album.images?.[0]?.url || "";

      const videoId = await getYoutubeVideoId(`${songName} ${artistName}`);

      return {
        song: songName,
        movie,
        composer,
        cover,
        videoId
      };
    })
  );

  return songs;
}

```

## /server/utils/generateCode.js

```js path="/server/utils/generateCode.js" 
export const generateRoomCode = () => {
    return Math.floor(100000 + Math.random() * 900000).toString();
  };
  
```

## /server/utils/getAIHint.js

```js path="/server/utils/getAIHint.js" 
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
import { PromptTemplate } from "@langchain/core/prompts";

export const getAIHint = async (songName) => {
  try {
    const model = new ChatGoogleGenerativeAI({
      apiKey: process.env.GEMINI_API_KEY,
      model: "gemini-1.5-flash",
      temperature: 0.7,
    });

    const promptTemplate = PromptTemplate.fromTemplate(
      `Give me a fun, short, and helpful hint for the song '{songName}'.
Avoid mentioning the title or giving it away directly. The hint should make it much easier to guess`
    );

    const formattedPrompt = await promptTemplate.format({ songName: songName });
    const result = await model.invoke(formattedPrompt);

    const hint = result.content;

    return hint.trim();
  } catch (err) {
    console.error("Error generating AI hint:", err.message);
    return "Couldn't fetch a hint right now. Try again later.";
  }
};
```

## /server/utils/getSpotifyAccessToken.js

```js path="/server/utils/getSpotifyAccessToken.js" 

import fetch from 'node-fetch';

let cachedToken = null;
let expiresAt = 0;

export async function getSpotifyAccessToken() {
  const now = Date.now();

  if (cachedToken && now < expiresAt) {
    return cachedToken;
  }

  const clientId = process.env.SPOTIFY_CLIENT_ID;
  const clientSecret = process.env.SPOTIFY_CLIENT_SECRET;

  const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');

  const res = await fetch("https://accounts.spotify.com/api/token", {
    method: "POST",
    headers: {
      Authorization: `Basic ${credentials}`,
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: "grant_type=client_credentials"
  });

  const data = await res.json();

  if (data.access_token) {
    cachedToken = data.access_token;
    expiresAt = now + (data.expires_in * 1000) - 60000;
    return cachedToken;
  } else {
    throw new Error("Failed to fetch Spotify access token");
  }
}

```

## /server/utils/getYoutubeVideoId.js

```js path="/server/utils/getYoutubeVideoId.js" 
import fetch from 'node-fetch';

export default async function getYoutubeVideoId(query) {
  const apiKey = process.env.YOUTUBE_API_KEY;
  const encodedQuery = encodeURIComponent(query);

  const url = `https://www.googleapis.com/youtube/v3/search?part=snippet&q=${encodedQuery}&key=${apiKey}&type=video&maxResults=1`;

  const res = await fetch(url);
  const data = await res.json();

  const videoId = data.items?.[0]?.id?.videoId || null;
  return videoId;
}

```


The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.
Copied!