|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>ChatVerse - Connect & Chat</title> |
|
|
<link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌌</text></svg>"> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<style> |
|
|
@keyframes float { |
|
|
0%, 100% { transform: translateY(0px); } |
|
|
50% { transform: translateY(-20px); } |
|
|
} |
|
|
.float-animation { |
|
|
animation: float 6s ease-in-out infinite; |
|
|
} |
|
|
.message-bubble { |
|
|
animation: slideIn 0.3s ease-out; |
|
|
} |
|
|
@keyframes slideIn { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(10px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
.typing-indicator span { |
|
|
animation: typing 1.4s infinite ease-in-out; |
|
|
} |
|
|
.typing-indicator span:nth-child(2) { |
|
|
animation-delay: 0.2s; |
|
|
} |
|
|
.typing-indicator span:nth-child(3) { |
|
|
animation-delay: 0.4s; |
|
|
} |
|
|
@keyframes typing { |
|
|
0%, 60%, 100% { |
|
|
transform: translateY(0); |
|
|
} |
|
|
30% { |
|
|
transform: translateY(-10px); |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 min-h-screen"> |
|
|
|
|
|
<div id="loginScreen" class="min-h-screen flex items-center justify-center p-4"> |
|
|
<div class="bg-white/10 backdrop-blur-lg rounded-3xl p-8 max-w-md w-full shadow-2xl border border-white/20"> |
|
|
<div class="text-center mb-8"> |
|
|
<div class="float-animation"> |
|
|
<h1 class="text-5xl font-bold text-white mb-2">ChatVerse</h1> |
|
|
<p class="text-purple-200">Connect with the universe 🌌</p> |
|
|
</div> |
|
|
</div> |
|
|
<form id="loginForm" class="space-y-6"> |
|
|
<div> |
|
|
<label class="block text-purple-200 mb-2">Choose your username</label> |
|
|
<input type="text" id="username" required |
|
|
class="w-full px-4 py-3 rounded-xl bg-white/20 backdrop-blur text-white placeholder-purple-200 border border-purple-300/30 focus:outline-none focus:border-purple-400 transition" |
|
|
placeholder="Enter username"> |
|
|
</div> |
|
|
<button type="submit" |
|
|
class="w-full bg-gradient-to-r from-purple-500 to-pink-500 text-white py-3 rounded-xl font-semibold hover:from-purple-600 hover:to-pink-600 transform hover:scale-105 transition duration-300 shadow-lg"> |
|
|
Join ChatVerse |
|
|
</button> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="chatInterface" class="hidden h-screen flex flex-col"> |
|
|
|
|
|
<header class="bg-white/10 backdrop-blur-lg border-b border-white/20 p-4"> |
|
|
<div class="flex items-center justify-between max-w-7xl mx-auto"> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<h1 class="text-2xl font-bold text-white">ChatVerse</h1> |
|
|
<span class="bg-green-400 w-3 h-3 rounded-full animate-pulse"></span> |
|
|
</div> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<span id="currentUser" class="text-purple-200"></span> |
|
|
<button onclick="logout()" class="text-purple-200 hover:text-white transition"> |
|
|
<i data-feather="log-out"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
<div class="flex-1 flex overflow-hidden"> |
|
|
|
|
|
<aside class="w-64 bg-white/5 backdrop-blur-lg border-r border-white/10 p-4 overflow-y-auto"> |
|
|
<h2 class="text-purple-200 font-semibold mb-4 flex items-center"> |
|
|
<i data-feather="users" class="w-4 h-4 mr-2"></i> |
|
|
Online Users (<span id="onlineCount">0</span>) |
|
|
</h2> |
|
|
<div id="usersList" class="space-y-2"> |
|
|
|
|
|
</div> |
|
|
</aside> |
|
|
|
|
|
|
|
|
<main class="flex-1 flex flex-col"> |
|
|
|
|
|
<div id="messagesContainer" class="flex-1 overflow-y-auto p-6 space-y-4"> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="typingIndicator" class="px-6 py-2 hidden"> |
|
|
<div class="typing-indicator flex items-center space-x-1 text-purple-300"> |
|
|
<span class="w-2 h-2 bg-purple-300 rounded-full"></span> |
|
|
<span class="w-2 h-2 bg-purple-300 rounded-full"></span> |
|
|
<span class="w-2 h-2 bg-purple-300 rounded-full"></span> |
|
|
<span class="text-sm ml-2" id="typingText"></span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white/10 backdrop-blur-lg border-t border-white/20 p-4"> |
|
|
<form id="messageForm" class="flex space-x-3"> |
|
|
<input type="text" id="messageInput" |
|
|
placeholder="Type your message..." |
|
|
class="flex-1 px-4 py-3 rounded-xl bg-white/20 backdrop-blur text-white placeholder-purple-200 border border-purple-300/30 focus:outline-none focus:border-purple-400 transition" |
|
|
autocomplete="off"> |
|
|
<button type="submit" |
|
|
class="bg-gradient-to-r from-purple-500 to-pink-500 text-white px-6 py-3 rounded-xl hover:from-purple-600 hover:to-pink-600 transform hover:scale-105 transition duration-300 shadow-lg"> |
|
|
<i data-feather="send" class="w-5 h-5"></i> |
|
|
</button> |
|
|
</form> |
|
|
</div> |
|
|
</main> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
class ChatSystem { |
|
|
constructor() { |
|
|
this.currentUser = null; |
|
|
this.users = new Set(); |
|
|
this.messages = []; |
|
|
this.broadcastChannel = new BroadcastChannel('chatverse'); |
|
|
this.typingTimeout = null; |
|
|
this.isTyping = false; |
|
|
|
|
|
this.broadcastChannel.onmessage = (event) => { |
|
|
const { type, data } = event.data; |
|
|
this.handleMessage(type, data); |
|
|
}; |
|
|
} |
|
|
|
|
|
handleMessage(type, data) { |
|
|
switch(type) { |
|
|
case 'userJoined': |
|
|
this.addUser(data.username); |
|
|
break; |
|
|
case 'userLeft': |
|
|
this.removeUser(data.username); |
|
|
break; |
|
|
case 'message': |
|
|
this.addMessage(data); |
|
|
break; |
|
|
case 'typing': |
|
|
this.showTyping(data.username); |
|
|
break; |
|
|
case 'stopTyping': |
|
|
this.hideTyping(); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
broadcast(type, data) { |
|
|
this.broadcastChannel.postMessage({ type, data }); |
|
|
} |
|
|
|
|
|
login(username) { |
|
|
this.currentUser = username; |
|
|
localStorage.setItem('currentUser', username); |
|
|
this.broadcast('userJoined', { username }); |
|
|
this.addUser(username); |
|
|
this.loadMessages(); |
|
|
} |
|
|
|
|
|
logout() { |
|
|
if (this.currentUser) { |
|
|
this.broadcast('userLeft', { username: this.currentUser }); |
|
|
localStorage.removeItem('currentUser'); |
|
|
this.removeUser(this.currentUser); |
|
|
this.currentUser = null; |
|
|
} |
|
|
} |
|
|
|
|
|
addUser(username) { |
|
|
this.users.add(username); |
|
|
this.updateUsersList(); |
|
|
} |
|
|
|
|
|
removeUser(username) { |
|
|
this.users.delete(username); |
|
|
this.updateUsersList(); |
|
|
} |
|
|
|
|
|
updateUsersList() { |
|
|
const usersList = document.getElementById('usersList'); |
|
|
const onlineCount = document.getElementById('onlineCount'); |
|
|
|
|
|
usersList.innerHTML = ''; |
|
|
onlineCount.textContent = this.users.size; |
|
|
|
|
|
this.users.forEach(username => { |
|
|
const userDiv = document.createElement('div'); |
|
|
userDiv.className = 'flex items-center space-x-2 p-2 rounded-lg hover:bg-white/10 transition cursor-pointer'; |
|
|
userDiv.innerHTML = ` |
|
|
<div class="w-2 h-2 bg-green-400 rounded-full"></div> |
|
|
<span class="text-purple-200">${username}</span> |
|
|
${username === this.currentUser ? '<span class="text-xs text-purple-400">(You)</span>' : ''} |
|
|
`; |
|
|
usersList.appendChild(userDiv); |
|
|
}); |
|
|
} |
|
|
|
|
|
sendMessage(content) { |
|
|
const message = { |
|
|
id: Date.now(), |
|
|
username: this.currentUser, |
|
|
content: content, |
|
|
timestamp: new Date().toISOString() |
|
|
}; |
|
|
|
|
|
this.messages.push(message); |
|
|
this.broadcast('message', message); |
|
|
this.addMessage(message); |
|
|
this.saveMessages(); |
|
|
} |
|
|
|
|
|
addMessage(message) { |
|
|
const messagesContainer = document.getElementById('messagesContainer'); |
|
|
const messageDiv = document.createElement('div'); |
|
|
messageDiv.className = `message-bubble flex ${message.username === this.currentUser ? 'justify-end' : 'justify-start'}`; |
|
|
|
|
|
const time = new Date(message.timestamp).toLocaleTimeString('en-US', { |
|
|
hour: '2-digit', |
|
|
minute: '2-digit' |
|
|
}); |
|
|
|
|
|
messageDiv.innerHTML = ` |
|
|
<div class="max-w-xs lg:max-w-md px-4 py-2 rounded-2xl ${ |
|
|
message.username === this.currentUser |
|
|
? 'bg-gradient-to-r from-purple-500 to-pink-500 text-white' |
|
|
: 'bg-white/20 backdrop-blur text-purple-100' |
|
|
}"> |
|
|
<div class="flex items-baseline space-x-2"> |
|
|
<span class="font-semibold text-sm">${message.username}</span> |
|
|
<span class="text-xs opacity-70">${time}</span> |
|
|
</div> |
|
|
<p class="mt-1">${message.content}</p> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
messagesContainer.appendChild(messageDiv); |
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight; |
|
|
} |
|
|
|
|
|
showTyping(username) { |
|
|
if (username === this.currentUser) return; |
|
|
|
|
|
const typingIndicator = document.getElementById('typingIndicator'); |
|
|
const typingText = document.getElementById('typingText'); |
|
|
|
|
|
typingText.textContent = `${username} is typing...`; |
|
|
typingIndicator.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
hideTyping() { |
|
|
document.getElementById('typingIndicator').classList.add('hidden'); |
|
|
} |
|
|
|
|
|
handleTyping() { |
|
|
if (!this.isTyping) { |
|
|
this.isTyping = true; |
|
|
this.broadcast('typing', { username: this.currentUser }); |
|
|
} |
|
|
|
|
|
clearTimeout(this.typingTimeout); |
|
|
this.typingTimeout = setTimeout(() => { |
|
|
this.isTyping = false; |
|
|
this.broadcast('stopTyping', {}); |
|
|
}, 1000); |
|
|
} |
|
|
|
|
|
saveMessages() { |
|
|
localStorage.setItem('chatMessages', JSON.stringify(this.messages)); |
|
|
} |
|
|
|
|
|
loadMessages() { |
|
|
const saved = localStorage.getItem('chatMessages'); |
|
|
if (saved) { |
|
|
this.messages = JSON.parse(saved); |
|
|
this.messages.forEach(msg => this.addMessage(msg)); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const chatSystem = new ChatSystem(); |
|
|
|
|
|
|
|
|
window.addEventListener('DOMContentLoaded', () => { |
|
|
const savedUser = localStorage.getItem('currentUser'); |
|
|
if (savedUser) { |
|
|
showChatInterface(savedUser); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('loginForm').addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
const username = document.getElementById('username').value.trim(); |
|
|
if (username) { |
|
|
chatSystem.login(username); |
|
|
showChatInterface(username); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('messageForm').addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
const input = document.getElementById('messageInput'); |
|
|
const message = input.value.trim(); |
|
|
if (message) { |
|
|
chatSystem.sendMessage(message); |
|
|
input.value = ''; |
|
|
chatSystem.hideTyping(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('messageInput').addEventListener('input', () => { |
|
|
chatSystem.handleTyping(); |
|
|
}); |
|
|
|
|
|
|
|
|
function showChatInterface(username) { |
|
|
document.getElementById('loginScreen').classList.add('hidden'); |
|
|
document.getElementById('chatInterface').classList.remove('hidden'); |
|
|
document.getElementById('currentUser').textContent = username; |
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
|
|
|
function logout() { |
|
|
chatSystem.logout(); |
|
|
document.getElementById('chatInterface').classList.add('hidden'); |
|
|
document.getElementById('loginScreen').classList.remove('hidden'); |
|
|
document.getElementById('messagesContainer').innerHTML = ''; |
|
|
document.getElementById('username').value = ''; |
|
|
} |
|
|
|
|
|
|
|
|
feather.replace(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|