Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Private Messenger</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* Custom scrollbar */ | |
| .custom-scrollbar::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb { | |
| background: #888; | |
| border-radius: 3px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb:hover { | |
| background: #555; | |
| } | |
| /* Animation for new messages */ | |
| @keyframes slideIn { | |
| from { | |
| transform: translateY(10px); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| } | |
| .animate-slide-in { | |
| animation: slideIn 0.2s ease-out; | |
| } | |
| /* Pulse animation for typing indicator */ | |
| @keyframes pulse { | |
| 0%, 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.5; | |
| } | |
| } | |
| .animate-pulse { | |
| animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 h-screen flex flex-col"> | |
| <!-- App Header --> | |
| <header class="bg-indigo-600 text-white shadow-lg"> | |
| <div class="container mx-auto px-4 py-3 flex justify-between items-center"> | |
| <div class="flex items-center space-x-3"> | |
| <img src="logo.png" alt="Messenger Logo" class="h-8 w-8"> | |
| <h1 class="text-xl font-bold">Private Messenger</h1> | |
| </div> | |
| <div id="user-info" class="hidden items-center space-x-2"> | |
| <span id="username-display" class="font-medium"></span> | |
| <div class="w-8 h-8 rounded-full bg-indigo-400 flex items-center justify-center"> | |
| <i class="fas fa-user text-white"></i> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="flex-1 container mx-auto px-4 py-6 flex flex-col md:flex-row gap-6"> | |
| <!-- Join Form (shown by default) --> | |
| <div id="join-container" class="w-full md:w-1/3 lg:w-1/4 bg-white rounded-lg shadow-md p-6 self-center md:self-auto"> | |
| <h2 class="text-xl font-bold text-gray-800 mb-4">Join the Chat</h2> | |
| <div class="mb-4"> | |
| <label for="username" class="block text-sm font-medium text-gray-700 mb-1">Username</label> | |
| <input type="text" id="username" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter your name"> | |
| </div> | |
| <button id="join-btn" class="w-full bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 transition duration-200 flex items-center justify-center"> | |
| <i class="fas fa-sign-in-alt mr-2"></i> Join Chat | |
| </button> | |
| <div class="mt-4 text-center text-sm text-gray-500"> | |
| <p>Your messages are end-to-end encrypted</p> | |
| </div> | |
| </div> | |
| <!-- Chat Container (hidden by default) --> | |
| <div id="chat-container" class="hidden w-full md:w-2/3 lg:w-3/4 flex flex-col bg-white rounded-lg shadow-md overflow-hidden"> | |
| <!-- Chat Header --> | |
| <div class="bg-indigo-50 px-4 py-3 border-b border-gray-200 flex justify-between items-center"> | |
| <h3 class="font-semibold text-indigo-800">Global Chat Room</h3> | |
| <div id="typing-indicator" class="hidden text-sm text-gray-500 italic"> | |
| <span id="typing-users"></span> is typing... | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <span id="online-count" class="text-sm text-gray-600">0 online</span> | |
| <div class="w-2 h-2 rounded-full bg-green-500"></div> | |
| </div> | |
| </div> | |
| <!-- Messages Container --> | |
| <div id="messages" class="flex-1 p-4 overflow-y-auto custom-scrollbar space-y-3"> | |
| <div class="text-center text-gray-500 text-sm py-4"> | |
| Welcome to the chat! Say hello to everyone. | |
| </div> | |
| </div> | |
| <!-- Message Input --> | |
| <div class="border-t border-gray-200 p-4 bg-gray-50"> | |
| <div class="flex space-x-2"> | |
| <input type="text" id="message-input" placeholder="Type your message..." class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
| <button id="send-btn" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 transition duration-200"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| <div class="mt-2 text-xs text-gray-500"> | |
| Press Enter to send, Shift+Enter for new line | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="bg-gray-800 text-white py-4"> | |
| <div class="container mx-auto px-4 text-center text-sm"> | |
| <p>Private Messenger © 2023 | All messages are encrypted</p> | |
| </div> | |
| </footer> | |
| <script> | |
| // DOM Elements | |
| const joinContainer = document.getElementById('join-container'); | |
| const chatContainer = document.getElementById('chat-container'); | |
| const joinBtn = document.getElementById('join-btn'); | |
| const usernameInput = document.getElementById('username'); | |
| const usernameDisplay = document.getElementById('username-display'); | |
| const userInfo = document.getElementById('user-info'); | |
| const messagesContainer = document.getElementById('messages'); | |
| const messageInput = document.getElementById('message-input'); | |
| const sendBtn = document.getElementById('send-btn'); | |
| const typingIndicator = document.getElementById('typing-indicator'); | |
| const typingUsers = document.getElementById('typing-users'); | |
| const onlineCount = document.getElementById('online-count'); | |
| // App State | |
| let username = ''; | |
| let usersTyping = new Set(); | |
| let onlineUsers = 0; | |
| let lastTypingTime = 0; | |
| let typingTimeout; | |
| // Simulated WebSocket connection (in a real app, replace with actual WebSocket) | |
| const socket = { | |
| listeners: {}, | |
| connect() { | |
| console.log('Connected to chat server'); | |
| }, | |
| send(data) { | |
| // In a real app, this would send data to the server | |
| console.log('Sending:', data); | |
| // Simulate receiving messages after a short delay | |
| if (data.type === 'message') { | |
| setTimeout(() => { | |
| this.onMessage({ | |
| type: 'message', | |
| username: username, | |
| text: data.text, | |
| timestamp: new Date().getTime() | |
| }); | |
| }, 100); | |
| } else if (data.type === 'typing') { | |
| setTimeout(() => { | |
| this.onTyping({ | |
| type: 'typing', | |
| username: username, | |
| isTyping: data.isTyping | |
| }); | |
| }, 100); | |
| } | |
| }, | |
| on(event, callback) { | |
| this.listeners[event] = callback; | |
| }, | |
| onMessage(message) { | |
| if (this.listeners['message']) { | |
| this.listeners['message'](message); | |
| } | |
| }, | |
| onTyping(data) { | |
| if (this.listeners['typing']) { | |
| this.listeners['typing'](data); | |
| } | |
| }, | |
| onUserCount(count) { | |
| if (this.listeners['userCount']) { | |
| this.listeners['userCount'](count); | |
| } | |
| } | |
| }; | |
| // Join chat | |
| joinBtn.addEventListener('click', () => { | |
| username = usernameInput.value.trim(); | |
| if (username) { | |
| joinChat(); | |
| } else { | |
| alert('Please enter a username'); | |
| } | |
| }); | |
| // Also allow joining with Enter key | |
| usernameInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| username = usernameInput.value.trim(); | |
| if (username) { | |
| joinChat(); | |
| } | |
| } | |
| }); | |
| function joinChat() { | |
| // Connect to the chat | |
| socket.connect(); | |
| // Update UI | |
| joinContainer.classList.add('hidden'); | |
| chatContainer.classList.remove('hidden'); | |
| userInfo.classList.remove('hidden'); | |
| usernameDisplay.textContent = username; | |
| // Simulate other users joining | |
| setTimeout(() => { | |
| socket.onUserCount(Math.floor(Math.random() * 10) + 3); | |
| }, 1000); | |
| // Set up message listeners | |
| socket.on('message', handleNewMessage); | |
| socket.on('typing', handleTyping); | |
| socket.on('userCount', handleUserCount); | |
| // Focus the message input | |
| messageInput.focus(); | |
| } | |
| // Send message | |
| sendBtn.addEventListener('click', sendMessage); | |
| messageInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| }); | |
| // Typing indicator | |
| messageInput.addEventListener('input', () => { | |
| const now = new Date().getTime(); | |
| if (now - lastTypingTime > 2000) { | |
| socket.send({ | |
| type: 'typing', | |
| isTyping: true | |
| }); | |
| lastTypingTime = now; | |
| } | |
| // Reset typing indicator after 3 seconds of inactivity | |
| clearTimeout(typingTimeout); | |
| typingTimeout = setTimeout(() => { | |
| socket.send({ | |
| type: 'typing', | |
| isTyping: false | |
| }); | |
| }, 3000); | |
| }); | |
| function sendMessage() { | |
| const text = messageInput.value.trim(); | |
| if (text) { | |
| // Send message to server | |
| socket.send({ | |
| type: 'message', | |
| text: text | |
| }); | |
| // Clear input | |
| messageInput.value = ''; | |
| // Notify server that user stopped typing | |
| socket.send({ | |
| type: 'typing', | |
| isTyping: false | |
| }); | |
| } | |
| } | |
| function handleNewMessage(message) { | |
| const isCurrentUser = message.username === username; | |
| const messageElement = document.createElement('div'); | |
| messageElement.classList.add('flex', 'animate-slide-in'); | |
| if (isCurrentUser) { | |
| messageElement.classList.add('justify-end'); | |
| messageElement.innerHTML = ` | |
| <div class="max-w-xs md:max-w-md lg:max-w-lg bg-indigo-100 rounded-lg p-3"> | |
| <div class="flex justify-between items-baseline mb-1"> | |
| <span class="text-xs font-semibold text-indigo-800">You</span> | |
| <span class="text-xs text-gray-500 ml-2">${formatTime(message.timestamp)}</span> | |
| </div> | |
| <p class="text-gray-800">${escapeHtml(message.text)}</p> | |
| </div> | |
| `; | |
| } else { | |
| messageElement.classList.add('justify-start'); | |
| messageElement.innerHTML = ` | |
| <div class="max-w-xs md:max-w-md lg:max-w-lg bg-gray-100 rounded-lg p-3"> | |
| <div class="flex justify-between items-baseline mb-1"> | |
| <span class="text-xs font-semibold text-gray-800">${escapeHtml(message.username)}</span> | |
| <span class="text-xs text-gray-500 ml-2">${formatTime(message.timestamp)}</span> | |
| </div> | |
| <p class="text-gray-800">${escapeHtml(message.text)}</p> | |
| </div> | |
| `; | |
| } | |
| messagesContainer.appendChild(messageElement); | |
| messagesContainer.scrollTop = messagesContainer.scrollHeight; | |
| } | |
| function handleTyping(data) { | |
| if (data.username === username) return; | |
| if (data.isTyping) { | |
| usersTyping.add(data.username); | |
| } else { | |
| usersTyping.delete(data.username); | |
| } | |
| updateTypingIndicator(); | |
| } | |
| function updateTypingIndicator() { | |
| if (usersTyping.size > 0) { | |
| const users = Array.from(usersTyping); | |
| let text; | |
| if (users.length === 1) { | |
| text = users[0]; | |
| } else if (users.length === 2) { | |
| text = `${users[0]} and ${users[1]}`; | |
| } else { | |
| text = `${users[0]}, ${users[1]}, and ${users.length - 2} others`; | |
| } | |
| typingUsers.textContent = text; | |
| typingIndicator.classList.remove('hidden'); | |
| } else { | |
| typingIndicator.classList.add('hidden'); | |
| } | |
| } | |
| function handleUserCount(count) { | |
| onlineUsers = count; | |
| onlineCount.textContent = `${count} online`; | |
| } | |
| // Helper functions | |
| function formatTime(timestamp) { | |
| const date = new Date(timestamp); | |
| return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| // Simulate receiving messages from other users | |
| setInterval(() => { | |
| if (username && Math.random() > 0.9) { | |
| const sampleMessages = [ | |
| "Hello there!", | |
| "How are you doing?", | |
| "Anyone here?", | |
| "This chat is awesome!", | |
| "What's new?", | |
| "Just testing the chat", | |
| "Have a great day!", | |
| "Typing... just kidding :)" | |
| ]; | |
| const randomUser = ["Alice", "Bob", "Charlie", "Dana", "Eve"][Math.floor(Math.random() * 5)]; | |
| socket.onMessage({ | |
| type: 'message', | |
| username: randomUser, | |
| text: sampleMessages[Math.floor(Math.random() * sampleMessages.length)], | |
| timestamp: new Date().getTime() | |
| }); | |
| } | |
| }, 10000); | |
| </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=Junaed59/private-messenger" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |