| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Real-Time Chat App</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script> |
| <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js"></script> |
| <style> |
| .message-container { |
| scrollbar-width: thin; |
| scrollbar-color: #4B5563 #1F2937; |
| } |
| |
| .message-container::-webkit-scrollbar { |
| width: 8px; |
| } |
| |
| .message-container::-webkit-scrollbar-track { |
| background: #1F2937; |
| } |
| |
| .message-container::-webkit-scrollbar-thumb { |
| background-color: #4B5563; |
| border-radius: 4px; |
| } |
| |
| .typing-indicator::after { |
| content: "..."; |
| animation: typing 1.5s infinite; |
| } |
| |
| @keyframes typing { |
| 0% { content: "."; } |
| 33% { content: ".."; } |
| 66% { content: "..."; } |
| } |
| |
| .fade-in { |
| animation: fadeIn 0.3s ease-in; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col"> |
| |
| <script> |
| |
| const firebaseConfig = { |
| apiKey: "AIzaSyB5XQ7j5X5X5X5X5X5X5X5X5X5X5X5X5X5", |
| authDomain: "realtime-chat-app-12345.firebaseapp.com", |
| databaseURL: "https://realtime-chat-app-12345.firebaseio.com", |
| projectId: "realtime-chat-app-12345", |
| storageBucket: "realtime-chat-app-12345.appspot.com", |
| messagingSenderId: "123456789012", |
| appId: "1:123456789012:web:5X5X5X5X5X5X5X5X5X5X5X" |
| }; |
| |
| |
| firebase.initializeApp(firebaseConfig); |
| const database = firebase.database(); |
| </script> |
|
|
| <header class="bg-gray-800 py-4 px-6 shadow-lg"> |
| <div class="container mx-auto flex justify-between items-center"> |
| <h1 class="text-2xl font-bold text-blue-400">Real-Time Chat</h1> |
| <div id="connection-status" class="flex items-center"> |
| <span class="h-3 w-3 rounded-full bg-red-500 mr-2"></span> |
| <span>Disconnected</span> |
| </div> |
| </div> |
| </header> |
|
|
| <main class="flex-1 container mx-auto p-4 flex flex-col md:flex-row gap-6"> |
| |
| <div class="bg-gray-800 rounded-lg p-6 w-full md:w-1/3 lg:w-1/4 shadow-lg"> |
| <div class="mb-6"> |
| <h2 class="text-xl font-semibold mb-4">Your Profile</h2> |
| <div class="space-y-4"> |
| <div> |
| <label for="user-id" class="block text-sm font-medium mb-1">Your Unique ID</label> |
| <div class="flex"> |
| <input type="text" id="user-id" readonly class="bg-gray-700 text-gray-200 rounded-l-md px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| <button onclick="copyUserId()" class="bg-blue-600 hover:bg-blue-700 px-3 rounded-r-md flex items-center"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" /> |
| </svg> |
| </button> |
| </div> |
| </div> |
| <div> |
| <label for="display-name" class="block text-sm font-medium mb-1">Display Name</label> |
| <input type="text" id="display-name" placeholder="Enter your name" class="bg-gray-700 text-gray-200 rounded-md px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| </div> |
| <button onclick="generateUserId()" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition duration-200"> |
| Generate New ID |
| </button> |
| </div> |
| </div> |
|
|
| <div> |
| <h2 class="text-xl font-semibold mb-4">Connect to Friend</h2> |
| <div class="space-y-4"> |
| <div> |
| <label for="friend-id" class="block text-sm font-medium mb-1">Friend's ID</label> |
| <input type="text" id="friend-id" placeholder="Enter friend's ID" class="bg-gray-700 text-gray-200 rounded-md px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| </div> |
| <button onclick="connectToFriend()" id="connect-button" class="w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-md transition duration-200"> |
| Connect |
| </button> |
| <button onclick="disconnectFromFriend()" id="disconnect-button" class="w-full bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded-md transition duration-200 hidden"> |
| Disconnect |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-gray-800 rounded-lg p-6 flex-1 shadow-lg flex flex-col"> |
| <div class="flex justify-between items-center mb-4"> |
| <h2 class="text-xl font-semibold">Chat</h2> |
| <div id="chat-status" class="text-sm text-gray-400"> |
| Not connected to anyone |
| </div> |
| </div> |
|
|
| <div id="message-container" class="message-container flex-1 overflow-y-auto mb-4 space-y-3 p-2 bg-gray-700 rounded-lg"> |
| <div class="text-center text-gray-400 py-10"> |
| Connect with a friend to start chatting |
| </div> |
| </div> |
|
|
| <div id="typing-indicator" class="typing-indicator text-sm text-gray-400 mb-2 h-5 hidden"> |
| Friend is typing |
| </div> |
|
|
| <div class="flex gap-2"> |
| <input type="text" id="message-input" placeholder="Type a message..." disabled class="flex-1 bg-gray-700 text-gray-200 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| <button onclick="sendMessage()" id="send-button" disabled class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition duration-200 disabled:opacity-50"> |
| Send |
| </button> |
| </div> |
| </div> |
| </main> |
|
|
| <script> |
| |
| let userId = ''; |
| let friendId = ''; |
| let displayName = 'Anonymous'; |
| let isConnected = false; |
| let typingTimeout; |
| let chatRef; |
| let typingRef; |
| let userRef; |
| |
| |
| function init() { |
| |
| if (localStorage.getItem('chatUserId')) { |
| userId = localStorage.getItem('chatUserId'); |
| document.getElementById('user-id').value = userId; |
| } else { |
| generateUserId(); |
| } |
| |
| |
| if (localStorage.getItem('chatDisplayName')) { |
| displayName = localStorage.getItem('chatDisplayName'); |
| document.getElementById('display-name').value = displayName; |
| } |
| |
| |
| document.getElementById('display-name').addEventListener('input', function() { |
| displayName = this.value || 'Anonymous'; |
| localStorage.setItem('chatDisplayName', displayName); |
| |
| if (isConnected) { |
| |
| userRef.update({ |
| name: displayName |
| }); |
| } |
| }); |
| |
| document.getElementById('message-input').addEventListener('input', function() { |
| if (isConnected) { |
| |
| typingRef.set(true); |
| |
| |
| if (typingTimeout) clearTimeout(typingTimeout); |
| |
| |
| typingTimeout = setTimeout(() => { |
| typingRef.set(false); |
| }, 1000); |
| } |
| }); |
| |
| document.getElementById('message-input').addEventListener('keypress', function(e) { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| sendMessage(); |
| } |
| }); |
| } |
| |
| |
| function generateUserId() { |
| userId = 'user-' + Math.random().toString(36).substr(2, 9); |
| document.getElementById('user-id').value = userId; |
| localStorage.setItem('chatUserId', userId); |
| |
| |
| if (isConnected) { |
| disconnectFromFriend(); |
| } |
| } |
| |
| |
| function copyUserId() { |
| const userIdInput = document.getElementById('user-id'); |
| userIdInput.select(); |
| document.execCommand('copy'); |
| |
| |
| const copyButton = userIdInput.nextElementSibling; |
| copyButton.innerHTML = ` |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> |
| </svg> |
| `; |
| |
| setTimeout(() => { |
| copyButton.innerHTML = ` |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" /> |
| </svg> |
| `; |
| }, 2000); |
| } |
| |
| |
| function connectToFriend() { |
| friendId = document.getElementById('friend-id').value.trim(); |
| |
| if (!friendId) { |
| alert('Please enter your friend\'s ID'); |
| return; |
| } |
| |
| if (friendId === userId) { |
| alert('You cannot connect to yourself'); |
| return; |
| } |
| |
| |
| const friendRef = database.ref('users/' + friendId); |
| |
| friendRef.once('value').then((snapshot) => { |
| if (snapshot.exists()) { |
| |
| isConnected = true; |
| |
| |
| document.getElementById('connect-button').classList.add('hidden'); |
| document.getElementById('disconnect-button').classList.remove('hidden'); |
| document.getElementById('message-input').disabled = false; |
| document.getElementById('send-button').disabled = false; |
| document.getElementById('friend-id').disabled = true; |
| |
| |
| document.getElementById('connection-status').innerHTML = ` |
| <span class="h-3 w-3 rounded-full bg-green-500 mr-2"></span> |
| <span>Connected to ${snapshot.val().name || 'Friend'}</span> |
| `; |
| |
| document.getElementById('chat-status').textContent = `Chatting with ${snapshot.val().name || 'Friend'}`; |
| |
| |
| document.getElementById('message-container').innerHTML = ''; |
| |
| |
| const chatId = [userId, friendId].sort().join('-'); |
| chatRef = database.ref('chats/' + chatId); |
| |
| |
| typingRef = database.ref('typing/' + chatId + '/' + userId); |
| |
| |
| userRef = database.ref('users/' + userId); |
| |
| |
| userRef.set({ |
| name: displayName, |
| connectedTo: friendId, |
| lastSeen: firebase.database.ServerValue.TIMESTAMP |
| }); |
| |
| |
| chatRef.limitToLast(100).on('child_added', (snapshot) => { |
| const message = snapshot.val(); |
| addMessageToChat(message); |
| }); |
| |
| |
| database.ref('typing/' + chatId + '/' + friendId).on('value', (snapshot) => { |
| const isTyping = snapshot.val(); |
| const typingIndicator = document.getElementById('typing-indicator'); |
| |
| if (isTyping) { |
| typingIndicator.classList.remove('hidden'); |
| } else { |
| typingIndicator.classList.add('hidden'); |
| } |
| }); |
| |
| |
| database.ref('users/' + friendId + '/connectedTo').on('value', (snapshot) => { |
| if (snapshot.val() !== userId) { |
| |
| disconnectFromFriend(); |
| alert('Your friend has disconnected'); |
| } |
| }); |
| } else { |
| alert('Friend not found. Please check the ID and try again.'); |
| } |
| }).catch((error) => { |
| console.error('Error connecting to friend:', error); |
| alert('Error connecting to friend. Please try again.'); |
| }); |
| } |
| |
| |
| function disconnectFromFriend() { |
| if (!isConnected) return; |
| |
| isConnected = false; |
| |
| |
| document.getElementById('connect-button').classList.remove('hidden'); |
| document.getElementById('disconnect-button').classList.add('hidden'); |
| document.getElementById('message-input').disabled = true; |
| document.getElementById('send-button').disabled = true; |
| document.getElementById('friend-id').disabled = false; |
| |
| |
| document.getElementById('connection-status').innerHTML = ` |
| <span class="h-3 w-3 rounded-full bg-red-500 mr-2"></span> |
| <span>Disconnected</span> |
| `; |
| |
| document.getElementById('chat-status').textContent = 'Not connected to anyone'; |
| document.getElementById('typing-indicator').classList.add('hidden'); |
| |
| |
| addSystemMessage('Disconnected from chat'); |
| |
| |
| if (chatRef) chatRef.off(); |
| if (typingRef) typingRef.off(); |
| if (userRef) { |
| userRef.update({ |
| connectedTo: null, |
| lastSeen: firebase.database.ServerValue.TIMESTAMP |
| }); |
| } |
| |
| |
| if (typingRef) typingRef.set(false); |
| } |
| |
| |
| function sendMessage() { |
| const messageInput = document.getElementById('message-input'); |
| const messageText = messageInput.value.trim(); |
| |
| if (!messageText || !isConnected) return; |
| |
| |
| const message = { |
| sender: userId, |
| senderName: displayName, |
| text: messageText, |
| timestamp: firebase.database.ServerValue.TIMESTAMP |
| }; |
| |
| |
| chatRef.push(message); |
| |
| |
| messageInput.value = ''; |
| |
| |
| typingRef.set(false); |
| } |
| |
| |
| function addMessageToChat(message) { |
| const messageContainer = document.getElementById('message-container'); |
| |
| |
| if (messageContainer.children.length === 1 && messageContainer.children[0].classList.contains('text-center')) { |
| messageContainer.innerHTML = ''; |
| } |
| |
| const messageElement = document.createElement('div'); |
| messageElement.classList.add('fade-in'); |
| |
| if (message.sender === userId) { |
| |
| messageElement.innerHTML = ` |
| <div class="flex justify-end"> |
| <div class="bg-blue-600 text-white rounded-l-lg rounded-br-lg px-4 py-2 max-w-xs md:max-w-md lg:max-w-lg"> |
| <div class="text-xs text-blue-200 mb-1">You</div> |
| <div>${message.text}</div> |
| </div> |
| </div> |
| `; |
| } else if (message.sender === 'system') { |
| |
| messageElement.innerHTML = ` |
| <div class="text-center text-gray-400 text-sm py-2"> |
| ${message.text} |
| </div> |
| `; |
| } else { |
| |
| messageElement.innerHTML = ` |
| <div class="flex justify-start"> |
| <div class="bg-gray-700 text-white rounded-r-lg rounded-bl-lg px-4 py-2 max-w-xs md:max-w-md lg:max-w-lg"> |
| <div class="text-xs text-gray-300 mb-1">${message.senderName || 'Friend'}</div> |
| <div>${message.text}</div> |
| </div> |
| </div> |
| `; |
| } |
| |
| messageContainer.appendChild(messageElement); |
| messageContainer.scrollTop = messageContainer.scrollHeight; |
| } |
| |
| |
| function addSystemMessage(text) { |
| addMessageToChat({ |
| sender: 'system', |
| text: text, |
| timestamp: firebase.database.ServerValue.TIMESTAMP |
| }); |
| } |
| |
| |
| window.onload = init; |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Fdgg55/chat-app" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |