Dhurgh commited on
Commit
64e57ec
·
1 Parent(s): 95a6aa2

Deploy latest updates to FireTech-Messager-Server

Browse files
Files changed (13) hide show
  1. GETTING-STARTED.md +5 -5
  2. MOBILE-CONNECT-FIX.md +5 -6
  3. QUICKSTART.sh +3 -3
  4. README.md +2 -2
  5. SETUP-GUIDE.md +1 -1
  6. client.js +200 -210
  7. index.html +77 -81
  8. init-db.js +3 -3
  9. mobile-connect.ps1 +6 -7
  10. package-lock.json +2 -2
  11. package.json +1 -1
  12. server.js +14 -19
  13. setup-https.ps1 +11 -12
GETTING-STARTED.md CHANGED
@@ -167,11 +167,11 @@ PORT=8080
167
  ## Command Reference
168
 
169
  ```powershell
170
- npm install # Install dependencies
171
- npm start # Start production server
172
- npm run dev # Start dev server (auto-reload)
173
- npm run init-db # Add sample shop items
174
- npm run https # Setup HTTPS (if needed)
175
  ```
176
 
177
  ## Environment Variables
 
167
  ## Command Reference
168
 
169
  ```powershell
170
+ npm install
171
+ npm start
172
+ npm run dev
173
+ npm run init-db
174
+ npm run https
175
  ```
176
 
177
  ## Environment Variables
MOBILE-CONNECT-FIX.md CHANGED
@@ -36,13 +36,13 @@ cd C:\Users\dell\Downloads\message
36
  taskkill /F /IM node.exe 2>$null
37
  Start-Sleep -Seconds 2
38
 
39
- # Set environment variables
40
  $env:SSL_PFX_PATH = (Resolve-Path ".\certs\lan-localhost.pfx").Path
41
  $env:SSL_PFX_PASSPHRASE = "123456"
42
  $env:PORT = "3001"
43
  $env:HOST = "0.0.0.0"
44
 
45
- # Start server
46
  npm start
47
  ```
48
 
@@ -73,17 +73,16 @@ Wait for this message to appear:
73
 
74
  ### Check 1: Are you on the SAME Wi-Fi?
75
  ```powershell
76
- # Run on PC
77
  ipconfig | findstr "Wi-Fi" -A 5
78
 
79
- # Phone: Settings → Wi-Fi → see network name
80
- # They must match!
81
  ```
82
 
83
  ### Check 2: Is server actually listening?
84
  ```powershell
85
  netstat -ano | findstr ":3001"
86
- # Should show: TCP 0.0.0.0:3001 0.0.0.0:0 LISTENING
87
  ```
88
 
89
  ### Check 3: Firewall again (run as Admin)
 
36
  taskkill /F /IM node.exe 2>$null
37
  Start-Sleep -Seconds 2
38
 
39
+
40
  $env:SSL_PFX_PATH = (Resolve-Path ".\certs\lan-localhost.pfx").Path
41
  $env:SSL_PFX_PASSPHRASE = "123456"
42
  $env:PORT = "3001"
43
  $env:HOST = "0.0.0.0"
44
 
45
+
46
  npm start
47
  ```
48
 
 
73
 
74
  ### Check 1: Are you on the SAME Wi-Fi?
75
  ```powershell
76
+
77
  ipconfig | findstr "Wi-Fi" -A 5
78
 
79
+
 
80
  ```
81
 
82
  ### Check 2: Is server actually listening?
83
  ```powershell
84
  netstat -ano | findstr ":3001"
85
+
86
  ```
87
 
88
  ### Check 3: Firewall again (run as Admin)
QUICKSTART.sh CHANGED
@@ -1,11 +1,11 @@
1
  #!/bin/bash
2
- # Quick Start Guide
3
 
4
  echo "🚀 Discord-Like Chat App - Quick Start"
5
  echo "======================================"
6
  echo ""
7
 
8
- # Check Node.js
9
  if ! command -v node &> /dev/null; then
10
  echo "❌ Node.js is not installed. Please install from https://nodejs.org/"
11
  exit 1
@@ -15,7 +15,7 @@ echo "✅ Node.js: $(node --version)"
15
  echo "✅ npm: $(npm --version)"
16
  echo ""
17
 
18
- # Check MongoDB
19
  if ! command -v mongod &> /dev/null; then
20
  echo "⚠️ MongoDB not found in PATH"
21
  echo " Make sure MongoDB is running separately!"
 
1
  #!/bin/bash
2
+
3
 
4
  echo "🚀 Discord-Like Chat App - Quick Start"
5
  echo "======================================"
6
  echo ""
7
 
8
+
9
  if ! command -v node &> /dev/null; then
10
  echo "❌ Node.js is not installed. Please install from https://nodejs.org/"
11
  exit 1
 
15
  echo "✅ npm: $(npm --version)"
16
  echo ""
17
 
18
+
19
  if ! command -v mongod &> /dev/null; then
20
  echo "⚠️ MongoDB not found in PATH"
21
  echo " Make sure MongoDB is running separately!"
README.md CHANGED
@@ -164,7 +164,7 @@ New-NetFirewallRule -DisplayName "Chat App HTTPS" -Direction Inbound -LocalPort
164
  cd C:\Users\dell\Downloads\message
165
  taskkill /F /IM node.exe
166
 
167
- # Replace 192.168.56.1 with your actual LAN IP
168
  $cert = New-SelfSignedCertificate `
169
  -Subject "CN=localhost" `
170
  -CertStoreLocation "Cert:\CurrentUser\My" `
@@ -339,7 +339,7 @@ const HOST = process.env.HOST || '0.0.0.0';
339
  ### Adjust Message History
340
  Edit `server.js`:
341
  ```javascript
342
- const MAX_HISTORY = 50; // Change to desired number
343
  ```
344
 
345
  ## 📝 Features in Detail
 
164
  cd C:\Users\dell\Downloads\message
165
  taskkill /F /IM node.exe
166
 
167
+
168
  $cert = New-SelfSignedCertificate `
169
  -Subject "CN=localhost" `
170
  -CertStoreLocation "Cert:\CurrentUser\My" `
 
339
  ### Adjust Message History
340
  Edit `server.js`:
341
  ```javascript
342
+ const MAX_HISTORY = 50;
343
  ```
344
 
345
  ## 📝 Features in Detail
SETUP-GUIDE.md CHANGED
@@ -190,7 +190,7 @@ You can use either:
190
  ### JWT Secret
191
  Change the `JWT_SECRET` in `.env` to a secure random string for production:
192
  ```bash
193
- # Generate a secure key
194
  node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
195
  ```
196
 
 
190
  ### JWT Secret
191
  Change the `JWT_SECRET` in `.env` to a secure random string for production:
192
  ```bash
193
+
194
  node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
195
  ```
196
 
client.js CHANGED
@@ -1,4 +1,3 @@
1
- // ==================== GLOBAL STATE ====================
2
  let socket;
3
  let currentUser = null;
4
  let currentToken = null;
@@ -55,8 +54,7 @@ let themeSettings = {
55
  }
56
  };
57
 
58
- // ==================== SERVER CONFIGURATION ====================
59
- // Always use same-origin backend so it works on Railway, custom domains, LAN IPs, and localhost.
60
  const SERVER_URL = window.location.origin;
61
  const API_BASE = SERVER_URL + '/api';
62
 
@@ -76,7 +74,7 @@ async function syncBuildVersionBadge() {
76
  badge.title = `Running backend version: ${data.version}`;
77
  }
78
  } catch (error) {
79
- // Keep fallback badge text if version endpoint is unavailable.
80
  }
81
  }
82
 
@@ -126,7 +124,7 @@ function startIncomingCallAlert() {
126
  osc.start();
127
  osc.stop(incomingCallAudioContext.currentTime + 0.15);
128
  } catch (error) {
129
- // Browser may block sound until user gesture; visual prompt still appears.
130
  }
131
  }, 900);
132
  }
@@ -142,7 +140,7 @@ function stopIncomingCallAlert() {
142
  }
143
  }
144
 
145
- // ==================== AUTH FUNCTIONS ====================
146
 
147
  function toggleAuthMode() {
148
  isRegistering = !isRegistering;
@@ -335,7 +333,7 @@ function switchAccount() {
335
  setTimeout(() => location.reload(), 400);
336
  }
337
 
338
- // ==================== SOCKET.IO CONNECTION ====================
339
 
340
  function connectSocket() {
341
  socket = io(SERVER_URL, {
@@ -382,12 +380,12 @@ function connectSocket() {
382
 
383
  const isCurrentRoom = currentChatContext?.type === roomType && String(currentChatContext?.name) === String(roomName);
384
  if (isCurrentRoom) {
385
- // Only show if from someone else to avoid duplicates from optimistic updates
386
  if (String(payload.from) !== String(currentUser.id)) {
387
  addMessageToChat(payload);
388
  }
389
  } else if (payload.from !== currentUser.id && chatSettings.notifyOnReceived) {
390
- // Show notification for room message when not in that room
391
  const messagePreview = payload.mediaType === 'text' ? payload.content : `${payload.mediaType} message`;
392
  const roomLabel = roomType === 'community' ? 'Community' : 'Group';
393
  showToastClickable(
@@ -419,7 +417,7 @@ function connectSocket() {
419
  const fromUserId = payload?.fromUserId;
420
  if (!fromUserId) return;
421
 
422
- // If we're currently chatting with the user who cleared history, clear our UI too
423
  if (currentChatContext?.type === 'dm' && String(currentChatContext?.id) === String(fromUserId)) {
424
  document.getElementById('messages-container').innerHTML = '';
425
  refreshChatScrollbar();
@@ -439,14 +437,14 @@ function connectSocket() {
439
  );
440
 
441
  if (isCurrentChat) {
442
- // Only add to chat if we're the receiver (to avoid duplicates from optimistic updates)
443
  if (!isSender) {
444
  addMessageToChat(data);
445
  }
446
  }
447
 
448
  if (isSender && chatSettings.notifyOnSent) {
449
- // Don't show toast for sent messages (already optimistically shown)
450
  }
451
 
452
  if (!isSender && chatSettings.notifyOnReceived) {
@@ -468,17 +466,17 @@ function connectSocket() {
468
  });
469
 
470
  socket.on('dm user typing', (data) => {
471
- // Display typing indicator for DM
472
  showTypingIndicator(data.username);
473
  });
474
 
475
  socket.on('dm user stop typing', (data) => {
476
- // Hide typing indicator for DM
477
  hideTypingIndicator(data.username);
478
  });
479
 
480
  socket.on('community user typing', (data) => {
481
- // Display typing indicator for community chats
482
  if (currentChatContext.type === 'community' && currentChatContext.name === data.community) {
483
  showTypingIndicator(data.username);
484
  }
@@ -491,7 +489,7 @@ function connectSocket() {
491
  });
492
 
493
  socket.on('room user typing', (data) => {
494
- // Display typing indicator for room/group chats
495
  if ((currentChatContext.type === 'group' || currentChatContext.type === 'room') && String(currentChatContext.id) === String(data.roomId)) {
496
  showTypingIndicator(data.username);
497
  }
@@ -632,15 +630,14 @@ function hideTypingIndicator() {
632
  }
633
  }
634
 
635
- // ==================== UI FUNCTIONS ====================
636
 
637
  function showMainApp() {
638
  document.getElementById('authContainer').style.display = 'none';
639
  document.getElementById('mainApp').classList.remove('hidden');
640
 
641
- // AGGRESSIVELY close all panels on startup (especially for mobile)
642
  try {
643
- // Close profile panel - multiple methods to ensure it stays closed
644
  const profilePanel = document.getElementById('profilePanel');
645
  if (profilePanel) {
646
  profilePanel.classList.remove('show');
@@ -651,26 +648,26 @@ function showMainApp() {
651
  profilePanel.style.transform = 'translateX(500px)';
652
  }
653
 
654
- // Close settings panel
655
  const settingsPanel = document.getElementById('settingsPanel');
656
  if (settingsPanel) {
657
  settingsPanel.classList.remove('show');
658
  settingsPanel.style.right = '-500px';
659
  }
660
 
661
- // Close mobile members panel
662
  const mobileMembersPanel = document.getElementById('mobileMembersPanel');
663
  if (mobileMembersPanel) {
664
  mobileMembersPanel.classList.remove('show');
665
  }
666
 
667
- // Close sidebar overlay
668
  const sidebarOverlay = document.getElementById('sidebarOverlay');
669
  if (sidebarOverlay) {
670
  sidebarOverlay.classList.remove('show');
671
  }
672
 
673
- // Close channel sidebar on mobile
674
  const channelSidebar = document.getElementById('channelSidebar');
675
  if (channelSidebar) {
676
  channelSidebar.classList.remove('show');
@@ -681,13 +678,12 @@ function showMainApp() {
681
 
682
  loadUserProfile();
683
  loadShopItems();
684
-
685
- // Check if there's a friend request in the URL
686
  const urlParams = new URLSearchParams(window.location.search);
687
  const addFriendId = urlParams.get('addFriend');
688
  if (addFriendId && addFriendId !== currentUser.id) {
689
  sendFriendRequestFromLink(addFriendId);
690
- // Clean up the URL
691
  window.history.replaceState({}, document.title, window.location.pathname);
692
  }
693
  }
@@ -723,31 +719,31 @@ function openDM(friendId, friendName) {
723
  clearPendingMediaDraft();
724
  updatePinButtonState();
725
 
726
- // Check if chatting with self
727
  const isSelfChat = String(friendId) === String(currentUser.id);
728
  const displayName = isSelfChat ? `📝 ${currentUser.username} (Notes)` : `💬 ${friendName}`;
729
 
730
- // Update UI
731
  const chatTitleEl = document.getElementById('chatTitle');
732
  chatTitleEl.textContent = displayName;
733
  chatTitleEl.onclick = null;
734
  document.getElementById('messages-container').innerHTML = '';
735
  refreshChatScrollbar();
736
 
737
- // Show close chat button
738
  document.getElementById('closeChatBtn').style.display = 'block';
739
 
740
- // Load previous messages
741
  loadDMMessages(friendId);
742
 
743
- // Join DM room
744
  if (socket && socket.connected) {
745
  socket.emit('join dm', friendId);
746
  }
747
 
748
  showSection('chat');
749
 
750
- // Close mobile sidebar if open
751
  if (window.innerWidth <= 480) {
752
  closeMobileSidebar();
753
  }
@@ -760,7 +756,7 @@ async function loadDMMessages(friendId) {
760
  });
761
  const messages = await response.json();
762
 
763
- // Clear and load messages
764
  document.getElementById('messages-container').innerHTML = '';
765
  messages.forEach(msg => {
766
  addMessageToChat({
@@ -836,7 +832,7 @@ function closeShopModal() {
836
  showSection('chat');
837
  }
838
 
839
- // ==================== PROFILE FUNCTIONS ====================
840
 
841
  async function loadUserProfile() {
842
  try {
@@ -900,7 +896,7 @@ async function saveProfile() {
900
  }
901
  }
902
 
903
- // ==================== MESSAGING FUNCTIONS ====================
904
 
905
  function handleMessageKeypress(event) {
906
  if (event.key === 'Enter' && chatSettings.enterToSend) {
@@ -980,7 +976,7 @@ function renderPendingMediaDraft() {
980
  itemWrapper.appendChild(file);
981
  }
982
 
983
- // Remove button
984
  const removeBtn = document.createElement('button');
985
  removeBtn.innerHTML = '✕';
986
  removeBtn.style.position = 'absolute';
@@ -1149,7 +1145,7 @@ async function sendMessage() {
1149
  }
1150
 
1151
  try {
1152
- // Track message statistics
1153
  if (content) {
1154
  updateUserStats('messagesCount', 1);
1155
  updateUserStats('totalChars', content.length);
@@ -1196,7 +1192,7 @@ async function sendMessage() {
1196
  return;
1197
  }
1198
 
1199
- // Optimistically display message immediately
1200
  const optimisticMessage = {
1201
  id: Date.now(),
1202
  from: currentUser.id,
@@ -1209,7 +1205,7 @@ async function sendMessage() {
1209
  };
1210
  addMessageToChat(optimisticMessage);
1211
 
1212
- // Send to server without waiting
1213
  socket.emit('send dm', {
1214
  toUserId: targetId,
1215
  content: content,
@@ -1274,18 +1270,18 @@ function addMessageToChat(data) {
1274
  text.style.fontSize = '16px';
1275
  }
1276
 
1277
- // Handle stickers
1278
  if (data.mediaType === 'sticker') {
1279
  text.style.fontSize = '96px';
1280
  text.style.lineHeight = '1';
1281
  text.textContent = data.content;
1282
  } else {
1283
- // Check if message is only emojis
1284
  const emojiRegex = /^[\p{Emoji}\p{Emoji_Component}\s]+$/u;
1285
  const isOnlyEmojis = data.content && emojiRegex.test(data.content.trim());
1286
 
1287
  if (isOnlyEmojis && data.content.trim().length <= 20) {
1288
- // Enlarge emoji-only messages
1289
  text.style.fontSize = '64px';
1290
  text.style.lineHeight = '1.2';
1291
  }
@@ -1297,14 +1293,14 @@ function addMessageToChat(data) {
1297
  content.appendChild(text);
1298
  content.appendChild(time);
1299
 
1300
- // Add right-click context menu support
1301
  messageDiv.oncontextmenu = (e) => {
1302
  e.preventDefault();
1303
  showMessageContextMenu(e, messageDiv, data);
1304
  return false;
1305
  };
1306
 
1307
- // Add message actions (edit, delete, react)
1308
  const actions = document.createElement('div');
1309
  actions.className = 'message-actions';
1310
 
@@ -1361,7 +1357,7 @@ function addMessageToChat(data) {
1361
 
1362
  content.appendChild(actions);
1363
 
1364
- // Reactions container
1365
  const reactionsDiv = document.createElement('div');
1366
  reactionsDiv.className = 'message-reactions';
1367
  reactionsDiv.dataset.messageId = messageDiv.dataset.messageId;
@@ -1418,7 +1414,7 @@ function addMessageToChat(data) {
1418
  audio.src = data.mediaUrl;
1419
  audio.controls = true;
1420
  audio.style.maxWidth = '300px';
1421
- // Apply speaker output device
1422
  if (audioSettings.speakerId && audioSettings.speakerId !== 'default' && audio.setSinkId) {
1423
  audio.setSinkId(audioSettings.speakerId).catch(err => {
1424
  console.warn('Could not set audio output device:', err);
@@ -1433,7 +1429,7 @@ function addMessageToChat(data) {
1433
  messageDiv.appendChild(content);
1434
  container.appendChild(messageDiv);
1435
 
1436
- // Scroll to the latest message smoothly
1437
  setTimeout(() => {
1438
  messageDiv.scrollIntoView({ behavior: 'smooth', block: 'end' });
1439
  refreshChatScrollbar();
@@ -1476,7 +1472,7 @@ function updateMembersList(users) {
1476
  item.style.cursor = 'pointer';
1477
  item.title = `Click to view ${user.username}'s profile`;
1478
 
1479
- // Create avatar
1480
  const avatar = document.createElement('div');
1481
  avatar.className = 'member-avatar';
1482
  if (user.avatar) {
@@ -1493,11 +1489,11 @@ function updateMembersList(users) {
1493
  const name = document.createElement('span');
1494
  name.textContent = user.username || 'Unknown';
1495
 
1496
- // Get correct user ID (handle different property names)
1497
  const userId = user.id || user._id || user.userId;
1498
  const username = user.username || 'User';
1499
 
1500
- // Make item clickable to view profile
1501
  item.onclick = () => {
1502
  if (!userId) {
1503
  showToast('❌ User ID not available', 'error');
@@ -1516,14 +1512,14 @@ function updateMembersList(users) {
1516
  membersList.appendChild(item);
1517
  });
1518
 
1519
- // Also update mobile members list
1520
  const mobileMembersList = document.getElementById('mobileMembersList');
1521
  if (mobileMembersList) {
1522
  mobileMembersList.innerHTML = membersList.innerHTML;
1523
  }
1524
  }
1525
 
1526
- // ==================== FILE UPLOAD ====================
1527
 
1528
  async function handleFileUpload(event) {
1529
  const files = Array.from(event.target.files || []);
@@ -1542,8 +1538,7 @@ async function handleFileUpload(event) {
1542
  let sentCount = 0;
1543
 
1544
  for (const file of files) {
1545
- // Read and send each media file immediately after selection.
1546
- // This removes the extra "press send" step and feels instant like WhatsApp.
1547
  const payload = await new Promise((resolve) => {
1548
  const reader = new FileReader();
1549
  reader.onload = (e) => {
@@ -1574,7 +1569,7 @@ async function handleFileUpload(event) {
1574
  showToast(`Sent ${sentCount} media file(s)`, 'success');
1575
  }
1576
 
1577
- // Reset the input
1578
  event.target.value = '';
1579
  }
1580
 
@@ -1610,11 +1605,11 @@ async function uploadProfileImage(event) {
1610
  };
1611
  reader.readAsDataURL(file);
1612
 
1613
- // Reset the input
1614
  event.target.value = '';
1615
  }
1616
 
1617
- // ==================== FRIENDS FUNCTIONS ====================
1618
 
1619
  async function loadFriendsForDM() {
1620
  try {
@@ -1626,7 +1621,7 @@ async function loadFriendsForDM() {
1626
  const channelsList = document.getElementById('channelsList');
1627
  channelsList.innerHTML = '';
1628
 
1629
- // Separate pinned and unpinned friends
1630
  const pinnedFriends = [];
1631
  const unpinnedFriends = [];
1632
 
@@ -1639,7 +1634,7 @@ async function loadFriendsForDM() {
1639
  }
1640
  });
1641
 
1642
- // Show pinned section if there are pinned chats
1643
  if (pinnedFriends.length > 0) {
1644
  const pinnedTitle = document.createElement('div');
1645
  pinnedTitle.className = 'channel-section-title';
@@ -1651,7 +1646,6 @@ async function loadFriendsForDM() {
1651
  });
1652
  }
1653
 
1654
- // Show communities if any
1655
  if (joinedCommunities.length > 0) {
1656
  const communitiesTitle = document.createElement('div');
1657
  communitiesTitle.className = 'channel-section-title';
@@ -1680,7 +1674,7 @@ async function loadFriendsForDM() {
1680
  });
1681
  }
1682
 
1683
- // Show groups if any
1684
  if (joinedGroups.length > 0) {
1685
  const groupsTitle = document.createElement('div');
1686
  groupsTitle.className = 'channel-section-title';
@@ -1714,7 +1708,7 @@ async function loadFriendsForDM() {
1714
  directTitle.textContent = 'DIRECT MESSAGES';
1715
  channelsList.appendChild(directTitle);
1716
 
1717
- // Add self-chat option with avatar
1718
  const selfItem = document.createElement('div');
1719
  selfItem.className = 'channel-item';
1720
 
@@ -1762,7 +1756,7 @@ function createFriendChannelItem(friend, container, isPinned) {
1762
  item.classList.add('pinned');
1763
  }
1764
 
1765
- // Create avatar
1766
  const avatar = document.createElement('div');
1767
  avatar.className = 'channel-avatar';
1768
  if (friend.avatar) {
@@ -1773,7 +1767,7 @@ function createFriendChannelItem(friend, container, isPinned) {
1773
  avatar.textContent = friend.username.charAt(0).toUpperCase();
1774
  }
1775
 
1776
- // Create text with status
1777
  const text = document.createElement('span');
1778
  const status = friend.status === 'online' ? '🟢' : '⚫';
1779
  text.textContent = `${status} ${friend.username}`;
@@ -1791,7 +1785,7 @@ function createFriendChannelItem(friend, container, isPinned) {
1791
  item.style.cursor = 'pointer';
1792
  item.onclick = () => openDM(friend.id, friend.username);
1793
 
1794
- // Add right-click context menu for profile
1795
  item.oncontextmenu = (e) => {
1796
  e.preventDefault();
1797
  showFriendContextMenu(e, friend.id, friend.username);
@@ -1925,7 +1919,7 @@ async function sendFriendRequestFromLink(friendId) {
1925
  }
1926
  }
1927
 
1928
- // ==================== SHOP FUNCTIONS ====================
1929
 
1930
  async function loadShopItems() {
1931
  try {
@@ -1998,7 +1992,7 @@ async function buyItem(itemId) {
1998
  }
1999
  }
2000
 
2001
- // ==================== INVENTORY FUNCTIONS ====================
2002
 
2003
  async function loadInventory() {
2004
  try {
@@ -2043,7 +2037,7 @@ function applyShopItem(itemId, category) {
2043
  },
2044
  'Badges': () => {
2045
  showToast('🏆 Badge equipped! It now appears next to your name.', 'success');
2046
- // Badge would be stored in user profile and shown in messages
2047
  },
2048
  'Themes': () => {
2049
  const colors = ['#ff6b9d', '#c44569', '#4a69bd', '#6a89cc', '#60a3bc', '#78e08f', '#f6b93b', '#e55039'];
@@ -2053,7 +2047,7 @@ function applyShopItem(itemId, category) {
2053
  showToast('🎨 Theme applied! Your accent color has changed.', 'success');
2054
  },
2055
  'Effects': () => {
2056
- // Message glow effect
2057
  const style = document.createElement('style');
2058
  style.id = 'message-glow-effect';
2059
  style.textContent = `
@@ -2119,7 +2113,6 @@ function applyShopItem(itemId, category) {
2119
  }
2120
 
2121
 
2122
- // ==================== TOAST NOTIFICATIONS ====================
2123
 
2124
  function showToast(message, type = 'success') {
2125
  const container = document.getElementById('toastContainer');
@@ -2144,7 +2137,7 @@ function showToast(message, type = 'success') {
2144
  }, 3000);
2145
  }
2146
 
2147
- // ==================== SETTINGS PANEL ====================
2148
 
2149
  function openSettings() {
2150
  document.getElementById('settingsPanel').classList.add('show');
@@ -2165,13 +2158,13 @@ function closeSettings() {
2165
  }
2166
 
2167
  function switchSettingsTab(tab) {
2168
- // Update nav items
2169
  document.querySelectorAll('.settings-nav-item').forEach(item => {
2170
  item.classList.remove('active');
2171
  });
2172
  event.target.classList.add('active');
2173
 
2174
- // Update sections
2175
  document.querySelectorAll('.settings-section').forEach(section => {
2176
  section.classList.remove('active');
2177
  });
@@ -2211,7 +2204,7 @@ async function updateUsername() {
2211
  }
2212
  }
2213
 
2214
- // ==================== THEME SWITCHING ====================
2215
 
2216
  function changeTheme(theme) {
2217
  document.documentElement.setAttribute('data-theme', theme);
@@ -2228,7 +2221,7 @@ function loadTheme() {
2228
  }
2229
  }
2230
 
2231
- // ==================== CLOSE CHAT ====================
2232
 
2233
  function closeCurrentChat() {
2234
  if (activeRoomSubscription && socket && socket.connected) {
@@ -2318,7 +2311,7 @@ function sendRoomMessage(payload) {
2318
  return;
2319
  }
2320
 
2321
- // Optimistically display message immediately
2322
  const optimisticMessage = {
2323
  id: Date.now(),
2324
  from: currentUser.id,
@@ -2357,7 +2350,7 @@ function ensureChatTarget() {
2357
  return null;
2358
  }
2359
 
2360
- // ==================== DESKTOP NOTIFICATIONS ====================
2361
 
2362
  function requestNotificationPermission() {
2363
  if ('Notification' in window && Notification.permission === 'default') {
@@ -2458,14 +2451,14 @@ function openCommunitiesModal() {
2458
  const modal = document.getElementById('communitiesModal');
2459
  const list = document.getElementById('communitiesList');
2460
 
2461
- // Show communities with better styling
2462
  const communities = [
2463
- { name: 'Gaming', emoji: '🎮', description: 'Talk about your favorite games' },
2464
- { name: 'Coding', emoji: '💻', description: 'Share code and learn together' },
2465
- { name: 'Music', emoji: '🎵', description: 'Discuss and share music' },
2466
- { name: 'Art', emoji: '🎨', description: 'Share your creative works' },
2467
- { name: 'Sports', emoji: '⚽', description: 'Sports fans unite' },
2468
- { name: 'Movies', emoji: '🎬', description: 'Film enthusiasts' }
2469
  ];
2470
 
2471
  list.innerHTML = communities.map(c => {
@@ -2475,7 +2468,7 @@ function openCommunitiesModal() {
2475
  <div class="friend-item" style="flex-direction: column; align-items: flex-start; padding: 16px;">
2476
  <div style="display: flex; width: 100%; justify-content: space-between; align-items: center;">
2477
  <div>
2478
- <span class="friend-name" style="font-size: 16px;">${profile.icon || c.emoji} ${c.name}</span>
2479
  <div style="font-size: 12px; color: var(--text-secondary); margin-top: 4px;">${c.description}</div>
2480
  </div>
2481
  <div style="display:flex; gap:6px;">
@@ -2502,7 +2495,7 @@ function openGroupsModal() {
2502
  const modal = document.getElementById('groupsModal');
2503
  const list = document.getElementById('groupsList');
2504
 
2505
- // Show groups with better styling
2506
  const groups = [
2507
  { name: 'Study Group', emoji: '📚', description: 'Study together and share notes' },
2508
  { name: 'Project Team', emoji: '💼', description: 'Collaborate on projects' },
@@ -2571,7 +2564,7 @@ function openCommunityChat(name) {
2571
  document.getElementById('closeChatBtn').style.display = 'block';
2572
  showSection('chat');
2573
 
2574
- // Close mobile sidebar if open
2575
  if (window.innerWidth <= 480) {
2576
  closeMobileSidebar();
2577
  }
@@ -2593,7 +2586,7 @@ function openGroupChat(name) {
2593
  document.getElementById('closeChatBtn').style.display = 'block';
2594
  showSection('chat');
2595
 
2596
- // Close mobile sidebar if open
2597
  if (window.innerWidth <= 480) {
2598
  closeMobileSidebar();
2599
  }
@@ -2636,7 +2629,7 @@ function joinGroup(name) {
2636
  showToast(`Joined ${name} group!`);
2637
  }
2638
 
2639
- // ==================== STICKER & GIF PICKER ====================
2640
 
2641
  const stickers = [
2642
  '😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '🙃',
@@ -2684,7 +2677,7 @@ function loadPickerContent() {
2684
  content.appendChild(item);
2685
  });
2686
  } else if (currentPickerTab === 'gifs') {
2687
- // Expanded GIF library with popular reactions and animations
2688
  const gifs = [
2689
  'https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif',
2690
  'https://media.giphy.com/media/l0MYt5jPR6QX5pnqM/giphy.gif',
@@ -2723,7 +2716,7 @@ function loadPickerContent() {
2723
  }
2724
 
2725
  function sendSticker(sticker) {
2726
- // Insert emoji into text input instead of staging
2727
  const messageInput = document.getElementById('messageInput');
2728
  if (messageInput) {
2729
  const currentText = messageInput.value;
@@ -2745,7 +2738,7 @@ function sendGif(gifUrl) {
2745
  document.getElementById('pickerPanel').classList.remove('show');
2746
  }
2747
 
2748
- // ==================== VOICE RECORDING ====================
2749
 
2750
  let mediaRecorder;
2751
  let audioChunks = [];
@@ -2786,14 +2779,14 @@ async function toggleRecording() {
2786
 
2787
  if (!mediaRecorder || mediaRecorder.state === 'inactive') {
2788
  try {
2789
- // Use selected microphone
2790
  const stream = await getSelectedMicrophone();
2791
  const mimeType = getSupportedAudioMimeType();
2792
  recordedAudioMimeType = mimeType || 'audio/webm';
2793
  mediaRecorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
2794
  audioChunks = [];
2795
 
2796
- // Hide preview when starting new recording
2797
  if (previewContainer) previewContainer.style.display = 'none';
2798
  if (previewAudio) {
2799
  previewAudio.pause();
@@ -2813,22 +2806,22 @@ async function toggleRecording() {
2813
  showToast('🎧 Recording ready! CLICK PLAY BUTTON to hear it before sending!', 'success');
2814
  if (sendBtn) sendBtn.disabled = false;
2815
 
2816
- // Show preview container and load audio
2817
  if (previewContainer) previewContainer.style.display = 'block';
2818
  if (previewAudio) {
2819
  previewAudio.src = reader.result;
2820
  previewAudio.load();
2821
- // Set volume to max for better audibility
2822
  previewAudio.volume = 1.0;
2823
 
2824
- // Apply speaker output device
2825
  if (audioSettings.speakerId && audioSettings.speakerId !== 'default' && previewAudio.setSinkId) {
2826
  previewAudio.setSinkId(audioSettings.speakerId).catch(err => {
2827
  console.warn('Could not set preview audio output:', err);
2828
  });
2829
  }
2830
 
2831
- // Try to play automatically (may be blocked by browser)
2832
  previewAudio.play().catch(err => {
2833
  console.log('Autoplay blocked, user must click play:', err);
2834
  showToast('⚠️ Click the PLAY button ▶️ below to hear your recording!', 'warning');
@@ -2926,7 +2919,7 @@ function cancelRecording() {
2926
  window.recordedAudio = null;
2927
  recordingSeconds = 0;
2928
 
2929
- // Hide preview container
2930
  if (previewContainer) previewContainer.style.display = 'none';
2931
  if (previewAudio) {
2932
  previewAudio.pause();
@@ -2936,23 +2929,22 @@ function cancelRecording() {
2936
  if (sendBtn) sendBtn.disabled = true;
2937
  }
2938
 
2939
- // ==================== SMOOTH SCROLLBAR WHEEL ====================
2940
 
2941
  function handleChatWheel(event) {
2942
  const container = document.getElementById('messages-container');
2943
  if (!container) return;
2944
 
2945
- // Allow smooth wheel scrolling with accelerated movement
2946
  event.preventDefault();
2947
  const delta = event.deltaY;
2948
  container.scrollBy({ top: delta * 0.8, behavior: 'auto' });
2949
  }
2950
 
2951
- // ==================== AUDIO DEVICE SETTINGS ====================
2952
 
2953
  async function refreshAudioDevices() {
2954
  try {
2955
- // Request permissions first
2956
  await navigator.mediaDevices.getUserMedia({ audio: true, video: false })
2957
  .then(stream => stream.getTracks().forEach(track => track.stop()))
2958
  .catch(() => console.log('Microphone permission needed'));
@@ -3003,7 +2995,7 @@ function saveAudioSettings() {
3003
 
3004
  localStorage.setItem('audioSettings', JSON.stringify(audioSettings));
3005
 
3006
- // Apply speaker to all existing audio elements
3007
  applySpeakerToAudioElements();
3008
 
3009
  showToast('Audio settings saved', 'success');
@@ -3013,7 +3005,7 @@ function applySpeakerToAudioElements() {
3013
  const speakerId = audioSettings.speakerId;
3014
  if (!speakerId || speakerId === 'default') return;
3015
 
3016
- // Apply to all audio elements
3017
  document.querySelectorAll('audio').forEach(audio => {
3018
  if (audio.setSinkId) {
3019
  audio.setSinkId(speakerId).catch(err => {
@@ -3022,7 +3014,7 @@ function applySpeakerToAudioElements() {
3022
  }
3023
  });
3024
 
3025
- // Also apply to voice preview
3026
  const voicePreview = document.getElementById('voicePreview');
3027
  if (voicePreview && voicePreview.setSinkId) {
3028
  voicePreview.setSinkId(speakerId).catch(err => {
@@ -3052,7 +3044,7 @@ async function getSelectedMicrophone() {
3052
  });
3053
  }
3054
 
3055
- // ==================== THEME & COLOR CUSTOMIZATION ====================
3056
 
3057
  function changeTheme(theme) {
3058
  themeSettings.mode = theme;
@@ -3101,10 +3093,10 @@ function applySimpleCustomColors() {
3101
  root.style.setProperty('--primary-color', icon);
3102
  root.style.setProperty('--secondary-color', icon);
3103
  root.style.setProperty('--icon-color', icon);
3104
- // Keep UI text color fixed (don't change with custom colors)
3105
  root.style.setProperty('--ui-text-color', '#ffffff');
3106
 
3107
- // Save simple colors to localStorage
3108
  const simpleTheme = { bg, text, icon, mode: 'custom' };
3109
  localStorage.setItem('simpleTheme', JSON.stringify(simpleTheme));
3110
  }
@@ -3200,17 +3192,17 @@ function loadThemeSettings() {
3200
  }
3201
  }
3202
 
3203
- // ==================== MESSAGE ACTIONS (EDIT, DELETE, REACT) ====================
3204
 
3205
- let messageReactions = {}; // Store reactions: { messageId: { emoji: [userIds] } }
 
3206
 
3207
  function showQuickReactions(messageId, messageDiv) {
3208
  const quickReactions = ['👍', '❤️', '😂', '😮', '😢', '🙏'];
3209
 
3210
- // Remove any existing picker
3211
  document.querySelectorAll('.reaction-picker').forEach(p => p.remove());
3212
 
3213
- // Create quick reaction picker
3214
  const picker = document.createElement('div');
3215
  picker.className = 'reaction-picker';
3216
  picker.style.position = 'fixed';
@@ -3223,7 +3215,7 @@ function showQuickReactions(messageId, messageDiv) {
3223
  picker.style.zIndex = '9999';
3224
  picker.style.boxShadow = '0 8px 24px rgba(0,0,0,0.6)';
3225
 
3226
- // Position near the message (center of screen)
3227
  const rect = messageDiv.getBoundingClientRect();
3228
  picker.style.left = Math.max(20, rect.left) + 'px';
3229
  picker.style.top = Math.max(20, rect.top - 60) + 'px';
@@ -3256,7 +3248,7 @@ function showQuickReactions(messageId, messageDiv) {
3256
 
3257
  document.body.appendChild(picker);
3258
 
3259
- // Remove picker when clicking outside
3260
  const closePickerHandler = (e) => {
3261
  if (!picker.contains(e.target)) {
3262
  picker.remove();
@@ -3282,13 +3274,13 @@ function addReaction(messageId, emoji) {
3282
  const userIndex = messageReactions[messageId][emoji].indexOf(userId);
3283
 
3284
  if (userIndex > -1) {
3285
- // Remove reaction if already added
3286
  messageReactions[messageId][emoji].splice(userIndex, 1);
3287
  if (messageReactions[messageId][emoji].length === 0) {
3288
  delete messageReactions[messageId][emoji];
3289
  }
3290
  } else {
3291
- // Add reaction
3292
  messageReactions[messageId][emoji].push(userId);
3293
  updateUserStats('reactionsGiven', 1);
3294
  }
@@ -3299,7 +3291,7 @@ function addReaction(messageId, emoji) {
3299
  }
3300
 
3301
  function showMessageContextMenu(e, messageDiv, data) {
3302
- // Remove any existing context menu
3303
  document.querySelectorAll('.message-context-menu').forEach(m => m.remove());
3304
 
3305
  const menu = document.createElement('div');
@@ -3337,7 +3329,7 @@ function showMessageContextMenu(e, messageDiv, data) {
3337
  btn.style.fontWeight = '500';
3338
  btn.style.borderBottom = '1px solid var(--border-color)';
3339
 
3340
- // Add hover class for color coding
3341
  btn.onmouseover = () => {
3342
  if (item.hoverClass === 'danger-hover') {
3343
  btn.style.background = 'var(--danger)';
@@ -3365,18 +3357,18 @@ function showMessageContextMenu(e, messageDiv, data) {
3365
  menu.appendChild(btn);
3366
  });
3367
 
3368
- // Remove border from last item
3369
  const lastBtn = menu.lastElementChild;
3370
  if (lastBtn) lastBtn.style.borderBottom = 'none';
3371
 
3372
  document.body.appendChild(menu);
3373
 
3374
- // Close menu when clicking elsewhere
3375
  document.addEventListener('click', () => menu.remove(), { once: true });
3376
  }
3377
 
3378
  function showFriendContextMenu(e, friendId, friendName) {
3379
- // Remove any existing context menu
3380
  document.querySelectorAll('.message-context-menu').forEach(m => m.remove());
3381
 
3382
  const menu = document.createElement('div');
@@ -3408,7 +3400,7 @@ function showFriendContextMenu(e, friendId, friendName) {
3408
  btn.style.fontWeight = '500';
3409
  btn.style.borderBottom = '1px solid var(--border-color)';
3410
 
3411
- // Add hover class for color coding
3412
  btn.onmouseover = () => {
3413
  if (item.hoverClass === 'danger-hover') {
3414
  btn.style.background = 'var(--danger)';
@@ -3436,13 +3428,13 @@ function showFriendContextMenu(e, friendId, friendName) {
3436
  menu.appendChild(btn);
3437
  });
3438
 
3439
- // Remove border from last item
3440
  const lastBtn = menu.lastElementChild;
3441
  if (lastBtn) lastBtn.style.borderBottom = 'none';
3442
 
3443
  document.body.appendChild(menu);
3444
 
3445
- // Close menu when clicking elsewhere
3446
  document.addEventListener('click', () => menu.remove(), { once: true });
3447
  }
3448
 
@@ -3498,19 +3490,19 @@ function copyMessageText(messageId) {
3498
  } else if (imgElement || vidElement || audioElement) {
3499
  const url = imgElement?.src || vidElement?.src || audioElement?.src || '';
3500
  if (url) {
3501
- // Add to media preview panel
3502
  if (imgElement) {
3503
  addToMediaPreview('image', url);
3504
  } else if (vidElement) {
3505
  addToMediaPreview('video', url);
3506
  }
3507
 
3508
- // Copy to clipboard
3509
  navigator.clipboard.writeText(url).then(() => {
3510
  const mediaType = imgElement ? 'Image' : vidElement ? 'Video' : 'Audio';
3511
  showToast(`✅ ${mediaType} copied! View in preview panel.`, 'success');
3512
 
3513
- // Show preview panel if it has items
3514
  if (copiedMediaItems.length > 0) {
3515
  setTimeout(() => openMediaPreview(), 500);
3516
  }
@@ -3542,8 +3534,7 @@ function editMessage(messageId, textElement) {
3542
 
3543
  showToast('✅ Message edited', 'success');
3544
 
3545
- // TODO: Emit to server if needed
3546
- // socket.emit('edit message', { messageId, newText });
3547
  }
3548
  });
3549
  }
@@ -3556,24 +3547,23 @@ function deleteMessage(messageId, messageDiv) {
3556
  true
3557
  ).then(confirmed => {
3558
  if (confirmed) {
3559
- // Add fade-out animation
3560
  messageDiv.style.transition = 'all 0.3s ease-out';
3561
  messageDiv.style.opacity = '0';
3562
  messageDiv.style.transform = 'translateX(-100%)';
3563
 
3564
- // Remove from DOM after animation
3565
  setTimeout(() => {
3566
  messageDiv.remove();
3567
  showToast('✅ Message deleted', 'success');
3568
  }, 300);
3569
 
3570
- // TODO: Emit to server to delete from database
3571
- // socket.emit('delete message', { messageId });
3572
  }
3573
  });
3574
  }
3575
 
3576
- // ==================== GROUP CREATION & MANAGEMENT ====================
3577
 
3578
  function createNewGroup() {
3579
  customPrompt('Enter a name for your new group:', '👥 Create Group', 'My Awesome Group', '', '👥').then(groupName => {
@@ -3596,7 +3586,7 @@ function createNewGroup() {
3596
 
3597
  const ids = memberIds.split(',').map(id => id.trim()).filter(Boolean);
3598
  showToast(`✅ Group "${groupName}" created with ${ids.length} members!`, 'success');
3599
- // TODO: socket.emit('create group', { groupName, memberIds: ids });
3600
  });
3601
  });
3602
  }
@@ -3629,7 +3619,7 @@ function changeGroupPhoto() {
3629
  input.click();
3630
  }
3631
 
3632
- // ==================== VIDEO CALLS ====================
3633
 
3634
  function startVideoCall() {
3635
  if (!currentChatContext || currentChatContext.type === 'none' || !currentChatContext.id) {
@@ -3646,11 +3636,11 @@ function startVideoCall() {
3646
  const targetId = currentChatContext.id;
3647
  const callType = 'Video Call';
3648
 
3649
- // Open video call modal
3650
  openVideoCallModal(targetName, targetId, callType);
3651
  }
3652
 
3653
- // ==================== ARCHIVE CHATS ====================
3654
 
3655
  let archivedChats = JSON.parse(localStorage.getItem('archivedChats') || '[]');
3656
 
@@ -3667,14 +3657,14 @@ function toggleArchiveChat() {
3667
  const archiveIndex = archivedChats.findIndex(c => c.id === chatId && c.type === chatType);
3668
 
3669
  if (archiveIndex > -1) {
3670
- // Unarchive
3671
  archivedChats.splice(archiveIndex, 1);
3672
  showToast(`${chatName} unarchived`, 'success');
3673
  } else {
3674
- // Archive
3675
  archivedChats.push({ id: chatId, name: chatName, type: chatType });
3676
  showToast(`${chatName} archived`, 'success');
3677
- // PERMANENTLY REMOVE from chat list to prevent return
3678
  if (chatType === 'dm') {
3679
  const friendIndex = friends.findIndex(f => f.id === chatId);
3680
  if (friendIndex > -1) {
@@ -3686,7 +3676,7 @@ function toggleArchiveChat() {
3686
  }
3687
 
3688
  localStorage.setItem('archivedChats', JSON.stringify(archivedChats));
3689
- loadFriendsForDM(); // Refresh list to hide/show archived chats
3690
  }
3691
 
3692
  function showArchivedChats() {
@@ -3746,11 +3736,11 @@ function unarchiveChat(type, id) {
3746
  showToast(`${chat.name} unarchived`, 'success');
3747
  loadFriendsForDM();
3748
  closeCustomDialog();
3749
- showArchivedChats(); // Refresh the archived chats modal
3750
  }
3751
  }
3752
 
3753
- // ==================== STATISTICS & QUIZZES ====================
3754
 
3755
  let userStats = JSON.parse(localStorage.getItem('userStats') || '{"messagesCount": 0, "totalChars": 0, "reactionsGiven": 0, "archiveCount": 0, "quizCorrect": 0}');
3756
 
@@ -3882,9 +3872,9 @@ function resetUserStats() {
3882
  });
3883
  }
3884
 
3885
- // ==================== MOBILE NAVIGATION ====================
3886
  function showMobileSection(section) {
3887
- // Keep one panel visible at a time on phones.
3888
  if (window.innerWidth <= 768) {
3889
  closeMobileSidebar();
3890
  closeSettings();
@@ -3893,7 +3883,7 @@ function showMobileSection(section) {
3893
  closeGroupsModal();
3894
  }
3895
 
3896
- // Update active state on nav items
3897
  document.querySelectorAll('.mobile-nav-item').forEach(item => {
3898
  if (item.getAttribute('data-section') === section) {
3899
  item.classList.add('active');
@@ -3902,7 +3892,7 @@ function showMobileSection(section) {
3902
  }
3903
  });
3904
 
3905
- // Handle different sections
3906
  switch(section) {
3907
  case 'chats':
3908
  toggleChannelSidebar();
@@ -3986,7 +3976,7 @@ function updateMobileMembersList() {
3986
  const desktopList = document.getElementById('membersList');
3987
  if (!mobileList || !desktopList) return;
3988
 
3989
- // Copy the desktop members list to mobile
3990
  mobileList.innerHTML = desktopList.innerHTML;
3991
  }
3992
 
@@ -3994,7 +3984,7 @@ function getCommunityProfile(name) {
3994
  if (!communityProfiles[name]) {
3995
  communityProfiles[name] = {
3996
  name,
3997
- icon: '🌐',
3998
  image: '',
3999
  members: [
4000
  {
@@ -4117,7 +4107,7 @@ function setCommunityIcon(name, icon) {
4117
  if (currentChatContext.type === 'community' && currentChatContext.name === name) {
4118
  document.getElementById('chatTitle').textContent = `${profile.icon} Community: ${name}`;
4119
  }
4120
- loadFriendsForDM(); // Refresh chat list to show new icon
4121
  showToast('Community icon updated! 🎉', 'success');
4122
  }
4123
 
@@ -4145,7 +4135,7 @@ function changeCommunityImagePrompt(name) {
4145
  communityProfiles[name] = profile;
4146
  localStorage.setItem('communityProfiles', JSON.stringify(communityProfiles));
4147
 
4148
- // IMMEDIATELY update profile panel
4149
  if (currentChatContext?.type === 'community' && currentChatContext?.name === name) {
4150
  const profileAvatar = document.getElementById('profileAvatar');
4151
  if (profileAvatar) {
@@ -4158,7 +4148,7 @@ function changeCommunityImagePrompt(name) {
4158
  if (profileName) {
4159
  profileName.textContent = name;
4160
  }
4161
- // Force re-render profile to ensure image persists
4162
  setTimeout(() => {
4163
  const avatarDiv = document.getElementById('profileAvatar');
4164
  if (avatarDiv && !avatarDiv.style.backgroundImage) {
@@ -4191,7 +4181,7 @@ function changeGroupImagePrompt(name) {
4191
  groupProfiles[name] = profile;
4192
  localStorage.setItem('groupProfiles', JSON.stringify(groupProfiles));
4193
 
4194
- // IMMEDIATELY update profile panel
4195
  if (currentChatContext?.type === 'group' && currentChatContext?.name === name) {
4196
  const profileAvatar = document.getElementById('profileAvatar');
4197
  if (profileAvatar) {
@@ -4204,7 +4194,7 @@ function changeGroupImagePrompt(name) {
4204
  if (profileName) {
4205
  profileName.textContent = name;
4206
  }
4207
- // Force re-render profile to ensure image persists
4208
  setTimeout(() => {
4209
  const avatarDiv = document.getElementById('profileAvatar');
4210
  if (avatarDiv && !avatarDiv.style.backgroundImage) {
@@ -4222,7 +4212,7 @@ function changeGroupImagePrompt(name) {
4222
  input.click();
4223
  }
4224
 
4225
- // Update toggleChannelSidebar to work with overlay
4226
  function toggleChannelSidebarEnhanced() {
4227
  const sidebar = document.getElementById('channelSidebar');
4228
  const overlay = document.getElementById('sidebarOverlay');
@@ -4231,7 +4221,7 @@ function toggleChannelSidebarEnhanced() {
4231
  overlay?.classList.toggle('show');
4232
  }
4233
 
4234
- // ==================== PINNED CHATS ====================
4235
  function togglePinCurrentChat() {
4236
  if (!currentChatContext || currentChatContext.type === 'none') {
4237
  showToast('No chat selected', 'warning');
@@ -4242,18 +4232,18 @@ function togglePinCurrentChat() {
4242
  const index = pinnedChats.indexOf(chatId);
4243
 
4244
  if (index > -1) {
4245
- // Unpin
4246
  pinnedChats.splice(index, 1);
4247
  showToast('Chat unpinned', 'success');
4248
  } else {
4249
- // Pin
4250
  pinnedChats.push(chatId);
4251
  showToast('Chat pinned to top', 'success');
4252
  }
4253
 
4254
  localStorage.setItem('pinnedChats', JSON.stringify(pinnedChats));
4255
  updatePinButtonState();
4256
- loadFriendsForDM(); // Refresh the chat list to show pinned at top
4257
  }
4258
 
4259
  function updatePinButtonState() {
@@ -4272,7 +4262,7 @@ function isChatPinned(chatType, chatId) {
4272
  return pinnedChats.includes(chatKey);
4273
  }
4274
 
4275
- // ==================== CLICKABLE NOTIFICATIONS ====================
4276
  function showToastClickable(message, type = 'success', chatContext = null) {
4277
  const container = document.getElementById('toastContainer');
4278
  if (!container) return;
@@ -4299,7 +4289,7 @@ function showToastClickable(message, type = 'success', chatContext = null) {
4299
  if (chatContext) {
4300
  toast.style.cursor = 'pointer';
4301
  toast.addEventListener('click', () => {
4302
- // Navigate to chat
4303
  if (chatContext.type === 'dm') {
4304
  openDM(chatContext.friendId, chatContext.friendName);
4305
  } else if (chatContext.type === 'community') {
@@ -4308,10 +4298,10 @@ function showToastClickable(message, type = 'success', chatContext = null) {
4308
  openRoomChat('group', chatContext.name);
4309
  }
4310
 
4311
- // Remove the notification
4312
  toast.remove();
4313
 
4314
- // Close any open modals/sidebars on mobile
4315
  if (window.innerWidth <= 480) {
4316
  closeMobileSidebar();
4317
  }
@@ -4325,7 +4315,7 @@ function showToastClickable(message, type = 'success', chatContext = null) {
4325
  }, 5000);
4326
  }
4327
 
4328
- // ==================== INITIALIZATION ====================
4329
 
4330
  window.addEventListener('DOMContentLoaded', () => {
4331
  loadTheme();
@@ -4341,14 +4331,14 @@ window.addEventListener('DOMContentLoaded', () => {
4341
  document.getElementById('autoplayGifs')?.addEventListener('change', saveChatSettings);
4342
  document.getElementById('compactMode')?.addEventListener('change', saveAppearanceSettings);
4343
 
4344
- // Add typing notification support for communities and rooms
4345
  let typingTimeout = null;
4346
  const typingInput = document.getElementById('messageInput');
4347
  if (typingInput) {
4348
  typingInput.addEventListener('input', () => {
4349
  if (!chatSettings.showTypingIndicator) return;
4350
 
4351
- // Emit typing event for current chat context
4352
  if (currentChatContext.type === 'community' && currentChatContext.name) {
4353
  socket?.emit('community typing', { community: currentChatContext.name, username: currentUser?.username });
4354
  clearTimeout(typingTimeout);
@@ -4391,13 +4381,13 @@ window.addEventListener('DOMContentLoaded', () => {
4391
  picker.classList.remove('show');
4392
  });
4393
 
4394
- // Load audio and theme settings
4395
  loadAudioSettings();
4396
  loadThemeSettings();
4397
  refreshAudioDevices();
4398
  updateStatsDisplay();
4399
 
4400
- // Check for existing token
4401
  syncBuildVersionBadge();
4402
 
4403
  const token = localStorage.getItem('token');
@@ -4413,7 +4403,7 @@ window.addEventListener('DOMContentLoaded', () => {
4413
  document.getElementById('mainApp').classList.add('hidden');
4414
  }
4415
  });
4416
- // ==================== CUSTOM DIALOG SYSTEM ====================
4417
 
4418
  function customAlert(message, title = 'Notice', icon = 'ℹ️') {
4419
  return new Promise((resolve) => {
@@ -4502,7 +4492,7 @@ function resolveCustomDialog(value) {
4502
  if (window.customDialogResolve) window.customDialogResolve(value);
4503
  }
4504
 
4505
- // ==================== QR CODE FUNCTIONALITY ====================
4506
 
4507
  function showQRCodeModal() {
4508
  const modal = document.getElementById('qrModal');
@@ -4514,10 +4504,10 @@ function showQRCodeModal() {
4514
  return;
4515
  }
4516
 
4517
- // Clear previous QR code completely
4518
  qrContainer.innerHTML = '';
4519
 
4520
- // Create new container div for QR code
4521
  const qrDiv = document.createElement('div');
4522
  qrDiv.id = 'qrCodeCanvas';
4523
  qrDiv.style.backgroundColor = '#ffffff';
@@ -4526,13 +4516,13 @@ function showQRCodeModal() {
4526
  qrDiv.style.display = 'inline-block';
4527
  qrContainer.appendChild(qrDiv);
4528
 
4529
- // Display user ID
4530
  userIdSpan.textContent = currentUser.id || currentUser._id || 'Unknown';
4531
 
4532
- // Generate QR code with friend request data
4533
  const friendRequestData = `FRIEND_REQUEST:${currentUser.id || currentUser._id}:${currentUser.username || 'User'}`;
4534
 
4535
- // Use QRCode library if available, otherwise use API
4536
  if (typeof QRCode !== 'undefined') {
4537
  try {
4538
  new QRCode(qrDiv, {
@@ -4550,7 +4540,7 @@ function showQRCodeModal() {
4550
  }
4551
  } else {
4552
  console.warn('⚠️ QRCode library not loaded, using fallback');
4553
- // Use API fallback
4554
  generateQRCodeFallback(qrDiv, friendRequestData);
4555
  }
4556
 
@@ -4560,7 +4550,7 @@ function showQRCodeModal() {
4560
  function generateQRCodeFallback(container, data) {
4561
  console.log('📱 Using QR Code API fallback');
4562
 
4563
- // Use QR Code API with image
4564
  const img = new Image();
4565
  img.style.width = '200px';
4566
  img.style.height = '200px';
@@ -4576,7 +4566,7 @@ function generateQRCodeFallback(container, data) {
4576
 
4577
  img.onerror = () => {
4578
  console.error('❌ QR Code API failed');
4579
- // Final fallback - show text
4580
  container.innerHTML = `
4581
  <div style="width: 200px; height: 200px; background: white; border: 2px solid #000; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-direction: column; padding: 20px; box-sizing: border-box;">
4582
  <div style="font-size: 24px; margin-bottom: 10px;">📱</div>
@@ -4607,13 +4597,13 @@ function scanQRCodeForFriend() {
4607
  let friendId = null;
4608
  let friendName = 'User';
4609
 
4610
- // Parse QR data
4611
  if (qrData.startsWith('FRIEND_REQUEST:')) {
4612
  const parts = qrData.split(':');
4613
  friendId = parts[1];
4614
  friendName = parts[2] || 'User';
4615
  } else {
4616
- // Just ID provided
4617
  friendId = qrData.trim();
4618
  }
4619
 
@@ -4622,7 +4612,7 @@ function scanQRCodeForFriend() {
4622
  return;
4623
  }
4624
 
4625
- // Send friend request
4626
  customConfirm(
4627
  `Send friend request to <strong>${friendName}</strong> (ID: ${friendId})?`,
4628
  '👤 Friend Request',
@@ -4641,7 +4631,7 @@ function scanQRCodeForFriend() {
4641
  });
4642
  }
4643
 
4644
- // ==================== VIDEO CALL MODAL ====================
4645
 
4646
  function openVideoCallModal(targetName, targetId, callType) {
4647
  targetName = targetName || currentChatContext.name || 'User';
@@ -4674,7 +4664,7 @@ function openVideoCallModal(targetName, targetId, callType) {
4674
 
4675
  customAlert(html, '📞 Video Call', '📞');
4676
 
4677
- // Emit to server if connected
4678
  if (socket && socket.connected && targetId) {
4679
  socket.emit('start video call', {
4680
  targetId: targetId,
@@ -4710,7 +4700,7 @@ function handleIncomingVideoCall(payload) {
4710
  callId: activeCallState.callId,
4711
  toId: activeCallState.peerId
4712
  });
4713
- // Wait for caller offer to arrive, then start peer session from signaling handler.
4714
  } else {
4715
  stopIncomingCallAlert();
4716
  socket.emit('reject video call', {
@@ -4746,7 +4736,7 @@ async function ensureLocalCallStream() {
4746
  } catch (error) {
4747
  console.error('Media access error:', error);
4748
 
4749
- // Retry with safe defaults if selected device is unavailable.
4750
  try {
4751
  localCallStream = await navigator.mediaDevices.getUserMedia({
4752
  audio: true,
@@ -4772,7 +4762,7 @@ function createPeerConnection(peerId) {
4772
  { urls: 'stun:stun.l.google.com:19302' },
4773
  { urls: 'stun:stun1.l.google.com:19302' },
4774
  { urls: 'stun:stun2.l.google.com:19302' },
4775
- // Public TURN servers for better mobile connectivity
4776
  {
4777
  urls: 'turn:openrelay.metered.ca:80',
4778
  username: 'openrelayproject',
@@ -4814,18 +4804,18 @@ function createPeerConnection(peerId) {
4814
  if (remoteVideo && event.streams[0]) {
4815
  remoteVideo.srcObject = event.streams[0];
4816
 
4817
- // CRITICAL: Ensure audio is enabled
4818
  remoteVideo.muted = false;
4819
  remoteVideo.volume = 1.0;
4820
 
4821
- // Apply selected speaker if supported
4822
  if (audioSettings.speakerId && audioSettings.speakerId !== 'default' && typeof remoteVideo.setSinkId === 'function') {
4823
  remoteVideo.setSinkId(audioSettings.speakerId).catch((sinkError) => {
4824
  console.warn('Could not apply selected speaker for call:', sinkError);
4825
  });
4826
  }
4827
 
4828
- // Force play with user interaction handling
4829
  const playPromise = remoteVideo.play();
4830
  if (playPromise !== undefined) {
4831
  playPromise.then(() => {
@@ -4833,7 +4823,7 @@ function createPeerConnection(peerId) {
4833
  showToast('📞 Connected! Audio should be working now', 'success');
4834
  }).catch((error) => {
4835
  console.error('Remote video play error:', error);
4836
- // Try auto-playing after a short delay
4837
  setTimeout(() => {
4838
  remoteVideo.play().catch(e => console.error('Retry play failed:', e));
4839
  }, 500);
@@ -4845,13 +4835,13 @@ function createPeerConnection(peerId) {
4845
  peerConnection.onconnectionstatechange = () => {
4846
  console.log('📡 Connection state:', peerConnection.connectionState);
4847
 
4848
- // If user intentionally ended call, don't show error messages
4849
  if (window.userIntentionallEndedCall) {
4850
  console.log('User ended call intentionally, skipping error messages');
4851
  return;
4852
  }
4853
 
4854
- // Clear any existing disconnect timeout
4855
  if (window.callDisconnectTimeout) {
4856
  clearTimeout(window.callDisconnectTimeout);
4857
  window.callDisconnectTimeout = null;
@@ -4859,22 +4849,22 @@ function createPeerConnection(peerId) {
4859
 
4860
  if (peerConnection.connectionState === 'connecting') {
4861
  showToast('📞 Connecting call...', 'info');
4862
- // Start timer immediately when connecting begins
4863
  if (!callTimerInterval) {
4864
  startCallTimer();
4865
  }
4866
  } else if (peerConnection.connectionState === 'disconnected') {
4867
  showToast('⚠️ Connection unstable, trying to reconnect...', 'warning');
4868
- // Give more time for mobile networks to reconnect
4869
  window.callDisconnectTimeout = setTimeout(() => {
4870
  if (peerConnection && peerConnection.connectionState === 'disconnected' && !window.userIntentionallEndedCall) {
4871
  showToast('Call ended due to connection loss', 'error');
4872
  cleanupCallSession(false);
4873
  }
4874
- }, 15000); // 15 seconds grace period for mobile networks
4875
  } else if (peerConnection.connectionState === 'connected') {
4876
  showToast('📞 Call connected!', 'success');
4877
- // Ensure timer is running
4878
  if (!callTimerInterval) {
4879
  startCallTimer();
4880
  }
@@ -4888,7 +4878,7 @@ function createPeerConnection(peerId) {
4888
  }
4889
  }, 12000);
4890
  } else if (peerConnection.connectionState === 'closed') {
4891
- // Don't show toast for normal closure (user hung up)
4892
  cleanupCallSession(false);
4893
  }
4894
  };
@@ -4898,7 +4888,7 @@ function createPeerConnection(peerId) {
4898
  console.log('ICE connection state:', peerConnection.iceConnectionState);
4899
 
4900
  if (peerConnection.iceConnectionState === 'failed') {
4901
- // Try ICE restart before ending the call.
4902
  peerConnection.restartIce?.();
4903
  }
4904
  };
@@ -4955,10 +4945,10 @@ async function startCallSession(peerName, peerId, isCaller) {
4955
  }
4956
 
4957
  function endVideoCall() {
4958
- // Mark that user intentionally ended the call (prevents timeout errors)
4959
  window.userIntentionallEndedCall = true;
4960
 
4961
- // Clear any disconnect timeout immediately
4962
  if (window.callDisconnectTimeout) {
4963
  clearTimeout(window.callDisconnectTimeout);
4964
  window.callDisconnectTimeout = null;
@@ -4995,10 +4985,10 @@ function toggleLocalMute() {
4995
  }
4996
 
4997
  function cleanupCallSession(keepDialogOpen = false) {
4998
- // Reset the user intention flag so next call works normally
4999
  window.userIntentionallEndedCall = false;
5000
 
5001
- // Clear any pending disconnect timeout
5002
  if (window.callDisconnectTimeout) {
5003
  clearTimeout(window.callDisconnectTimeout);
5004
  window.callDisconnectTimeout = null;
@@ -5029,13 +5019,13 @@ function cleanupCallSession(keepDialogOpen = false) {
5029
  }
5030
 
5031
  function visitUserProfile(userId, userName = 'User') {
5032
- // Validate inputs
5033
  if (!userId || userId === 'undefined') {
5034
  showToast('❌ Invalid user ID', 'error');
5035
  return;
5036
  }
5037
 
5038
- // Create a more detailed profile view
5039
  const html = `
5040
  <div style="text-align: center; padding: 20px 0;">
5041
  <div style="width: 100px; height: 100px; background: linear-gradient(135deg, var(--primary-color), #4752c4); border-radius: 50%; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; font-size: 48px; color: white;">
@@ -5102,7 +5092,7 @@ function sendFriendRequest(userId, userName = 'User') {
5102
  });
5103
  }
5104
 
5105
- // ==================== MEDIA PREVIEW FUNCTIONALITY ====================
5106
 
5107
  let copiedMediaItems = [];
5108
 
@@ -5144,7 +5134,7 @@ function closeMediaPreview() {
5144
  document.getElementById('mediaPreviewPanel').classList.remove('show');
5145
  }
5146
 
5147
- // ==================== FRIEND SEARCH FUNCTIONALITY ====================
5148
 
5149
  function filterFriends(searchText) {
5150
  const channelsList = document.getElementById('channelsList');
@@ -5162,7 +5152,7 @@ function filterFriends(searchText) {
5162
  });
5163
  }
5164
 
5165
- // ==================== ARCHIVE SINGLE MESSAGE ====================
5166
 
5167
  let archivedMessages = JSON.parse(localStorage.getItem('archivedMessages') || '[]');
5168
 
@@ -5183,7 +5173,7 @@ function archiveSingleMessage(messageId, messageDiv) {
5183
  archivedMessages.push(messageData);
5184
  localStorage.setItem('archivedMessages', JSON.stringify(archivedMessages));
5185
 
5186
- // Add archive animation
5187
  messageDiv.style.transition = 'all 0.4s ease';
5188
  messageDiv.style.opacity = '0.5';
5189
  messageDiv.style.transform = 'translateX(-20px)';
 
 
1
  let socket;
2
  let currentUser = null;
3
  let currentToken = null;
 
54
  }
55
  };
56
 
57
+
 
58
  const SERVER_URL = window.location.origin;
59
  const API_BASE = SERVER_URL + '/api';
60
 
 
74
  badge.title = `Running backend version: ${data.version}`;
75
  }
76
  } catch (error) {
77
+
78
  }
79
  }
80
 
 
124
  osc.start();
125
  osc.stop(incomingCallAudioContext.currentTime + 0.15);
126
  } catch (error) {
127
+
128
  }
129
  }, 900);
130
  }
 
140
  }
141
  }
142
 
143
+
144
 
145
  function toggleAuthMode() {
146
  isRegistering = !isRegistering;
 
333
  setTimeout(() => location.reload(), 400);
334
  }
335
 
336
+
337
 
338
  function connectSocket() {
339
  socket = io(SERVER_URL, {
 
380
 
381
  const isCurrentRoom = currentChatContext?.type === roomType && String(currentChatContext?.name) === String(roomName);
382
  if (isCurrentRoom) {
383
+
384
  if (String(payload.from) !== String(currentUser.id)) {
385
  addMessageToChat(payload);
386
  }
387
  } else if (payload.from !== currentUser.id && chatSettings.notifyOnReceived) {
388
+
389
  const messagePreview = payload.mediaType === 'text' ? payload.content : `${payload.mediaType} message`;
390
  const roomLabel = roomType === 'community' ? 'Community' : 'Group';
391
  showToastClickable(
 
417
  const fromUserId = payload?.fromUserId;
418
  if (!fromUserId) return;
419
 
420
+
421
  if (currentChatContext?.type === 'dm' && String(currentChatContext?.id) === String(fromUserId)) {
422
  document.getElementById('messages-container').innerHTML = '';
423
  refreshChatScrollbar();
 
437
  );
438
 
439
  if (isCurrentChat) {
440
+
441
  if (!isSender) {
442
  addMessageToChat(data);
443
  }
444
  }
445
 
446
  if (isSender && chatSettings.notifyOnSent) {
447
+
448
  }
449
 
450
  if (!isSender && chatSettings.notifyOnReceived) {
 
466
  });
467
 
468
  socket.on('dm user typing', (data) => {
469
+
470
  showTypingIndicator(data.username);
471
  });
472
 
473
  socket.on('dm user stop typing', (data) => {
474
+
475
  hideTypingIndicator(data.username);
476
  });
477
 
478
  socket.on('community user typing', (data) => {
479
+
480
  if (currentChatContext.type === 'community' && currentChatContext.name === data.community) {
481
  showTypingIndicator(data.username);
482
  }
 
489
  });
490
 
491
  socket.on('room user typing', (data) => {
492
+
493
  if ((currentChatContext.type === 'group' || currentChatContext.type === 'room') && String(currentChatContext.id) === String(data.roomId)) {
494
  showTypingIndicator(data.username);
495
  }
 
630
  }
631
  }
632
 
633
+
634
 
635
  function showMainApp() {
636
  document.getElementById('authContainer').style.display = 'none';
637
  document.getElementById('mainApp').classList.remove('hidden');
638
 
 
639
  try {
640
+
641
  const profilePanel = document.getElementById('profilePanel');
642
  if (profilePanel) {
643
  profilePanel.classList.remove('show');
 
648
  profilePanel.style.transform = 'translateX(500px)';
649
  }
650
 
651
+
652
  const settingsPanel = document.getElementById('settingsPanel');
653
  if (settingsPanel) {
654
  settingsPanel.classList.remove('show');
655
  settingsPanel.style.right = '-500px';
656
  }
657
 
658
+
659
  const mobileMembersPanel = document.getElementById('mobileMembersPanel');
660
  if (mobileMembersPanel) {
661
  mobileMembersPanel.classList.remove('show');
662
  }
663
 
664
+
665
  const sidebarOverlay = document.getElementById('sidebarOverlay');
666
  if (sidebarOverlay) {
667
  sidebarOverlay.classList.remove('show');
668
  }
669
 
670
+
671
  const channelSidebar = document.getElementById('channelSidebar');
672
  if (channelSidebar) {
673
  channelSidebar.classList.remove('show');
 
678
 
679
  loadUserProfile();
680
  loadShopItems();
681
+
 
682
  const urlParams = new URLSearchParams(window.location.search);
683
  const addFriendId = urlParams.get('addFriend');
684
  if (addFriendId && addFriendId !== currentUser.id) {
685
  sendFriendRequestFromLink(addFriendId);
686
+
687
  window.history.replaceState({}, document.title, window.location.pathname);
688
  }
689
  }
 
719
  clearPendingMediaDraft();
720
  updatePinButtonState();
721
 
722
+
723
  const isSelfChat = String(friendId) === String(currentUser.id);
724
  const displayName = isSelfChat ? `📝 ${currentUser.username} (Notes)` : `💬 ${friendName}`;
725
 
726
+
727
  const chatTitleEl = document.getElementById('chatTitle');
728
  chatTitleEl.textContent = displayName;
729
  chatTitleEl.onclick = null;
730
  document.getElementById('messages-container').innerHTML = '';
731
  refreshChatScrollbar();
732
 
733
+
734
  document.getElementById('closeChatBtn').style.display = 'block';
735
 
736
+
737
  loadDMMessages(friendId);
738
 
739
+
740
  if (socket && socket.connected) {
741
  socket.emit('join dm', friendId);
742
  }
743
 
744
  showSection('chat');
745
 
746
+
747
  if (window.innerWidth <= 480) {
748
  closeMobileSidebar();
749
  }
 
756
  });
757
  const messages = await response.json();
758
 
759
+
760
  document.getElementById('messages-container').innerHTML = '';
761
  messages.forEach(msg => {
762
  addMessageToChat({
 
832
  showSection('chat');
833
  }
834
 
835
+
836
 
837
  async function loadUserProfile() {
838
  try {
 
896
  }
897
  }
898
 
899
+
900
 
901
  function handleMessageKeypress(event) {
902
  if (event.key === 'Enter' && chatSettings.enterToSend) {
 
976
  itemWrapper.appendChild(file);
977
  }
978
 
979
+
980
  const removeBtn = document.createElement('button');
981
  removeBtn.innerHTML = '✕';
982
  removeBtn.style.position = 'absolute';
 
1145
  }
1146
 
1147
  try {
1148
+
1149
  if (content) {
1150
  updateUserStats('messagesCount', 1);
1151
  updateUserStats('totalChars', content.length);
 
1192
  return;
1193
  }
1194
 
1195
+
1196
  const optimisticMessage = {
1197
  id: Date.now(),
1198
  from: currentUser.id,
 
1205
  };
1206
  addMessageToChat(optimisticMessage);
1207
 
1208
+
1209
  socket.emit('send dm', {
1210
  toUserId: targetId,
1211
  content: content,
 
1270
  text.style.fontSize = '16px';
1271
  }
1272
 
1273
+
1274
  if (data.mediaType === 'sticker') {
1275
  text.style.fontSize = '96px';
1276
  text.style.lineHeight = '1';
1277
  text.textContent = data.content;
1278
  } else {
1279
+
1280
  const emojiRegex = /^[\p{Emoji}\p{Emoji_Component}\s]+$/u;
1281
  const isOnlyEmojis = data.content && emojiRegex.test(data.content.trim());
1282
 
1283
  if (isOnlyEmojis && data.content.trim().length <= 20) {
1284
+
1285
  text.style.fontSize = '64px';
1286
  text.style.lineHeight = '1.2';
1287
  }
 
1293
  content.appendChild(text);
1294
  content.appendChild(time);
1295
 
1296
+
1297
  messageDiv.oncontextmenu = (e) => {
1298
  e.preventDefault();
1299
  showMessageContextMenu(e, messageDiv, data);
1300
  return false;
1301
  };
1302
 
1303
+
1304
  const actions = document.createElement('div');
1305
  actions.className = 'message-actions';
1306
 
 
1357
 
1358
  content.appendChild(actions);
1359
 
1360
+
1361
  const reactionsDiv = document.createElement('div');
1362
  reactionsDiv.className = 'message-reactions';
1363
  reactionsDiv.dataset.messageId = messageDiv.dataset.messageId;
 
1414
  audio.src = data.mediaUrl;
1415
  audio.controls = true;
1416
  audio.style.maxWidth = '300px';
1417
+
1418
  if (audioSettings.speakerId && audioSettings.speakerId !== 'default' && audio.setSinkId) {
1419
  audio.setSinkId(audioSettings.speakerId).catch(err => {
1420
  console.warn('Could not set audio output device:', err);
 
1429
  messageDiv.appendChild(content);
1430
  container.appendChild(messageDiv);
1431
 
1432
+
1433
  setTimeout(() => {
1434
  messageDiv.scrollIntoView({ behavior: 'smooth', block: 'end' });
1435
  refreshChatScrollbar();
 
1472
  item.style.cursor = 'pointer';
1473
  item.title = `Click to view ${user.username}'s profile`;
1474
 
1475
+
1476
  const avatar = document.createElement('div');
1477
  avatar.className = 'member-avatar';
1478
  if (user.avatar) {
 
1489
  const name = document.createElement('span');
1490
  name.textContent = user.username || 'Unknown';
1491
 
1492
+
1493
  const userId = user.id || user._id || user.userId;
1494
  const username = user.username || 'User';
1495
 
1496
+
1497
  item.onclick = () => {
1498
  if (!userId) {
1499
  showToast('❌ User ID not available', 'error');
 
1512
  membersList.appendChild(item);
1513
  });
1514
 
1515
+
1516
  const mobileMembersList = document.getElementById('mobileMembersList');
1517
  if (mobileMembersList) {
1518
  mobileMembersList.innerHTML = membersList.innerHTML;
1519
  }
1520
  }
1521
 
1522
+
1523
 
1524
  async function handleFileUpload(event) {
1525
  const files = Array.from(event.target.files || []);
 
1538
  let sentCount = 0;
1539
 
1540
  for (const file of files) {
1541
+
 
1542
  const payload = await new Promise((resolve) => {
1543
  const reader = new FileReader();
1544
  reader.onload = (e) => {
 
1569
  showToast(`Sent ${sentCount} media file(s)`, 'success');
1570
  }
1571
 
1572
+
1573
  event.target.value = '';
1574
  }
1575
 
 
1605
  };
1606
  reader.readAsDataURL(file);
1607
 
1608
+
1609
  event.target.value = '';
1610
  }
1611
 
1612
+
1613
 
1614
  async function loadFriendsForDM() {
1615
  try {
 
1621
  const channelsList = document.getElementById('channelsList');
1622
  channelsList.innerHTML = '';
1623
 
1624
+
1625
  const pinnedFriends = [];
1626
  const unpinnedFriends = [];
1627
 
 
1634
  }
1635
  });
1636
 
1637
+
1638
  if (pinnedFriends.length > 0) {
1639
  const pinnedTitle = document.createElement('div');
1640
  pinnedTitle.className = 'channel-section-title';
 
1646
  });
1647
  }
1648
 
 
1649
  if (joinedCommunities.length > 0) {
1650
  const communitiesTitle = document.createElement('div');
1651
  communitiesTitle.className = 'channel-section-title';
 
1674
  });
1675
  }
1676
 
1677
+
1678
  if (joinedGroups.length > 0) {
1679
  const groupsTitle = document.createElement('div');
1680
  groupsTitle.className = 'channel-section-title';
 
1708
  directTitle.textContent = 'DIRECT MESSAGES';
1709
  channelsList.appendChild(directTitle);
1710
 
1711
+
1712
  const selfItem = document.createElement('div');
1713
  selfItem.className = 'channel-item';
1714
 
 
1756
  item.classList.add('pinned');
1757
  }
1758
 
1759
+
1760
  const avatar = document.createElement('div');
1761
  avatar.className = 'channel-avatar';
1762
  if (friend.avatar) {
 
1767
  avatar.textContent = friend.username.charAt(0).toUpperCase();
1768
  }
1769
 
1770
+
1771
  const text = document.createElement('span');
1772
  const status = friend.status === 'online' ? '🟢' : '⚫';
1773
  text.textContent = `${status} ${friend.username}`;
 
1785
  item.style.cursor = 'pointer';
1786
  item.onclick = () => openDM(friend.id, friend.username);
1787
 
1788
+
1789
  item.oncontextmenu = (e) => {
1790
  e.preventDefault();
1791
  showFriendContextMenu(e, friend.id, friend.username);
 
1919
  }
1920
  }
1921
 
1922
+
1923
 
1924
  async function loadShopItems() {
1925
  try {
 
1992
  }
1993
  }
1994
 
1995
+
1996
 
1997
  async function loadInventory() {
1998
  try {
 
2037
  },
2038
  'Badges': () => {
2039
  showToast('🏆 Badge equipped! It now appears next to your name.', 'success');
2040
+
2041
  },
2042
  'Themes': () => {
2043
  const colors = ['#ff6b9d', '#c44569', '#4a69bd', '#6a89cc', '#60a3bc', '#78e08f', '#f6b93b', '#e55039'];
 
2047
  showToast('🎨 Theme applied! Your accent color has changed.', 'success');
2048
  },
2049
  'Effects': () => {
2050
+
2051
  const style = document.createElement('style');
2052
  style.id = 'message-glow-effect';
2053
  style.textContent = `
 
2113
  }
2114
 
2115
 
 
2116
 
2117
  function showToast(message, type = 'success') {
2118
  const container = document.getElementById('toastContainer');
 
2137
  }, 3000);
2138
  }
2139
 
2140
+
2141
 
2142
  function openSettings() {
2143
  document.getElementById('settingsPanel').classList.add('show');
 
2158
  }
2159
 
2160
  function switchSettingsTab(tab) {
2161
+
2162
  document.querySelectorAll('.settings-nav-item').forEach(item => {
2163
  item.classList.remove('active');
2164
  });
2165
  event.target.classList.add('active');
2166
 
2167
+
2168
  document.querySelectorAll('.settings-section').forEach(section => {
2169
  section.classList.remove('active');
2170
  });
 
2204
  }
2205
  }
2206
 
2207
+
2208
 
2209
  function changeTheme(theme) {
2210
  document.documentElement.setAttribute('data-theme', theme);
 
2221
  }
2222
  }
2223
 
2224
+
2225
 
2226
  function closeCurrentChat() {
2227
  if (activeRoomSubscription && socket && socket.connected) {
 
2311
  return;
2312
  }
2313
 
2314
+
2315
  const optimisticMessage = {
2316
  id: Date.now(),
2317
  from: currentUser.id,
 
2350
  return null;
2351
  }
2352
 
2353
+
2354
 
2355
  function requestNotificationPermission() {
2356
  if ('Notification' in window && Notification.permission === 'default') {
 
2451
  const modal = document.getElementById('communitiesModal');
2452
  const list = document.getElementById('communitiesList');
2453
 
2454
+
2455
  const communities = [
2456
+ { name: 'Gaming', iconId: '1F579', description: 'Talk about your favorite games' },
2457
+ { name: 'Coding', iconId: '1F4BB', description: 'Share code and learn together' },
2458
+ { name: 'Music', iconId: '1F3B5', description: 'Discuss and share music' },
2459
+ { name: 'Art', iconId: '1F3A8', description: 'Share your creative works' },
2460
+ { name: 'Sports', iconId: '26BD', description: 'Sports fans unite' },
2461
+ { name: 'Movies', iconId: '1F3AC', description: 'Film enthusiasts' }
2462
  ];
2463
 
2464
  list.innerHTML = communities.map(c => {
 
2468
  <div class="friend-item" style="flex-direction: column; align-items: flex-start; padding: 16px;">
2469
  <div style="display: flex; width: 100%; justify-content: space-between; align-items: center;">
2470
  <div>
2471
+ <span class="friend-name" style="font-size: 16px;">${profile.icon || String.fromCodePoint(parseInt(c.iconId, 16))} ${c.name}</span>
2472
  <div style="font-size: 12px; color: var(--text-secondary); margin-top: 4px;">${c.description}</div>
2473
  </div>
2474
  <div style="display:flex; gap:6px;">
 
2495
  const modal = document.getElementById('groupsModal');
2496
  const list = document.getElementById('groupsList');
2497
 
2498
+
2499
  const groups = [
2500
  { name: 'Study Group', emoji: '📚', description: 'Study together and share notes' },
2501
  { name: 'Project Team', emoji: '💼', description: 'Collaborate on projects' },
 
2564
  document.getElementById('closeChatBtn').style.display = 'block';
2565
  showSection('chat');
2566
 
2567
+
2568
  if (window.innerWidth <= 480) {
2569
  closeMobileSidebar();
2570
  }
 
2586
  document.getElementById('closeChatBtn').style.display = 'block';
2587
  showSection('chat');
2588
 
2589
+
2590
  if (window.innerWidth <= 480) {
2591
  closeMobileSidebar();
2592
  }
 
2629
  showToast(`Joined ${name} group!`);
2630
  }
2631
 
2632
+
2633
 
2634
  const stickers = [
2635
  '😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '🙃',
 
2677
  content.appendChild(item);
2678
  });
2679
  } else if (currentPickerTab === 'gifs') {
2680
+
2681
  const gifs = [
2682
  'https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif',
2683
  'https://media.giphy.com/media/l0MYt5jPR6QX5pnqM/giphy.gif',
 
2716
  }
2717
 
2718
  function sendSticker(sticker) {
2719
+
2720
  const messageInput = document.getElementById('messageInput');
2721
  if (messageInput) {
2722
  const currentText = messageInput.value;
 
2738
  document.getElementById('pickerPanel').classList.remove('show');
2739
  }
2740
 
2741
+
2742
 
2743
  let mediaRecorder;
2744
  let audioChunks = [];
 
2779
 
2780
  if (!mediaRecorder || mediaRecorder.state === 'inactive') {
2781
  try {
2782
+
2783
  const stream = await getSelectedMicrophone();
2784
  const mimeType = getSupportedAudioMimeType();
2785
  recordedAudioMimeType = mimeType || 'audio/webm';
2786
  mediaRecorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
2787
  audioChunks = [];
2788
 
2789
+
2790
  if (previewContainer) previewContainer.style.display = 'none';
2791
  if (previewAudio) {
2792
  previewAudio.pause();
 
2806
  showToast('🎧 Recording ready! CLICK PLAY BUTTON to hear it before sending!', 'success');
2807
  if (sendBtn) sendBtn.disabled = false;
2808
 
2809
+
2810
  if (previewContainer) previewContainer.style.display = 'block';
2811
  if (previewAudio) {
2812
  previewAudio.src = reader.result;
2813
  previewAudio.load();
2814
+
2815
  previewAudio.volume = 1.0;
2816
 
2817
+
2818
  if (audioSettings.speakerId && audioSettings.speakerId !== 'default' && previewAudio.setSinkId) {
2819
  previewAudio.setSinkId(audioSettings.speakerId).catch(err => {
2820
  console.warn('Could not set preview audio output:', err);
2821
  });
2822
  }
2823
 
2824
+
2825
  previewAudio.play().catch(err => {
2826
  console.log('Autoplay blocked, user must click play:', err);
2827
  showToast('⚠️ Click the PLAY button ▶️ below to hear your recording!', 'warning');
 
2919
  window.recordedAudio = null;
2920
  recordingSeconds = 0;
2921
 
2922
+
2923
  if (previewContainer) previewContainer.style.display = 'none';
2924
  if (previewAudio) {
2925
  previewAudio.pause();
 
2929
  if (sendBtn) sendBtn.disabled = true;
2930
  }
2931
 
2932
+
2933
 
2934
  function handleChatWheel(event) {
2935
  const container = document.getElementById('messages-container');
2936
  if (!container) return;
2937
 
 
2938
  event.preventDefault();
2939
  const delta = event.deltaY;
2940
  container.scrollBy({ top: delta * 0.8, behavior: 'auto' });
2941
  }
2942
 
2943
+
2944
 
2945
  async function refreshAudioDevices() {
2946
  try {
2947
+
2948
  await navigator.mediaDevices.getUserMedia({ audio: true, video: false })
2949
  .then(stream => stream.getTracks().forEach(track => track.stop()))
2950
  .catch(() => console.log('Microphone permission needed'));
 
2995
 
2996
  localStorage.setItem('audioSettings', JSON.stringify(audioSettings));
2997
 
2998
+
2999
  applySpeakerToAudioElements();
3000
 
3001
  showToast('Audio settings saved', 'success');
 
3005
  const speakerId = audioSettings.speakerId;
3006
  if (!speakerId || speakerId === 'default') return;
3007
 
3008
+
3009
  document.querySelectorAll('audio').forEach(audio => {
3010
  if (audio.setSinkId) {
3011
  audio.setSinkId(speakerId).catch(err => {
 
3014
  }
3015
  });
3016
 
3017
+
3018
  const voicePreview = document.getElementById('voicePreview');
3019
  if (voicePreview && voicePreview.setSinkId) {
3020
  voicePreview.setSinkId(speakerId).catch(err => {
 
3044
  });
3045
  }
3046
 
3047
+
3048
 
3049
  function changeTheme(theme) {
3050
  themeSettings.mode = theme;
 
3093
  root.style.setProperty('--primary-color', icon);
3094
  root.style.setProperty('--secondary-color', icon);
3095
  root.style.setProperty('--icon-color', icon);
3096
+
3097
  root.style.setProperty('--ui-text-color', '#ffffff');
3098
 
3099
+
3100
  const simpleTheme = { bg, text, icon, mode: 'custom' };
3101
  localStorage.setItem('simpleTheme', JSON.stringify(simpleTheme));
3102
  }
 
3192
  }
3193
  }
3194
 
 
3195
 
3196
+
3197
+ let messageReactions = {};
3198
 
3199
  function showQuickReactions(messageId, messageDiv) {
3200
  const quickReactions = ['👍', '❤️', '😂', '😮', '😢', '🙏'];
3201
 
3202
+
3203
  document.querySelectorAll('.reaction-picker').forEach(p => p.remove());
3204
 
3205
+
3206
  const picker = document.createElement('div');
3207
  picker.className = 'reaction-picker';
3208
  picker.style.position = 'fixed';
 
3215
  picker.style.zIndex = '9999';
3216
  picker.style.boxShadow = '0 8px 24px rgba(0,0,0,0.6)';
3217
 
3218
+
3219
  const rect = messageDiv.getBoundingClientRect();
3220
  picker.style.left = Math.max(20, rect.left) + 'px';
3221
  picker.style.top = Math.max(20, rect.top - 60) + 'px';
 
3248
 
3249
  document.body.appendChild(picker);
3250
 
3251
+
3252
  const closePickerHandler = (e) => {
3253
  if (!picker.contains(e.target)) {
3254
  picker.remove();
 
3274
  const userIndex = messageReactions[messageId][emoji].indexOf(userId);
3275
 
3276
  if (userIndex > -1) {
3277
+
3278
  messageReactions[messageId][emoji].splice(userIndex, 1);
3279
  if (messageReactions[messageId][emoji].length === 0) {
3280
  delete messageReactions[messageId][emoji];
3281
  }
3282
  } else {
3283
+
3284
  messageReactions[messageId][emoji].push(userId);
3285
  updateUserStats('reactionsGiven', 1);
3286
  }
 
3291
  }
3292
 
3293
  function showMessageContextMenu(e, messageDiv, data) {
3294
+
3295
  document.querySelectorAll('.message-context-menu').forEach(m => m.remove());
3296
 
3297
  const menu = document.createElement('div');
 
3329
  btn.style.fontWeight = '500';
3330
  btn.style.borderBottom = '1px solid var(--border-color)';
3331
 
3332
+
3333
  btn.onmouseover = () => {
3334
  if (item.hoverClass === 'danger-hover') {
3335
  btn.style.background = 'var(--danger)';
 
3357
  menu.appendChild(btn);
3358
  });
3359
 
3360
+
3361
  const lastBtn = menu.lastElementChild;
3362
  if (lastBtn) lastBtn.style.borderBottom = 'none';
3363
 
3364
  document.body.appendChild(menu);
3365
 
3366
+
3367
  document.addEventListener('click', () => menu.remove(), { once: true });
3368
  }
3369
 
3370
  function showFriendContextMenu(e, friendId, friendName) {
3371
+
3372
  document.querySelectorAll('.message-context-menu').forEach(m => m.remove());
3373
 
3374
  const menu = document.createElement('div');
 
3400
  btn.style.fontWeight = '500';
3401
  btn.style.borderBottom = '1px solid var(--border-color)';
3402
 
3403
+
3404
  btn.onmouseover = () => {
3405
  if (item.hoverClass === 'danger-hover') {
3406
  btn.style.background = 'var(--danger)';
 
3428
  menu.appendChild(btn);
3429
  });
3430
 
3431
+
3432
  const lastBtn = menu.lastElementChild;
3433
  if (lastBtn) lastBtn.style.borderBottom = 'none';
3434
 
3435
  document.body.appendChild(menu);
3436
 
3437
+
3438
  document.addEventListener('click', () => menu.remove(), { once: true });
3439
  }
3440
 
 
3490
  } else if (imgElement || vidElement || audioElement) {
3491
  const url = imgElement?.src || vidElement?.src || audioElement?.src || '';
3492
  if (url) {
3493
+
3494
  if (imgElement) {
3495
  addToMediaPreview('image', url);
3496
  } else if (vidElement) {
3497
  addToMediaPreview('video', url);
3498
  }
3499
 
3500
+
3501
  navigator.clipboard.writeText(url).then(() => {
3502
  const mediaType = imgElement ? 'Image' : vidElement ? 'Video' : 'Audio';
3503
  showToast(`✅ ${mediaType} copied! View in preview panel.`, 'success');
3504
 
3505
+
3506
  if (copiedMediaItems.length > 0) {
3507
  setTimeout(() => openMediaPreview(), 500);
3508
  }
 
3534
 
3535
  showToast('✅ Message edited', 'success');
3536
 
3537
+
 
3538
  }
3539
  });
3540
  }
 
3547
  true
3548
  ).then(confirmed => {
3549
  if (confirmed) {
3550
+
3551
  messageDiv.style.transition = 'all 0.3s ease-out';
3552
  messageDiv.style.opacity = '0';
3553
  messageDiv.style.transform = 'translateX(-100%)';
3554
 
3555
+
3556
  setTimeout(() => {
3557
  messageDiv.remove();
3558
  showToast('✅ Message deleted', 'success');
3559
  }, 300);
3560
 
3561
+
 
3562
  }
3563
  });
3564
  }
3565
 
3566
+
3567
 
3568
  function createNewGroup() {
3569
  customPrompt('Enter a name for your new group:', '👥 Create Group', 'My Awesome Group', '', '👥').then(groupName => {
 
3586
 
3587
  const ids = memberIds.split(',').map(id => id.trim()).filter(Boolean);
3588
  showToast(`✅ Group "${groupName}" created with ${ids.length} members!`, 'success');
3589
+
3590
  });
3591
  });
3592
  }
 
3619
  input.click();
3620
  }
3621
 
3622
+
3623
 
3624
  function startVideoCall() {
3625
  if (!currentChatContext || currentChatContext.type === 'none' || !currentChatContext.id) {
 
3636
  const targetId = currentChatContext.id;
3637
  const callType = 'Video Call';
3638
 
3639
+
3640
  openVideoCallModal(targetName, targetId, callType);
3641
  }
3642
 
3643
+
3644
 
3645
  let archivedChats = JSON.parse(localStorage.getItem('archivedChats') || '[]');
3646
 
 
3657
  const archiveIndex = archivedChats.findIndex(c => c.id === chatId && c.type === chatType);
3658
 
3659
  if (archiveIndex > -1) {
3660
+
3661
  archivedChats.splice(archiveIndex, 1);
3662
  showToast(`${chatName} unarchived`, 'success');
3663
  } else {
3664
+
3665
  archivedChats.push({ id: chatId, name: chatName, type: chatType });
3666
  showToast(`${chatName} archived`, 'success');
3667
+
3668
  if (chatType === 'dm') {
3669
  const friendIndex = friends.findIndex(f => f.id === chatId);
3670
  if (friendIndex > -1) {
 
3676
  }
3677
 
3678
  localStorage.setItem('archivedChats', JSON.stringify(archivedChats));
3679
+ loadFriendsForDM();
3680
  }
3681
 
3682
  function showArchivedChats() {
 
3736
  showToast(`${chat.name} unarchived`, 'success');
3737
  loadFriendsForDM();
3738
  closeCustomDialog();
3739
+ showArchivedChats();
3740
  }
3741
  }
3742
 
3743
+
3744
 
3745
  let userStats = JSON.parse(localStorage.getItem('userStats') || '{"messagesCount": 0, "totalChars": 0, "reactionsGiven": 0, "archiveCount": 0, "quizCorrect": 0}');
3746
 
 
3872
  });
3873
  }
3874
 
3875
+
3876
  function showMobileSection(section) {
3877
+
3878
  if (window.innerWidth <= 768) {
3879
  closeMobileSidebar();
3880
  closeSettings();
 
3883
  closeGroupsModal();
3884
  }
3885
 
3886
+
3887
  document.querySelectorAll('.mobile-nav-item').forEach(item => {
3888
  if (item.getAttribute('data-section') === section) {
3889
  item.classList.add('active');
 
3892
  }
3893
  });
3894
 
3895
+
3896
  switch(section) {
3897
  case 'chats':
3898
  toggleChannelSidebar();
 
3976
  const desktopList = document.getElementById('membersList');
3977
  if (!mobileList || !desktopList) return;
3978
 
3979
+
3980
  mobileList.innerHTML = desktopList.innerHTML;
3981
  }
3982
 
 
3984
  if (!communityProfiles[name]) {
3985
  communityProfiles[name] = {
3986
  name,
3987
+ icon: '',
3988
  image: '',
3989
  members: [
3990
  {
 
4107
  if (currentChatContext.type === 'community' && currentChatContext.name === name) {
4108
  document.getElementById('chatTitle').textContent = `${profile.icon} Community: ${name}`;
4109
  }
4110
+ loadFriendsForDM();
4111
  showToast('Community icon updated! 🎉', 'success');
4112
  }
4113
 
 
4135
  communityProfiles[name] = profile;
4136
  localStorage.setItem('communityProfiles', JSON.stringify(communityProfiles));
4137
 
4138
+
4139
  if (currentChatContext?.type === 'community' && currentChatContext?.name === name) {
4140
  const profileAvatar = document.getElementById('profileAvatar');
4141
  if (profileAvatar) {
 
4148
  if (profileName) {
4149
  profileName.textContent = name;
4150
  }
4151
+
4152
  setTimeout(() => {
4153
  const avatarDiv = document.getElementById('profileAvatar');
4154
  if (avatarDiv && !avatarDiv.style.backgroundImage) {
 
4181
  groupProfiles[name] = profile;
4182
  localStorage.setItem('groupProfiles', JSON.stringify(groupProfiles));
4183
 
4184
+
4185
  if (currentChatContext?.type === 'group' && currentChatContext?.name === name) {
4186
  const profileAvatar = document.getElementById('profileAvatar');
4187
  if (profileAvatar) {
 
4194
  if (profileName) {
4195
  profileName.textContent = name;
4196
  }
4197
+
4198
  setTimeout(() => {
4199
  const avatarDiv = document.getElementById('profileAvatar');
4200
  if (avatarDiv && !avatarDiv.style.backgroundImage) {
 
4212
  input.click();
4213
  }
4214
 
4215
+
4216
  function toggleChannelSidebarEnhanced() {
4217
  const sidebar = document.getElementById('channelSidebar');
4218
  const overlay = document.getElementById('sidebarOverlay');
 
4221
  overlay?.classList.toggle('show');
4222
  }
4223
 
4224
+
4225
  function togglePinCurrentChat() {
4226
  if (!currentChatContext || currentChatContext.type === 'none') {
4227
  showToast('No chat selected', 'warning');
 
4232
  const index = pinnedChats.indexOf(chatId);
4233
 
4234
  if (index > -1) {
4235
+
4236
  pinnedChats.splice(index, 1);
4237
  showToast('Chat unpinned', 'success');
4238
  } else {
4239
+
4240
  pinnedChats.push(chatId);
4241
  showToast('Chat pinned to top', 'success');
4242
  }
4243
 
4244
  localStorage.setItem('pinnedChats', JSON.stringify(pinnedChats));
4245
  updatePinButtonState();
4246
+ loadFriendsForDM();
4247
  }
4248
 
4249
  function updatePinButtonState() {
 
4262
  return pinnedChats.includes(chatKey);
4263
  }
4264
 
4265
+
4266
  function showToastClickable(message, type = 'success', chatContext = null) {
4267
  const container = document.getElementById('toastContainer');
4268
  if (!container) return;
 
4289
  if (chatContext) {
4290
  toast.style.cursor = 'pointer';
4291
  toast.addEventListener('click', () => {
4292
+
4293
  if (chatContext.type === 'dm') {
4294
  openDM(chatContext.friendId, chatContext.friendName);
4295
  } else if (chatContext.type === 'community') {
 
4298
  openRoomChat('group', chatContext.name);
4299
  }
4300
 
4301
+
4302
  toast.remove();
4303
 
4304
+
4305
  if (window.innerWidth <= 480) {
4306
  closeMobileSidebar();
4307
  }
 
4315
  }, 5000);
4316
  }
4317
 
4318
+
4319
 
4320
  window.addEventListener('DOMContentLoaded', () => {
4321
  loadTheme();
 
4331
  document.getElementById('autoplayGifs')?.addEventListener('change', saveChatSettings);
4332
  document.getElementById('compactMode')?.addEventListener('change', saveAppearanceSettings);
4333
 
4334
+
4335
  let typingTimeout = null;
4336
  const typingInput = document.getElementById('messageInput');
4337
  if (typingInput) {
4338
  typingInput.addEventListener('input', () => {
4339
  if (!chatSettings.showTypingIndicator) return;
4340
 
4341
+
4342
  if (currentChatContext.type === 'community' && currentChatContext.name) {
4343
  socket?.emit('community typing', { community: currentChatContext.name, username: currentUser?.username });
4344
  clearTimeout(typingTimeout);
 
4381
  picker.classList.remove('show');
4382
  });
4383
 
4384
+
4385
  loadAudioSettings();
4386
  loadThemeSettings();
4387
  refreshAudioDevices();
4388
  updateStatsDisplay();
4389
 
4390
+
4391
  syncBuildVersionBadge();
4392
 
4393
  const token = localStorage.getItem('token');
 
4403
  document.getElementById('mainApp').classList.add('hidden');
4404
  }
4405
  });
4406
+
4407
 
4408
  function customAlert(message, title = 'Notice', icon = 'ℹ️') {
4409
  return new Promise((resolve) => {
 
4492
  if (window.customDialogResolve) window.customDialogResolve(value);
4493
  }
4494
 
4495
+
4496
 
4497
  function showQRCodeModal() {
4498
  const modal = document.getElementById('qrModal');
 
4504
  return;
4505
  }
4506
 
4507
+
4508
  qrContainer.innerHTML = '';
4509
 
4510
+
4511
  const qrDiv = document.createElement('div');
4512
  qrDiv.id = 'qrCodeCanvas';
4513
  qrDiv.style.backgroundColor = '#ffffff';
 
4516
  qrDiv.style.display = 'inline-block';
4517
  qrContainer.appendChild(qrDiv);
4518
 
4519
+
4520
  userIdSpan.textContent = currentUser.id || currentUser._id || 'Unknown';
4521
 
4522
+
4523
  const friendRequestData = `FRIEND_REQUEST:${currentUser.id || currentUser._id}:${currentUser.username || 'User'}`;
4524
 
4525
+
4526
  if (typeof QRCode !== 'undefined') {
4527
  try {
4528
  new QRCode(qrDiv, {
 
4540
  }
4541
  } else {
4542
  console.warn('⚠️ QRCode library not loaded, using fallback');
4543
+
4544
  generateQRCodeFallback(qrDiv, friendRequestData);
4545
  }
4546
 
 
4550
  function generateQRCodeFallback(container, data) {
4551
  console.log('📱 Using QR Code API fallback');
4552
 
4553
+
4554
  const img = new Image();
4555
  img.style.width = '200px';
4556
  img.style.height = '200px';
 
4566
 
4567
  img.onerror = () => {
4568
  console.error('❌ QR Code API failed');
4569
+
4570
  container.innerHTML = `
4571
  <div style="width: 200px; height: 200px; background: white; border: 2px solid #000; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-direction: column; padding: 20px; box-sizing: border-box;">
4572
  <div style="font-size: 24px; margin-bottom: 10px;">📱</div>
 
4597
  let friendId = null;
4598
  let friendName = 'User';
4599
 
4600
+
4601
  if (qrData.startsWith('FRIEND_REQUEST:')) {
4602
  const parts = qrData.split(':');
4603
  friendId = parts[1];
4604
  friendName = parts[2] || 'User';
4605
  } else {
4606
+
4607
  friendId = qrData.trim();
4608
  }
4609
 
 
4612
  return;
4613
  }
4614
 
4615
+
4616
  customConfirm(
4617
  `Send friend request to <strong>${friendName}</strong> (ID: ${friendId})?`,
4618
  '👤 Friend Request',
 
4631
  });
4632
  }
4633
 
4634
+
4635
 
4636
  function openVideoCallModal(targetName, targetId, callType) {
4637
  targetName = targetName || currentChatContext.name || 'User';
 
4664
 
4665
  customAlert(html, '📞 Video Call', '📞');
4666
 
4667
+
4668
  if (socket && socket.connected && targetId) {
4669
  socket.emit('start video call', {
4670
  targetId: targetId,
 
4700
  callId: activeCallState.callId,
4701
  toId: activeCallState.peerId
4702
  });
4703
+
4704
  } else {
4705
  stopIncomingCallAlert();
4706
  socket.emit('reject video call', {
 
4736
  } catch (error) {
4737
  console.error('Media access error:', error);
4738
 
4739
+
4740
  try {
4741
  localCallStream = await navigator.mediaDevices.getUserMedia({
4742
  audio: true,
 
4762
  { urls: 'stun:stun.l.google.com:19302' },
4763
  { urls: 'stun:stun1.l.google.com:19302' },
4764
  { urls: 'stun:stun2.l.google.com:19302' },
4765
+
4766
  {
4767
  urls: 'turn:openrelay.metered.ca:80',
4768
  username: 'openrelayproject',
 
4804
  if (remoteVideo && event.streams[0]) {
4805
  remoteVideo.srcObject = event.streams[0];
4806
 
4807
+
4808
  remoteVideo.muted = false;
4809
  remoteVideo.volume = 1.0;
4810
 
4811
+
4812
  if (audioSettings.speakerId && audioSettings.speakerId !== 'default' && typeof remoteVideo.setSinkId === 'function') {
4813
  remoteVideo.setSinkId(audioSettings.speakerId).catch((sinkError) => {
4814
  console.warn('Could not apply selected speaker for call:', sinkError);
4815
  });
4816
  }
4817
 
4818
+
4819
  const playPromise = remoteVideo.play();
4820
  if (playPromise !== undefined) {
4821
  playPromise.then(() => {
 
4823
  showToast('📞 Connected! Audio should be working now', 'success');
4824
  }).catch((error) => {
4825
  console.error('Remote video play error:', error);
4826
+
4827
  setTimeout(() => {
4828
  remoteVideo.play().catch(e => console.error('Retry play failed:', e));
4829
  }, 500);
 
4835
  peerConnection.onconnectionstatechange = () => {
4836
  console.log('📡 Connection state:', peerConnection.connectionState);
4837
 
4838
+
4839
  if (window.userIntentionallEndedCall) {
4840
  console.log('User ended call intentionally, skipping error messages');
4841
  return;
4842
  }
4843
 
4844
+
4845
  if (window.callDisconnectTimeout) {
4846
  clearTimeout(window.callDisconnectTimeout);
4847
  window.callDisconnectTimeout = null;
 
4849
 
4850
  if (peerConnection.connectionState === 'connecting') {
4851
  showToast('📞 Connecting call...', 'info');
4852
+
4853
  if (!callTimerInterval) {
4854
  startCallTimer();
4855
  }
4856
  } else if (peerConnection.connectionState === 'disconnected') {
4857
  showToast('⚠️ Connection unstable, trying to reconnect...', 'warning');
4858
+
4859
  window.callDisconnectTimeout = setTimeout(() => {
4860
  if (peerConnection && peerConnection.connectionState === 'disconnected' && !window.userIntentionallEndedCall) {
4861
  showToast('Call ended due to connection loss', 'error');
4862
  cleanupCallSession(false);
4863
  }
4864
+ }, 15000);
4865
  } else if (peerConnection.connectionState === 'connected') {
4866
  showToast('📞 Call connected!', 'success');
4867
+
4868
  if (!callTimerInterval) {
4869
  startCallTimer();
4870
  }
 
4878
  }
4879
  }, 12000);
4880
  } else if (peerConnection.connectionState === 'closed') {
4881
+
4882
  cleanupCallSession(false);
4883
  }
4884
  };
 
4888
  console.log('ICE connection state:', peerConnection.iceConnectionState);
4889
 
4890
  if (peerConnection.iceConnectionState === 'failed') {
4891
+
4892
  peerConnection.restartIce?.();
4893
  }
4894
  };
 
4945
  }
4946
 
4947
  function endVideoCall() {
4948
+
4949
  window.userIntentionallEndedCall = true;
4950
 
4951
+
4952
  if (window.callDisconnectTimeout) {
4953
  clearTimeout(window.callDisconnectTimeout);
4954
  window.callDisconnectTimeout = null;
 
4985
  }
4986
 
4987
  function cleanupCallSession(keepDialogOpen = false) {
4988
+
4989
  window.userIntentionallEndedCall = false;
4990
 
4991
+
4992
  if (window.callDisconnectTimeout) {
4993
  clearTimeout(window.callDisconnectTimeout);
4994
  window.callDisconnectTimeout = null;
 
5019
  }
5020
 
5021
  function visitUserProfile(userId, userName = 'User') {
5022
+
5023
  if (!userId || userId === 'undefined') {
5024
  showToast('❌ Invalid user ID', 'error');
5025
  return;
5026
  }
5027
 
5028
+
5029
  const html = `
5030
  <div style="text-align: center; padding: 20px 0;">
5031
  <div style="width: 100px; height: 100px; background: linear-gradient(135deg, var(--primary-color), #4752c4); border-radius: 50%; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; font-size: 48px; color: white;">
 
5092
  });
5093
  }
5094
 
5095
+
5096
 
5097
  let copiedMediaItems = [];
5098
 
 
5134
  document.getElementById('mediaPreviewPanel').classList.remove('show');
5135
  }
5136
 
5137
+
5138
 
5139
  function filterFriends(searchText) {
5140
  const channelsList = document.getElementById('channelsList');
 
5152
  });
5153
  }
5154
 
5155
+
5156
 
5157
  let archivedMessages = JSON.parse(localStorage.getItem('archivedMessages') || '[]');
5158
 
 
5173
  archivedMessages.push(messageData);
5174
  localStorage.setItem('archivedMessages', JSON.stringify(archivedMessages));
5175
 
5176
+
5177
  messageDiv.style.transition = 'all 0.4s ease';
5178
  messageDiv.style.opacity = '0.5';
5179
  messageDiv.style.transform = 'translateX(-20px)';
index.html CHANGED
@@ -27,7 +27,7 @@
27
  --danger: #f04747;
28
  }
29
 
30
- /* Light Theme */
31
  [data-theme="light"] {
32
  --primary-color: #5865f2;
33
  --secondary-color: #99aab5;
@@ -50,7 +50,7 @@
50
  overflow: hidden;
51
  }
52
 
53
- /* ==================== LOGIN PAGE ==================== */
54
  .auth-container {
55
  position: fixed;
56
  top: 0;
@@ -187,7 +187,7 @@
187
  cursor: pointer;
188
  }
189
 
190
- /* ==================== MAIN LAYOUT ==================== */
191
  .main-container {
192
  display: grid;
193
  grid-template-columns: 72px 240px 1fr 240px;
@@ -199,7 +199,7 @@
199
  display: none;
200
  }
201
 
202
- /* ==================== GUILD SIDEBAR ==================== */
203
  .guild-sidebar {
204
  background: var(--sidebar-bg);
205
  display: flex;
@@ -241,7 +241,7 @@
241
  margin: 8px 0;
242
  }
243
 
244
- /* ==================== CHANNEL SIDEBAR ==================== */
245
  .channel-sidebar {
246
  background: var(--sidebar-bg);
247
  border-right: 1px solid var(--border-color);
@@ -348,7 +348,7 @@
348
  font-weight: bold;
349
  }
350
 
351
- /* ==================== MAIN CHAT AREA ==================== */
352
  .chat-area {
353
  position: relative;
354
  display: flex;
@@ -431,7 +431,7 @@
431
  -webkit-overflow-scrolling: touch;
432
  }
433
 
434
- /* Hide native scrollbar (custom scrollbar is used) */
435
  #messages-container::-webkit-scrollbar {
436
  width: 0;
437
  height: 0;
@@ -682,7 +682,7 @@
682
  cursor: pointer;
683
  }
684
 
685
- /* Scroll buttons removed - using WhatsApp scrollbar instead */
686
 
687
  .input-area {
688
  padding: 16px 24px;
@@ -735,7 +735,7 @@
735
  display: none;
736
  }
737
 
738
- /* ==================== RIGHT SIDEBAR (MEMBERS/USERS) ==================== */
739
  .members-sidebar {
740
  background: var(--sidebar-bg);
741
  border-left: 1px solid var(--border-color);
@@ -784,7 +784,7 @@
784
  background: var(--text-secondary);
785
  }
786
 
787
- /* ==================== PROFILE PANEL ==================== */
788
  .profile-panel {
789
  position: fixed;
790
  right: 0;
@@ -1076,7 +1076,7 @@
1076
  to { opacity: 0; transform: translateX(100%); }
1077
  }
1078
 
1079
- /* ==================== STICKER & GIF PICKER - INTEGRATED ==================== */
1080
  .picker-panel {
1081
  position: absolute;
1082
  bottom: 60px;
@@ -1147,7 +1147,7 @@
1147
  object-fit: cover;
1148
  }
1149
 
1150
- /* ==================== VOICE RECORDER ==================== */
1151
  .voice-recorder {
1152
  position: absolute;
1153
  bottom: 70px;
@@ -1216,7 +1216,7 @@
1216
  color: var(--danger);
1217
  }
1218
 
1219
- /* ====================SETTINGS PANEL ==================== */
1220
  .settings-panel {
1221
  position: fixed;
1222
  top: 0;
@@ -1367,7 +1367,7 @@
1367
  transform: translateX(20px);
1368
  }
1369
 
1370
- /* ==================== MODAL ==================== */
1371
  .modal {
1372
  position: fixed;
1373
  top: 0;
@@ -1396,7 +1396,7 @@
1396
  overflow-y: auto;
1397
  }
1398
 
1399
- /* ==================== SCROLLBAR ==================== */
1400
  ::-webkit-scrollbar {
1401
  width: 8px;
1402
  }
@@ -1414,7 +1414,7 @@
1414
  background: #5a5d60;
1415
  }
1416
 
1417
- /* ==================== MOBILE BOTTOM NAV ==================== */
1418
  .mobile-bottom-nav {
1419
  display: none;
1420
  position: fixed;
@@ -1430,7 +1430,7 @@
1430
  box-shadow: 0 -4px 20px rgba(0,0,0,0.3);
1431
  }
1432
 
1433
- /* ==================== MOBILE MEMBERS PANEL ==================== */
1434
  .mobile-members-panel {
1435
  position: fixed;
1436
  right: -100%;
@@ -1512,7 +1512,7 @@
1512
  text-align: center;
1513
  }
1514
 
1515
- /* ==================== RESPONSIVE ==================== */
1516
  @media (max-width: 1200px) {
1517
  .main-container {
1518
  grid-template-columns: 72px 240px 1fr;
@@ -1532,7 +1532,7 @@
1532
  }
1533
  }
1534
 
1535
- /* ==================== MOBILE ADAPTIVE LAYOUT ==================== */
1536
  @media (max-width: 768px) {
1537
  body {
1538
  overflow: hidden;
@@ -1545,7 +1545,7 @@
1545
  max-height: -webkit-fill-available;
1546
  }
1547
 
1548
- /* Mobile Guild Sidebar - Compact */
1549
  .guild-sidebar {
1550
  padding: 2px 0;
1551
  width: 45px;
@@ -1581,7 +1581,7 @@
1581
  display: flex;
1582
  }
1583
 
1584
- /* Mobile overlay for sidebar */
1585
  .channel-sidebar.show::before {
1586
  content: '';
1587
  position: fixed;
@@ -1593,7 +1593,7 @@
1593
  z-index: -1;
1594
  }
1595
 
1596
- /* Mobile Chat Area */
1597
  .chat-area {
1598
  height: 100vh;
1599
  height: 100dvh;
@@ -1641,7 +1641,7 @@
1641
  display: none;
1642
  }
1643
 
1644
- /* Mobile Messages Container */
1645
  #messages-container {
1646
  flex: 1;
1647
  min-height: 0;
@@ -1674,7 +1674,7 @@
1674
  max-width: 90%;
1675
  }
1676
 
1677
- /* Mobile Input Area */
1678
  .input-area {
1679
  padding: 12px 16px;
1680
  gap: 8px;
@@ -1694,7 +1694,7 @@
1694
  flex-shrink: 0;
1695
  }
1696
 
1697
- /* Mobile Scroll Rail */
1698
  .chat-scroll-rail {
1699
  right: 4px;
1700
  top: 64px;
@@ -1706,7 +1706,7 @@
1706
  min-height: 40px;
1707
  }
1708
 
1709
- /* Mobile Panels */
1710
  .picker-panel {
1711
  width: 100%;
1712
  left: 0;
@@ -1727,7 +1727,7 @@
1727
  padding: 8px;
1728
  }
1729
 
1730
- /* Mobile Settings Panel */
1731
  .settings-panel {
1732
  width: 100%;
1733
  right: -100%;
@@ -1762,7 +1762,7 @@
1762
  border-bottom-color: var(--primary-color);
1763
  }
1764
 
1765
- /* Mobile Toasts */
1766
  .toast-container {
1767
  top: 10px;
1768
  right: 10px;
@@ -1774,18 +1774,17 @@
1774
  padding: 12px 16px;
1775
  }
1776
 
1777
- /* Mobile Profile Panel */
1778
  .profile-panel {
1779
  width: 100%;
1780
  }
1781
 
1782
- /* Hide members sidebar completely on mobile */
1783
  .members-sidebar {
1784
  display: none !important;
1785
  }
1786
  }
1787
 
1788
- /* ==================== CUSTOM DIALOG MODAL ==================== */
1789
  .custom-dialog {
1790
  position: fixed;
1791
  top: 0;
@@ -1903,7 +1902,7 @@
1903
  transform: translateY(-2px);
1904
  }
1905
 
1906
- /* ==================== COMMUNITY MEMBERS MODAL ==================== */
1907
  .community-modal {
1908
  position: fixed;
1909
  top: 0;
@@ -1975,7 +1974,7 @@
1975
  background: rgba(255, 193, 7, 0.1);
1976
  }
1977
 
1978
- /* ==================== FRIEND SEARCH BAR ==================== */
1979
  .friend-search-container {
1980
  padding: 12px 16px;
1981
  border-bottom: 1px solid var(--border-color);
@@ -2011,7 +2010,7 @@
2011
  font-size: 14px;
2012
  }
2013
 
2014
- /* ==================== QR CODE MODAL ==================== */
2015
  .qr-modal {
2016
  position: fixed;
2017
  top: 0;
@@ -2082,7 +2081,7 @@
2082
  transform: translateY(-2px);
2083
  }
2084
 
2085
- /* ==================== MEDIA PREVIEW PANEL ==================== */
2086
  .media-preview-panel {
2087
  position: fixed;
2088
  bottom: -100%;
@@ -2165,14 +2164,14 @@
2165
  object-fit: cover;
2166
  }
2167
 
2168
- /* ==================== ENHANCED HOVER EFFECTS ==================== */
2169
  .danger-hover:hover {
2170
  color: var(--danger) !important;
2171
  transform: scale(1.08);
2172
  filter: drop-shadow(0 0 8px rgba(240, 71, 71, 0.6));
2173
  }
2174
 
2175
- /* Keep text white on danger button hover */
2176
  .save-btn.danger-hover:hover {
2177
  color: white !important;
2178
  transform: scale(1.02) !important;
@@ -2190,7 +2189,7 @@
2190
  filter: drop-shadow(0 0 8px rgba(88, 101, 242, 0.6));
2191
  }
2192
 
2193
- /* ==================== MOBILE RESPONSIVENESS IMPROVEMENTS ==================== */
2194
  @media (max-width: 768px) {
2195
  .custom-dialog-content {
2196
  min-width: 90%;
@@ -2213,7 +2212,7 @@
2213
  }
2214
 
2215
  @media (max-width: 480px) {
2216
- /* Mobile layout: full-width chat area, hidden sidebars */
2217
  .main-container {
2218
  grid-template-columns: 1fr;
2219
  height: calc(100vh - 68px);
@@ -2271,7 +2270,7 @@
2271
  display: inline-block;
2272
  }
2273
 
2274
- /* Overlay for sidebar */
2275
  .sidebar-overlay {
2276
  display: none;
2277
  position: fixed;
@@ -2296,7 +2295,7 @@
2296
  max-width: 100%;
2297
  }
2298
 
2299
- /* Ensure chat header is visible and doesn't get cut off */
2300
  .chat-header {
2301
  padding: 12px 16px;
2302
  flex-shrink: 0;
@@ -2328,7 +2327,7 @@
2328
  display: none;
2329
  }
2330
 
2331
- /* Message container on mobile */
2332
  #messages-container {
2333
  flex: 1;
2334
  min-height: 0;
@@ -2350,7 +2349,7 @@
2350
  min-width: 32px;
2351
  }
2352
 
2353
- /* Input area on mobile */
2354
  .message-input-area {
2355
  flex-shrink: 0;
2356
  padding: 12px 16px;
@@ -2372,7 +2371,6 @@
2372
  font-size: 14px;
2373
  }
2374
 
2375
- /* Scrollbar adjustment */
2376
  .chat-scroll-rail {
2377
  right: 4px;
2378
  width: 8px;
@@ -2391,7 +2389,7 @@
2391
  width: 100%;
2392
  }
2393
 
2394
- /* Profile panel adjustments */
2395
  .profile-panel {
2396
  position: fixed;
2397
  left: 0;
@@ -2407,7 +2405,6 @@
2407
  border-radius: 0;
2408
  }
2409
 
2410
- /* Friends modal adjustments */
2411
  .friends-modal {
2412
  position: fixed;
2413
  left: 0;
@@ -2444,7 +2441,7 @@
2444
  </style>
2445
  </head>
2446
  <body>
2447
- <!-- AUTH CONTAINER -->
2448
  <div class="auth-container" id="authContainer">
2449
  <div class="auth-box">
2450
  <h2 id="authTitle">Login</h2>
@@ -2485,9 +2482,9 @@
2485
  </div>
2486
  </div>
2487
 
2488
- <!-- MAIN APP -->
2489
  <div class="main-container" id="mainApp">
2490
- <!-- GUILD SIDEBAR -->
2491
  <div class="guild-sidebar">
2492
  <div class="guild-item active" onclick="showSection('chat')" title="Chat">
2493
  <i class="fas fa-comment"></i>
@@ -2510,7 +2507,7 @@
2510
  </div>
2511
  </div>
2512
 
2513
- <!-- CHANNEL SIDEBAR -->
2514
  <div class="channel-sidebar" id="channelSidebar">
2515
  <div class="sidebar-header">
2516
  <h3>FireTech</h3>
@@ -2531,11 +2528,11 @@
2531
  </div>
2532
  </div>
2533
  <div class="channels-list" id="channelsList">
2534
- <!-- Direct messages will be populated here by loadFriendsForDM() -->
2535
  </div>
2536
  </div>
2537
 
2538
- <!-- CHAT AREA -->
2539
  <div class="chat-area">
2540
  <div class="chat-header">
2541
  <div class="chat-header-left">
@@ -2545,7 +2542,7 @@
2545
  <div class="chat-header-title" id="chatTitle">💬 Select a friend to chat</div>
2546
  </div>
2547
  <div class="chat-header-actions">
2548
- <span class="build-badge mobile-essential" id="buildVersionBadge" title="Frontend build version">v2.3.0</span>
2549
  <button class="header-btn" onclick="toggleArchiveChat()" title="Archive chat">
2550
  <i class="fas fa-archive"></i>
2551
  </button>
@@ -2617,14 +2614,14 @@
2617
  </div>
2618
  </div>
2619
 
2620
- <!-- MEMBERS SIDEBAR -->
2621
  <div class="members-sidebar">
2622
  <div class="members-header">👥 Online Members</div>
2623
  <div class="members-list" id="membersList"></div>
2624
  </div>
2625
  </div>
2626
 
2627
- <!-- PROFILE PANEL -->
2628
  <div class="profile-panel" id="profilePanel">
2629
  <button class="profile-close" onclick="closeProfile()"><i class="fas fa-times"></i></button>
2630
  <div class="profile-header">
@@ -2638,7 +2635,7 @@
2638
  <button class="tab-btn" onclick="switchProfileTab('inventory')">Inventory</button>
2639
  </div>
2640
 
2641
- <!-- Profile Tab -->
2642
  <div class="tab-content active" id="profileTab">
2643
  <div class="form-field">
2644
  <label>Profile Picture</label>
@@ -2666,14 +2663,13 @@
2666
  <button class="save-btn" onclick="saveProfile()">Save Profile</button>
2667
  </div>
2668
 
2669
- <!-- Inventory Tab -->
2670
  <div class="tab-content" id="inventoryTab">
2671
  <div id="inventoryList"></div>
2672
  </div>
2673
  </div>
2674
  </div>
2675
 
2676
- <!-- FRIENDS MODAL -->
2677
  <div class="modal hidden" id="friendsModal">
2678
  <div class="modal-content">
2679
  <h2><i class="fas fa-users"></i> Friends & Friend Requests</h2>
@@ -2685,7 +2681,7 @@
2685
  </div>
2686
  </div>
2687
 
2688
- <!-- SHOP MODAL -->
2689
  <div class="modal hidden" id="shopModal">
2690
  <div class="modal-content">
2691
  <h2><i class="fas fa-store"></i> Shop</h2>
@@ -2695,21 +2691,21 @@
2695
  </div>
2696
  </div>
2697
 
2698
- <!-- TOAST NOTIFICATIONS -->
2699
  <div class="toast-container" id="toastContainer"></div>
2700
 
2701
- <!-- STICKER/GIF PICKER -->
2702
  <div class="picker-panel" id="pickerPanel">
2703
  <div class="picker-tabs">
2704
  <button class="picker-tab active" onclick="switchPickerTab('stickers')">😀 Stickers</button>
2705
  <button class="picker-tab" onclick="switchPickerTab('gifs')">🎬 GIFs</button>
2706
  </div>
2707
  <div class="picker-content" id="pickerContent">
2708
- <!-- Will be populated by JS -->
2709
  </div>
2710
  </div>
2711
 
2712
- <!-- VOICE RECORDER -->
2713
  <div class="voice-recorder" id="voiceRecorder">
2714
  <div class="record-timer" id="recordTimer">00:00</div>
2715
  <button class="record-button" id="recordButton" onclick="toggleRecording()">
@@ -2725,7 +2721,7 @@
2725
  </div>
2726
  </div>
2727
 
2728
- <!-- SETTINGS PANEL -->
2729
  <div class="settings-panel" id="settingsPanel">
2730
  <div class="settings-header">
2731
  <h2>⚙️ Settings</h2>
@@ -2741,7 +2737,7 @@
2741
  <div class="settings-nav-item" onclick="switchSettingsTab('stats')">📊 Statistics</div>
2742
  </div>
2743
  <div class="settings-content">
2744
- <!-- Account Settings -->
2745
  <div class="settings-section active" id="accountSettings">
2746
  <h3>Account Settings</h3>
2747
  <div class="settings-item">
@@ -2768,7 +2764,7 @@
2768
  </div>
2769
  </div>
2770
 
2771
- <!-- Privacy Settings -->
2772
  <div class="settings-section" id="privacySettings">
2773
  <h3>Privacy & Safety</h3>
2774
  <div class="settings-item">
@@ -2795,7 +2791,7 @@
2795
  </div>
2796
  </div>
2797
 
2798
- <!-- Chat Settings -->
2799
  <div class="settings-section" id="chatSettings">
2800
  <h3>Chat Settings</h3>
2801
  <div class="settings-item">
@@ -2837,7 +2833,7 @@
2837
  </div>
2838
  </div>
2839
 
2840
- <!-- Notification Settings -->
2841
  <div class="settings-section" id="notificationSettings">
2842
  <h3>Notifications</h3>
2843
  <div class="settings-item">
@@ -2879,7 +2875,7 @@
2879
  </div>
2880
  </div>
2881
 
2882
- <!-- Appearance Settings -->
2883
  <div class="settings-section" id="appearanceSettings">
2884
  <h3>Appearance</h3>
2885
  <div class="settings-item">
@@ -2939,7 +2935,7 @@
2939
  </div>
2940
  </div>
2941
 
2942
- <!-- Statistics Settings -->
2943
  <div class="settings-section" id="statsSettings">
2944
  <h3>📊 Your Statistics</h3>
2945
  <div class="settings-item" style="padding: 20px; background: var(--bg-color); border-radius: 8px;">
@@ -2973,7 +2969,7 @@
2973
  </div>
2974
  </div>
2975
 
2976
- <!-- COMMUNITIES MODAL -->
2977
  <div class="modal hidden" id="communitiesModal">
2978
  <div class="modal-content">
2979
  <h2><i class="fas fa-globe"></i> Communities</h2>
@@ -2983,7 +2979,7 @@
2983
  </div>
2984
  </div>
2985
 
2986
- <!-- GROUPS MODAL -->
2987
  <div class="modal hidden" id="groupsModal">
2988
  <div class="modal-content">
2989
  <h2><i class="fas fa-user-group"></i> Groups</h2>
@@ -2994,7 +2990,7 @@
2994
 
2995
  </div>
2996
 
2997
- <!-- COMMUNITY DETAILS MODAL -->
2998
  <div class="community-modal" id="communityDetailsModal">
2999
  <div class="community-modal-content">
3000
  <div class="community-modal-header">
@@ -3005,7 +3001,7 @@
3005
  </div>
3006
  </div>
3007
 
3008
- <!-- QR CODE MODAL -->
3009
  <div class="qr-modal" id="qrModal">
3010
  <div class="qr-modal-content">
3011
  <h2 class="qr-modal-title">📱 Friend Request QR Code</h2>
@@ -3018,7 +3014,7 @@
3018
  </div>
3019
  </div>
3020
 
3021
- <!-- MEDIA PREVIEW PANEL -->
3022
  <div class="media-preview-panel" id="mediaPreviewPanel">
3023
  <div class="media-preview-header">
3024
  <h3 class="media-preview-title">📸 Copied Media</h3>
@@ -3027,11 +3023,11 @@
3027
  </button>
3028
  </div>
3029
  <div class="media-preview-content" id="mediaPreviewContent">
3030
- <!-- Copied media will be displayed here -->
3031
  </div>
3032
  </div>
3033
 
3034
- <!-- MOBILE BOTTOM NAVIGATION -->
3035
  <div class="mobile-bottom-nav" id="mobileBottomNav">
3036
  <button class="mobile-nav-item active" onclick="showMobileSection('chats')" data-section="chats">
3037
  <i class="fas fa-comment"></i>
@@ -3055,10 +3051,10 @@
3055
  </button>
3056
  </div>
3057
 
3058
- <!-- SIDEBAR OVERLAY FOR MOBILE -->
3059
  <div class="sidebar-overlay" id="sidebarOverlay" onclick="closeMobileSidebar()"></div>
3060
 
3061
- <!-- MOBILE MEMBERS PANEL -->
3062
  <div class="mobile-members-panel" id="mobileMembersPanel">
3063
  <div class="mobile-members-header">
3064
  <h3>👥 Online Members</h3>
@@ -3067,11 +3063,11 @@
3067
  </button>
3068
  </div>
3069
  <div class="mobile-members-list" id="mobileMembersList">
3070
- <!-- Members will be populated here -->
3071
  </div>
3072
  </div>
3073
 
3074
- <!-- CUSTOM DIALOG MODAL (replaces alert/confirm/prompt) -->
3075
  <div id="customDialog" style="display: none;"></div>
3076
 
3077
  <script src="/socket.io/socket.io.js"></script>
 
27
  --danger: #f04747;
28
  }
29
 
30
+
31
  [data-theme="light"] {
32
  --primary-color: #5865f2;
33
  --secondary-color: #99aab5;
 
50
  overflow: hidden;
51
  }
52
 
53
+
54
  .auth-container {
55
  position: fixed;
56
  top: 0;
 
187
  cursor: pointer;
188
  }
189
 
190
+
191
  .main-container {
192
  display: grid;
193
  grid-template-columns: 72px 240px 1fr 240px;
 
199
  display: none;
200
  }
201
 
202
+
203
  .guild-sidebar {
204
  background: var(--sidebar-bg);
205
  display: flex;
 
241
  margin: 8px 0;
242
  }
243
 
244
+
245
  .channel-sidebar {
246
  background: var(--sidebar-bg);
247
  border-right: 1px solid var(--border-color);
 
348
  font-weight: bold;
349
  }
350
 
351
+
352
  .chat-area {
353
  position: relative;
354
  display: flex;
 
431
  -webkit-overflow-scrolling: touch;
432
  }
433
 
434
+
435
  #messages-container::-webkit-scrollbar {
436
  width: 0;
437
  height: 0;
 
682
  cursor: pointer;
683
  }
684
 
685
+
686
 
687
  .input-area {
688
  padding: 16px 24px;
 
735
  display: none;
736
  }
737
 
738
+
739
  .members-sidebar {
740
  background: var(--sidebar-bg);
741
  border-left: 1px solid var(--border-color);
 
784
  background: var(--text-secondary);
785
  }
786
 
787
+
788
  .profile-panel {
789
  position: fixed;
790
  right: 0;
 
1076
  to { opacity: 0; transform: translateX(100%); }
1077
  }
1078
 
1079
+
1080
  .picker-panel {
1081
  position: absolute;
1082
  bottom: 60px;
 
1147
  object-fit: cover;
1148
  }
1149
 
1150
+
1151
  .voice-recorder {
1152
  position: absolute;
1153
  bottom: 70px;
 
1216
  color: var(--danger);
1217
  }
1218
 
1219
+
1220
  .settings-panel {
1221
  position: fixed;
1222
  top: 0;
 
1367
  transform: translateX(20px);
1368
  }
1369
 
1370
+
1371
  .modal {
1372
  position: fixed;
1373
  top: 0;
 
1396
  overflow-y: auto;
1397
  }
1398
 
1399
+
1400
  ::-webkit-scrollbar {
1401
  width: 8px;
1402
  }
 
1414
  background: #5a5d60;
1415
  }
1416
 
1417
+
1418
  .mobile-bottom-nav {
1419
  display: none;
1420
  position: fixed;
 
1430
  box-shadow: 0 -4px 20px rgba(0,0,0,0.3);
1431
  }
1432
 
1433
+
1434
  .mobile-members-panel {
1435
  position: fixed;
1436
  right: -100%;
 
1512
  text-align: center;
1513
  }
1514
 
1515
+
1516
  @media (max-width: 1200px) {
1517
  .main-container {
1518
  grid-template-columns: 72px 240px 1fr;
 
1532
  }
1533
  }
1534
 
1535
+
1536
  @media (max-width: 768px) {
1537
  body {
1538
  overflow: hidden;
 
1545
  max-height: -webkit-fill-available;
1546
  }
1547
 
1548
+
1549
  .guild-sidebar {
1550
  padding: 2px 0;
1551
  width: 45px;
 
1581
  display: flex;
1582
  }
1583
 
1584
+
1585
  .channel-sidebar.show::before {
1586
  content: '';
1587
  position: fixed;
 
1593
  z-index: -1;
1594
  }
1595
 
1596
+
1597
  .chat-area {
1598
  height: 100vh;
1599
  height: 100dvh;
 
1641
  display: none;
1642
  }
1643
 
1644
+
1645
  #messages-container {
1646
  flex: 1;
1647
  min-height: 0;
 
1674
  max-width: 90%;
1675
  }
1676
 
1677
+
1678
  .input-area {
1679
  padding: 12px 16px;
1680
  gap: 8px;
 
1694
  flex-shrink: 0;
1695
  }
1696
 
1697
+
1698
  .chat-scroll-rail {
1699
  right: 4px;
1700
  top: 64px;
 
1706
  min-height: 40px;
1707
  }
1708
 
1709
+
1710
  .picker-panel {
1711
  width: 100%;
1712
  left: 0;
 
1727
  padding: 8px;
1728
  }
1729
 
1730
+
1731
  .settings-panel {
1732
  width: 100%;
1733
  right: -100%;
 
1762
  border-bottom-color: var(--primary-color);
1763
  }
1764
 
1765
+
1766
  .toast-container {
1767
  top: 10px;
1768
  right: 10px;
 
1774
  padding: 12px 16px;
1775
  }
1776
 
1777
+
1778
  .profile-panel {
1779
  width: 100%;
1780
  }
1781
 
 
1782
  .members-sidebar {
1783
  display: none !important;
1784
  }
1785
  }
1786
 
1787
+
1788
  .custom-dialog {
1789
  position: fixed;
1790
  top: 0;
 
1902
  transform: translateY(-2px);
1903
  }
1904
 
1905
+
1906
  .community-modal {
1907
  position: fixed;
1908
  top: 0;
 
1974
  background: rgba(255, 193, 7, 0.1);
1975
  }
1976
 
1977
+
1978
  .friend-search-container {
1979
  padding: 12px 16px;
1980
  border-bottom: 1px solid var(--border-color);
 
2010
  font-size: 14px;
2011
  }
2012
 
2013
+
2014
  .qr-modal {
2015
  position: fixed;
2016
  top: 0;
 
2081
  transform: translateY(-2px);
2082
  }
2083
 
2084
+
2085
  .media-preview-panel {
2086
  position: fixed;
2087
  bottom: -100%;
 
2164
  object-fit: cover;
2165
  }
2166
 
2167
+
2168
  .danger-hover:hover {
2169
  color: var(--danger) !important;
2170
  transform: scale(1.08);
2171
  filter: drop-shadow(0 0 8px rgba(240, 71, 71, 0.6));
2172
  }
2173
 
2174
+
2175
  .save-btn.danger-hover:hover {
2176
  color: white !important;
2177
  transform: scale(1.02) !important;
 
2189
  filter: drop-shadow(0 0 8px rgba(88, 101, 242, 0.6));
2190
  }
2191
 
2192
+
2193
  @media (max-width: 768px) {
2194
  .custom-dialog-content {
2195
  min-width: 90%;
 
2212
  }
2213
 
2214
  @media (max-width: 480px) {
2215
+
2216
  .main-container {
2217
  grid-template-columns: 1fr;
2218
  height: calc(100vh - 68px);
 
2270
  display: inline-block;
2271
  }
2272
 
2273
+
2274
  .sidebar-overlay {
2275
  display: none;
2276
  position: fixed;
 
2295
  max-width: 100%;
2296
  }
2297
 
2298
+
2299
  .chat-header {
2300
  padding: 12px 16px;
2301
  flex-shrink: 0;
 
2327
  display: none;
2328
  }
2329
 
2330
+
2331
  #messages-container {
2332
  flex: 1;
2333
  min-height: 0;
 
2349
  min-width: 32px;
2350
  }
2351
 
2352
+
2353
  .message-input-area {
2354
  flex-shrink: 0;
2355
  padding: 12px 16px;
 
2371
  font-size: 14px;
2372
  }
2373
 
 
2374
  .chat-scroll-rail {
2375
  right: 4px;
2376
  width: 8px;
 
2389
  width: 100%;
2390
  }
2391
 
2392
+
2393
  .profile-panel {
2394
  position: fixed;
2395
  left: 0;
 
2405
  border-radius: 0;
2406
  }
2407
 
 
2408
  .friends-modal {
2409
  position: fixed;
2410
  left: 0;
 
2441
  </style>
2442
  </head>
2443
  <body>
2444
+
2445
  <div class="auth-container" id="authContainer">
2446
  <div class="auth-box">
2447
  <h2 id="authTitle">Login</h2>
 
2482
  </div>
2483
  </div>
2484
 
2485
+
2486
  <div class="main-container" id="mainApp">
2487
+
2488
  <div class="guild-sidebar">
2489
  <div class="guild-item active" onclick="showSection('chat')" title="Chat">
2490
  <i class="fas fa-comment"></i>
 
2507
  </div>
2508
  </div>
2509
 
2510
+
2511
  <div class="channel-sidebar" id="channelSidebar">
2512
  <div class="sidebar-header">
2513
  <h3>FireTech</h3>
 
2528
  </div>
2529
  </div>
2530
  <div class="channels-list" id="channelsList">
2531
+
2532
  </div>
2533
  </div>
2534
 
2535
+
2536
  <div class="chat-area">
2537
  <div class="chat-header">
2538
  <div class="chat-header-left">
 
2542
  <div class="chat-header-title" id="chatTitle">💬 Select a friend to chat</div>
2543
  </div>
2544
  <div class="chat-header-actions">
2545
+ <span class="build-badge mobile-essential" id="buildVersionBadge" title="Frontend build version">v2.3.1</span>
2546
  <button class="header-btn" onclick="toggleArchiveChat()" title="Archive chat">
2547
  <i class="fas fa-archive"></i>
2548
  </button>
 
2614
  </div>
2615
  </div>
2616
 
2617
+
2618
  <div class="members-sidebar">
2619
  <div class="members-header">👥 Online Members</div>
2620
  <div class="members-list" id="membersList"></div>
2621
  </div>
2622
  </div>
2623
 
2624
+
2625
  <div class="profile-panel" id="profilePanel">
2626
  <button class="profile-close" onclick="closeProfile()"><i class="fas fa-times"></i></button>
2627
  <div class="profile-header">
 
2635
  <button class="tab-btn" onclick="switchProfileTab('inventory')">Inventory</button>
2636
  </div>
2637
 
2638
+
2639
  <div class="tab-content active" id="profileTab">
2640
  <div class="form-field">
2641
  <label>Profile Picture</label>
 
2663
  <button class="save-btn" onclick="saveProfile()">Save Profile</button>
2664
  </div>
2665
 
2666
+
2667
  <div class="tab-content" id="inventoryTab">
2668
  <div id="inventoryList"></div>
2669
  </div>
2670
  </div>
2671
  </div>
2672
 
 
2673
  <div class="modal hidden" id="friendsModal">
2674
  <div class="modal-content">
2675
  <h2><i class="fas fa-users"></i> Friends & Friend Requests</h2>
 
2681
  </div>
2682
  </div>
2683
 
2684
+
2685
  <div class="modal hidden" id="shopModal">
2686
  <div class="modal-content">
2687
  <h2><i class="fas fa-store"></i> Shop</h2>
 
2691
  </div>
2692
  </div>
2693
 
2694
+
2695
  <div class="toast-container" id="toastContainer"></div>
2696
 
2697
+
2698
  <div class="picker-panel" id="pickerPanel">
2699
  <div class="picker-tabs">
2700
  <button class="picker-tab active" onclick="switchPickerTab('stickers')">😀 Stickers</button>
2701
  <button class="picker-tab" onclick="switchPickerTab('gifs')">🎬 GIFs</button>
2702
  </div>
2703
  <div class="picker-content" id="pickerContent">
2704
+
2705
  </div>
2706
  </div>
2707
 
2708
+
2709
  <div class="voice-recorder" id="voiceRecorder">
2710
  <div class="record-timer" id="recordTimer">00:00</div>
2711
  <button class="record-button" id="recordButton" onclick="toggleRecording()">
 
2721
  </div>
2722
  </div>
2723
 
2724
+
2725
  <div class="settings-panel" id="settingsPanel">
2726
  <div class="settings-header">
2727
  <h2>⚙️ Settings</h2>
 
2737
  <div class="settings-nav-item" onclick="switchSettingsTab('stats')">📊 Statistics</div>
2738
  </div>
2739
  <div class="settings-content">
2740
+
2741
  <div class="settings-section active" id="accountSettings">
2742
  <h3>Account Settings</h3>
2743
  <div class="settings-item">
 
2764
  </div>
2765
  </div>
2766
 
2767
+
2768
  <div class="settings-section" id="privacySettings">
2769
  <h3>Privacy & Safety</h3>
2770
  <div class="settings-item">
 
2791
  </div>
2792
  </div>
2793
 
2794
+
2795
  <div class="settings-section" id="chatSettings">
2796
  <h3>Chat Settings</h3>
2797
  <div class="settings-item">
 
2833
  </div>
2834
  </div>
2835
 
2836
+
2837
  <div class="settings-section" id="notificationSettings">
2838
  <h3>Notifications</h3>
2839
  <div class="settings-item">
 
2875
  </div>
2876
  </div>
2877
 
2878
+
2879
  <div class="settings-section" id="appearanceSettings">
2880
  <h3>Appearance</h3>
2881
  <div class="settings-item">
 
2935
  </div>
2936
  </div>
2937
 
2938
+
2939
  <div class="settings-section" id="statsSettings">
2940
  <h3>📊 Your Statistics</h3>
2941
  <div class="settings-item" style="padding: 20px; background: var(--bg-color); border-radius: 8px;">
 
2969
  </div>
2970
  </div>
2971
 
2972
+
2973
  <div class="modal hidden" id="communitiesModal">
2974
  <div class="modal-content">
2975
  <h2><i class="fas fa-globe"></i> Communities</h2>
 
2979
  </div>
2980
  </div>
2981
 
2982
+
2983
  <div class="modal hidden" id="groupsModal">
2984
  <div class="modal-content">
2985
  <h2><i class="fas fa-user-group"></i> Groups</h2>
 
2990
 
2991
  </div>
2992
 
2993
+
2994
  <div class="community-modal" id="communityDetailsModal">
2995
  <div class="community-modal-content">
2996
  <div class="community-modal-header">
 
3001
  </div>
3002
  </div>
3003
 
3004
+
3005
  <div class="qr-modal" id="qrModal">
3006
  <div class="qr-modal-content">
3007
  <h2 class="qr-modal-title">📱 Friend Request QR Code</h2>
 
3014
  </div>
3015
  </div>
3016
 
3017
+
3018
  <div class="media-preview-panel" id="mediaPreviewPanel">
3019
  <div class="media-preview-header">
3020
  <h3 class="media-preview-title">📸 Copied Media</h3>
 
3023
  </button>
3024
  </div>
3025
  <div class="media-preview-content" id="mediaPreviewContent">
3026
+
3027
  </div>
3028
  </div>
3029
 
3030
+
3031
  <div class="mobile-bottom-nav" id="mobileBottomNav">
3032
  <button class="mobile-nav-item active" onclick="showMobileSection('chats')" data-section="chats">
3033
  <i class="fas fa-comment"></i>
 
3051
  </button>
3052
  </div>
3053
 
3054
+
3055
  <div class="sidebar-overlay" id="sidebarOverlay" onclick="closeMobileSidebar()"></div>
3056
 
3057
+
3058
  <div class="mobile-members-panel" id="mobileMembersPanel">
3059
  <div class="mobile-members-header">
3060
  <h3>👥 Online Members</h3>
 
3063
  </button>
3064
  </div>
3065
  <div class="mobile-members-list" id="mobileMembersList">
3066
+
3067
  </div>
3068
  </div>
3069
 
3070
+
3071
  <div id="customDialog" style="display: none;"></div>
3072
 
3073
  <script src="/socket.io/socket.io.js"></script>
init-db.js CHANGED
@@ -66,15 +66,15 @@ const defaultItems = [
66
 
67
  async function initializeDatabase() {
68
  try {
69
- // Clear existing items
70
  await ShopItem.deleteMany({});
71
  console.log('Cleared existing shop items');
72
 
73
- // Insert new items
74
  await ShopItem.insertMany(defaultItems);
75
  console.log(`✅ Successfully added ${defaultItems.length} shop items to database`);
76
 
77
- // List all items
78
  const items = await ShopItem.find();
79
  console.log('\n📦 Shop Items:');
80
  items.forEach(item => {
 
66
 
67
  async function initializeDatabase() {
68
  try {
69
+
70
  await ShopItem.deleteMany({});
71
  console.log('Cleared existing shop items');
72
 
73
+
74
  await ShopItem.insertMany(defaultItems);
75
  console.log(`✅ Successfully added ${defaultItems.length} shop items to database`);
76
 
77
+
78
  const items = await ShopItem.find();
79
  console.log('\n📦 Shop Items:');
80
  items.forEach(item => {
mobile-connect.ps1 CHANGED
@@ -1,12 +1,11 @@
1
- # Complete automated mobile connection setup
2
- # Run as Administrator
3
 
4
  Write-Host "╔════════════════════════════════════════╗" -ForegroundColor Cyan
5
  Write-Host "║ Mobile Connection Setup - Complete ║" -ForegroundColor Cyan
6
  Write-Host "╚════════════════════════════════════════╝" -ForegroundColor Cyan
7
  Write-Host ""
8
 
9
- # Step 1: Get Wi-Fi IP
10
  Write-Host "Finding your PC Wi-Fi IP address..." -ForegroundColor Yellow
11
  $wifiIP = $null
12
  try {
@@ -26,14 +25,14 @@ if (!$wifiIP) {
26
  Write-Host "Found Wi-Fi IP: $wifiIP" -ForegroundColor Green
27
  Write-Host ""
28
 
29
- # Step 2: Kill existing Node
30
  Write-Host "Stopping existing servers..." -ForegroundColor Yellow
31
  taskkill /F /IM node.exe 2>$null | Out-Null
32
  Start-Sleep -Seconds 2
33
  Write-Host "OK - Stopped" -ForegroundColor Green
34
  Write-Host ""
35
 
36
- # Step 3: Ensure certificate exists
37
  Write-Host "Checking certificate..." -ForegroundColor Yellow
38
  $pfxPath = "C:\Users\dell\Downloads\message\certs\lan-localhost.pfx"
39
  if (!(Test-Path $pfxPath)) {
@@ -44,7 +43,7 @@ if (!(Test-Path $pfxPath)) {
44
  Write-Host "OK - Certificate ready" -ForegroundColor Green
45
  Write-Host ""
46
 
47
- # Step 4: Add firewall rule
48
  Write-Host "Adding firewall rule..." -ForegroundColor Yellow
49
  try {
50
  Remove-NetFirewallRule -DisplayName "Chat App 3001" -ErrorAction SilentlyContinue
@@ -55,7 +54,7 @@ try {
55
  }
56
  Write-Host ""
57
 
58
- # Step 5: Start server
59
  Write-Host "Starting server..." -ForegroundColor Yellow
60
  Write-Host ""
61
 
 
1
+
 
2
 
3
  Write-Host "╔════════════════════════════════════════╗" -ForegroundColor Cyan
4
  Write-Host "║ Mobile Connection Setup - Complete ║" -ForegroundColor Cyan
5
  Write-Host "╚════════════════════════════════════════╝" -ForegroundColor Cyan
6
  Write-Host ""
7
 
8
+
9
  Write-Host "Finding your PC Wi-Fi IP address..." -ForegroundColor Yellow
10
  $wifiIP = $null
11
  try {
 
25
  Write-Host "Found Wi-Fi IP: $wifiIP" -ForegroundColor Green
26
  Write-Host ""
27
 
28
+
29
  Write-Host "Stopping existing servers..." -ForegroundColor Yellow
30
  taskkill /F /IM node.exe 2>$null | Out-Null
31
  Start-Sleep -Seconds 2
32
  Write-Host "OK - Stopped" -ForegroundColor Green
33
  Write-Host ""
34
 
35
+
36
  Write-Host "Checking certificate..." -ForegroundColor Yellow
37
  $pfxPath = "C:\Users\dell\Downloads\message\certs\lan-localhost.pfx"
38
  if (!(Test-Path $pfxPath)) {
 
43
  Write-Host "OK - Certificate ready" -ForegroundColor Green
44
  Write-Host ""
45
 
46
+
47
  Write-Host "Adding firewall rule..." -ForegroundColor Yellow
48
  try {
49
  Remove-NetFirewallRule -DisplayName "Chat App 3001" -ErrorAction SilentlyContinue
 
54
  }
55
  Write-Host ""
56
 
57
+
58
  Write-Host "Starting server..." -ForegroundColor Yellow
59
  Write-Host ""
60
 
package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
  {
2
  "name": "discord-like-app",
3
- "version": "2.2.9",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "discord-like-app",
9
- "version": "2.2.9",
10
  "dependencies": {
11
  "bcryptjs": "^2.4.3",
12
  "body-parser": "^1.20.2",
 
1
  {
2
  "name": "discord-like-app",
3
+ "version": "2.3.0",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "discord-like-app",
9
+ "version": "2.3.0",
10
  "dependencies": {
11
  "bcryptjs": "^2.4.3",
12
  "body-parser": "^1.20.2",
package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "discord-like-app",
3
- "version": "2.3.0",
4
  "main": "server.js",
5
  "scripts": {
6
  "dev": "nodemon server.js",
 
1
  {
2
  "name": "discord-like-app",
3
+ "version": "2.3.1",
4
  "main": "server.js",
5
  "scripts": {
6
  "dev": "nodemon server.js",
server.js CHANGED
@@ -14,7 +14,7 @@ require('dotenv').config();
14
 
15
  const app = express();
16
 
17
- // Configure CORS
18
  const cors = require('cors');
19
  const allowedOrigins = process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',').map(o => o.trim()).filter(Boolean) : ['*'];
20
 
@@ -42,7 +42,6 @@ app.use(cors({
42
  allowedHeaders: ['Content-Type', 'Authorization']
43
  }));
44
 
45
- // Middleware
46
  app.use(bodyParser.json({ limit: '50mb' }));
47
  app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
48
 
@@ -67,8 +66,6 @@ app.get('/api/version', (req, res) => {
67
  }
68
  });
69
 
70
- // ======================== DATABASE CONNECTION ========================
71
-
72
  const connectionString = process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/discord-app';
73
  const isLocalConnection = connectionString.includes('localhost') || connectionString.includes('127.0.0.1');
74
 
@@ -79,7 +76,7 @@ const sequelize = new Sequelize(connectionString, {
79
  dialectOptions: isLocalConnection ? {} : { ssl: { require: true, rejectUnauthorized: false } }
80
  });
81
 
82
- // ======================== MODELS ========================
83
 
84
  const User = sequelize.define('User', {
85
  id: {
@@ -215,7 +212,7 @@ const DirectMessage = sequelize.define('DirectMessage', {
215
  mediaUrl: DataTypes.TEXT
216
  });
217
 
218
- // ======================== ASSOCIATIONS ========================
219
 
220
  User.belongsToMany(User, {
221
  as: 'friends',
@@ -245,7 +242,7 @@ FriendRequest.belongsTo(User, { as: 'to', foreignKey: 'toId' });
245
  DirectMessage.belongsTo(User, { as: 'sender', foreignKey: 'fromId' });
246
  DirectMessage.belongsTo(User, { as: 'recipient', foreignKey: 'toId' });
247
 
248
- // ======================== AUTH MIDDLEWARE ========================
249
 
250
  const authenticateToken = (req, res, next) => {
251
  const authHeader = req.headers['authorization'];
@@ -260,7 +257,7 @@ const authenticateToken = (req, res, next) => {
260
  });
261
  };
262
 
263
- // ======================== AUTH ROUTES ========================
264
 
265
  app.post('/api/register', async (req, res) => {
266
  try {
@@ -355,7 +352,6 @@ app.post('/api/reset-password', async (req, res) => {
355
  }
356
  });
357
 
358
- // ======================== PROFILE ROUTES ========================
359
 
360
  app.get('/api/profile/:userId', authenticateToken, async (req, res) => {
361
  try {
@@ -457,7 +453,7 @@ app.post('/api/profile/upload', authenticateToken, async (req, res) => {
457
  }
458
  });
459
 
460
- // ======================== FRIENDS ROUTES ========================
461
 
462
  app.post('/api/friends/request', authenticateToken, async (req, res) => {
463
  try {
@@ -583,7 +579,7 @@ app.get('/api/friends', authenticateToken, async (req, res) => {
583
  }
584
  });
585
 
586
- // ======================== DIRECT MESSAGES ROUTES ========================
587
 
588
  app.get('/api/dms/:friendId', authenticateToken, async (req, res) => {
589
  try {
@@ -658,7 +654,7 @@ app.delete('/api/dms/:friendId', authenticateToken, async (req, res) => {
658
  }
659
  });
660
 
661
- // Notify the other user to clear their chat UI as well
662
  emitToUser(friendId, 'dm history cleared', {
663
  fromUserId: req.user.userId,
664
  clearedAt: new Date().toISOString()
@@ -670,7 +666,7 @@ app.delete('/api/dms/:friendId', authenticateToken, async (req, res) => {
670
  }
671
  });
672
 
673
- // ======================== SHOP ROUTES ========================
674
 
675
  app.get('/api/shop', authenticateToken, async (req, res) => {
676
  try {
@@ -713,7 +709,7 @@ app.get('/api/inventory', authenticateToken, async (req, res) => {
713
  }
714
  });
715
 
716
- // ======================== SOCKET.IO ========================
717
 
718
  function createServer(appInstance) {
719
  const pfxPath = process.env.SSL_PFX_PATH;
@@ -862,7 +858,7 @@ io.on('connection', (socket) => {
862
  timestamp: dm.createdAt
863
  };
864
 
865
- // Always deliver directly to both users so messages arrive even if a DM room isn't currently joined.
866
  emitToUser(socket.userId, 'dm message', dmPayload);
867
  const deliveredToRecipient = emitToUser(toUserId, 'dm message', dmPayload);
868
 
@@ -1176,7 +1172,6 @@ io.on('connection', (socket) => {
1176
  });
1177
  });
1178
 
1179
- // ======================== INITIALIZATION ========================
1180
 
1181
  function getLocalNetworkAddress() {
1182
  const interfaces = os.networkInterfaces();
@@ -1193,10 +1188,10 @@ function getLocalNetworkAddress() {
1193
  const PORT = process.env.PORT || 3000;
1194
  const HOST = process.env.HOST || '0.0.0.0';
1195
 
1196
- // Migrate ENUM types for media support
1197
  async function migrateMediaTypeEnum() {
1198
  try {
1199
- // Check if we need to update the ENUM type
1200
  const [results] = await sequelize.query(`
1201
  SELECT EXISTS (
1202
  SELECT 1 FROM pg_type t
@@ -1209,7 +1204,7 @@ async function migrateMediaTypeEnum() {
1209
  if (!results[0].has_voice) {
1210
  console.log('📦 Migrating mediaType ENUM to include sticker, gif, voice...');
1211
 
1212
- // PostgreSQL requires special handling for ENUM changes
1213
  await sequelize.query(`
1214
  ALTER TYPE "enum_DirectMessages_mediaType" ADD VALUE IF NOT EXISTS 'sticker';
1215
  ALTER TYPE "enum_DirectMessages_mediaType" ADD VALUE IF NOT EXISTS 'gif';
 
14
 
15
  const app = express();
16
 
17
+
18
  const cors = require('cors');
19
  const allowedOrigins = process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',').map(o => o.trim()).filter(Boolean) : ['*'];
20
 
 
42
  allowedHeaders: ['Content-Type', 'Authorization']
43
  }));
44
 
 
45
  app.use(bodyParser.json({ limit: '50mb' }));
46
  app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
47
 
 
66
  }
67
  });
68
 
 
 
69
  const connectionString = process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/discord-app';
70
  const isLocalConnection = connectionString.includes('localhost') || connectionString.includes('127.0.0.1');
71
 
 
76
  dialectOptions: isLocalConnection ? {} : { ssl: { require: true, rejectUnauthorized: false } }
77
  });
78
 
79
+
80
 
81
  const User = sequelize.define('User', {
82
  id: {
 
212
  mediaUrl: DataTypes.TEXT
213
  });
214
 
215
+
216
 
217
  User.belongsToMany(User, {
218
  as: 'friends',
 
242
  DirectMessage.belongsTo(User, { as: 'sender', foreignKey: 'fromId' });
243
  DirectMessage.belongsTo(User, { as: 'recipient', foreignKey: 'toId' });
244
 
245
+
246
 
247
  const authenticateToken = (req, res, next) => {
248
  const authHeader = req.headers['authorization'];
 
257
  });
258
  };
259
 
260
+
261
 
262
  app.post('/api/register', async (req, res) => {
263
  try {
 
352
  }
353
  });
354
 
 
355
 
356
  app.get('/api/profile/:userId', authenticateToken, async (req, res) => {
357
  try {
 
453
  }
454
  });
455
 
456
+
457
 
458
  app.post('/api/friends/request', authenticateToken, async (req, res) => {
459
  try {
 
579
  }
580
  });
581
 
582
+
583
 
584
  app.get('/api/dms/:friendId', authenticateToken, async (req, res) => {
585
  try {
 
654
  }
655
  });
656
 
657
+
658
  emitToUser(friendId, 'dm history cleared', {
659
  fromUserId: req.user.userId,
660
  clearedAt: new Date().toISOString()
 
666
  }
667
  });
668
 
669
+
670
 
671
  app.get('/api/shop', authenticateToken, async (req, res) => {
672
  try {
 
709
  }
710
  });
711
 
712
+
713
 
714
  function createServer(appInstance) {
715
  const pfxPath = process.env.SSL_PFX_PATH;
 
858
  timestamp: dm.createdAt
859
  };
860
 
861
+
862
  emitToUser(socket.userId, 'dm message', dmPayload);
863
  const deliveredToRecipient = emitToUser(toUserId, 'dm message', dmPayload);
864
 
 
1172
  });
1173
  });
1174
 
 
1175
 
1176
  function getLocalNetworkAddress() {
1177
  const interfaces = os.networkInterfaces();
 
1188
  const PORT = process.env.PORT || 3000;
1189
  const HOST = process.env.HOST || '0.0.0.0';
1190
 
1191
+
1192
  async function migrateMediaTypeEnum() {
1193
  try {
1194
+
1195
  const [results] = await sequelize.query(`
1196
  SELECT EXISTS (
1197
  SELECT 1 FROM pg_type t
 
1204
  if (!results[0].has_voice) {
1205
  console.log('📦 Migrating mediaType ENUM to include sticker, gif, voice...');
1206
 
1207
+
1208
  await sequelize.query(`
1209
  ALTER TYPE "enum_DirectMessages_mediaType" ADD VALUE IF NOT EXISTS 'sticker';
1210
  ALTER TYPE "enum_DirectMessages_mediaType" ADD VALUE IF NOT EXISTS 'gif';
setup-https.ps1 CHANGED
@@ -1,11 +1,10 @@
1
- # Ultimate Chat App - Quick HTTPS Setup for LAN Access
2
- # Run this in PowerShell from the project directory
3
 
4
  Write-Host "🚀 Ultimate Chat App - HTTPS LAN Setup" -ForegroundColor Cyan
5
  Write-Host "=======================================" -ForegroundColor Cyan
6
  Write-Host ""
7
 
8
- # Get LAN IP
9
  $lanIP = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.InterfaceAlias -notlike "*Loopback*" -and $_.IPAddress -notlike "169.254.*"}).IPAddress | Select-Object -First 1
10
 
11
  if (!$lanIP) {
@@ -16,15 +15,15 @@ if (!$lanIP) {
16
  Write-Host "📡 Detected LAN IP: $lanIP" -ForegroundColor Green
17
  Write-Host ""
18
 
19
- # Stop existing Node processes
20
  Write-Host "🛑 Stopping existing Node processes..." -ForegroundColor Yellow
21
  taskkill /F /IM node.exe 2>$null | Out-Null
22
 
23
- # Create certs directory
24
  Write-Host "📁 Creating certs directory..." -ForegroundColor Yellow
25
  New-Item -ItemType Directory -Force .\certs | Out-Null
26
 
27
- # Add firewall rule
28
  Write-Host "🔥 Adding Windows Firewall rule..." -ForegroundColor Yellow
29
  $existingRule = Get-NetFirewallRule -DisplayName "Chat App HTTPS" -ErrorAction SilentlyContinue
30
  if (!$existingRule) {
@@ -34,7 +33,7 @@ if (!$existingRule) {
34
  Write-Host "✅ Firewall rule already exists" -ForegroundColor Green
35
  }
36
 
37
- # Generate certificate
38
  Write-Host "🔐 Generating HTTPS certificate for localhost and $lanIP..." -ForegroundColor Yellow
39
  $cert = New-SelfSignedCertificate `
40
  -Subject "CN=localhost" `
@@ -44,24 +43,24 @@ $cert = New-SelfSignedCertificate `
44
  -NotAfter (Get-Date).AddYears(2) `
45
  -TextExtension @("2.5.29.17={text}DNS=localhost&IPAddress=$lanIP")
46
 
47
- # Export certificate
48
  $pwd = ConvertTo-SecureString "123456" -AsPlainText -Force
49
  Export-PfxCertificate -Cert $cert -FilePath ".\certs\lan-localhost.pfx" -Password $pwd | Out-Null
50
  Export-Certificate -Cert $cert -FilePath ".\certs\lan-localhost.cer" -Force | Out-Null
51
  Write-Host "✅ Certificate generated" -ForegroundColor Green
52
 
53
- # Trust certificate
54
  Write-Host "🔒 Trusting certificate..." -ForegroundColor Yellow
55
  Import-Certificate -FilePath ".\certs\lan-localhost.cer" -CertStoreLocation "Cert:\CurrentUser\Root" | Out-Null
56
  Write-Host "✅ Certificate trusted" -ForegroundColor Green
57
  Write-Host ""
58
 
59
- # Set environment variables
60
  $env:SSL_PFX_PATH = (Resolve-Path ".\certs\lan-localhost.pfx").Path
61
  $env:SSL_PFX_PASSPHRASE = "123456"
62
  $env:PORT = "3001"
63
 
64
- # Display access URLs
65
  Write-Host "╔═══════════════════════════════════════╗" -ForegroundColor Cyan
66
  Write-Host "║ 🎉 Setup Complete! ║" -ForegroundColor Cyan
67
  Write-Host "╚═══════════════════════════════════════╝" -ForegroundColor Cyan
@@ -76,5 +75,5 @@ Write-Host ""
76
  Write-Host "Starting server..." -ForegroundColor Yellow
77
  Write-Host ""
78
 
79
- # Start the app
80
  npm start
 
1
+
 
2
 
3
  Write-Host "🚀 Ultimate Chat App - HTTPS LAN Setup" -ForegroundColor Cyan
4
  Write-Host "=======================================" -ForegroundColor Cyan
5
  Write-Host ""
6
 
7
+
8
  $lanIP = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.InterfaceAlias -notlike "*Loopback*" -and $_.IPAddress -notlike "169.254.*"}).IPAddress | Select-Object -First 1
9
 
10
  if (!$lanIP) {
 
15
  Write-Host "📡 Detected LAN IP: $lanIP" -ForegroundColor Green
16
  Write-Host ""
17
 
18
+
19
  Write-Host "🛑 Stopping existing Node processes..." -ForegroundColor Yellow
20
  taskkill /F /IM node.exe 2>$null | Out-Null
21
 
22
+
23
  Write-Host "📁 Creating certs directory..." -ForegroundColor Yellow
24
  New-Item -ItemType Directory -Force .\certs | Out-Null
25
 
26
+
27
  Write-Host "🔥 Adding Windows Firewall rule..." -ForegroundColor Yellow
28
  $existingRule = Get-NetFirewallRule -DisplayName "Chat App HTTPS" -ErrorAction SilentlyContinue
29
  if (!$existingRule) {
 
33
  Write-Host "✅ Firewall rule already exists" -ForegroundColor Green
34
  }
35
 
36
+
37
  Write-Host "🔐 Generating HTTPS certificate for localhost and $lanIP..." -ForegroundColor Yellow
38
  $cert = New-SelfSignedCertificate `
39
  -Subject "CN=localhost" `
 
43
  -NotAfter (Get-Date).AddYears(2) `
44
  -TextExtension @("2.5.29.17={text}DNS=localhost&IPAddress=$lanIP")
45
 
46
+
47
  $pwd = ConvertTo-SecureString "123456" -AsPlainText -Force
48
  Export-PfxCertificate -Cert $cert -FilePath ".\certs\lan-localhost.pfx" -Password $pwd | Out-Null
49
  Export-Certificate -Cert $cert -FilePath ".\certs\lan-localhost.cer" -Force | Out-Null
50
  Write-Host "✅ Certificate generated" -ForegroundColor Green
51
 
52
+
53
  Write-Host "🔒 Trusting certificate..." -ForegroundColor Yellow
54
  Import-Certificate -FilePath ".\certs\lan-localhost.cer" -CertStoreLocation "Cert:\CurrentUser\Root" | Out-Null
55
  Write-Host "✅ Certificate trusted" -ForegroundColor Green
56
  Write-Host ""
57
 
58
+
59
  $env:SSL_PFX_PATH = (Resolve-Path ".\certs\lan-localhost.pfx").Path
60
  $env:SSL_PFX_PASSPHRASE = "123456"
61
  $env:PORT = "3001"
62
 
63
+
64
  Write-Host "╔═══════════════════════════════════════╗" -ForegroundColor Cyan
65
  Write-Host "║ 🎉 Setup Complete! ║" -ForegroundColor Cyan
66
  Write-Host "╚═══════════════════════════════════════╝" -ForegroundColor Cyan
 
75
  Write-Host "Starting server..." -ForegroundColor Yellow
76
  Write-Host ""
77
 
78
+
79
  npm start