Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Empathy Connection Game</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/emoji-picker-element@1"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| background-color: #f3f4f6; | |
| height: 100vh; | |
| overflow: hidden; | |
| } | |
| .message-container { | |
| scrollbar-width: thin; | |
| scrollbar-color: #9CA3AF #E5E7EB; | |
| } | |
| .message-container::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .message-container::-webkit-scrollbar-track { | |
| background: #E5E7EB; | |
| } | |
| .message-container::-webkit-scrollbar-thumb { | |
| background-color: #9CA3AF; | |
| border-radius: 3px; | |
| } | |
| .typing-indicator::after { | |
| content: "..."; | |
| animation: typing 1.5s infinite; | |
| display: inline-block; | |
| width: 0; | |
| } | |
| @keyframes typing { | |
| 0% { content: "."; } | |
| 33% { content: ".."; } | |
| 66% { content: "..."; } | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| .emoji-picker { | |
| position: absolute; | |
| bottom: 60px; | |
| right: 20px; | |
| z-index: 10; | |
| } | |
| .confetti { | |
| position: absolute; | |
| width: 10px; | |
| height: 10px; | |
| background-color: #f00; | |
| opacity: 0; | |
| } | |
| </style> | |
| </head> | |
| <body class="flex flex-col h-screen"> | |
| <!-- Header --> | |
| <header class="bg-indigo-600 text-white p-4 shadow-md"> | |
| <div class="container mx-auto flex justify-between items-center"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-heart text-2xl"></i> | |
| <h1 class="text-xl font-bold">Empathy Connection</h1> | |
| </div> | |
| <div id="user-count" class="flex items-center space-x-1"> | |
| <i class="fas fa-users"></i> | |
| <span>1</span> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Game Area --> | |
| <main class="flex-1 flex flex-col md:flex-row overflow-hidden"> | |
| <!-- Sidebar --> | |
| <aside class="w-full md:w-64 bg-white p-4 border-r border-gray-200 flex flex-col"> | |
| <div class="mb-6"> | |
| <h2 class="font-bold text-lg text-indigo-700 mb-2">Game Rules</h2> | |
| <ul class="text-sm space-y-2 text-gray-600"> | |
| <li class="flex items-start"> | |
| <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
| <span>Share personal experiences when prompted</span> | |
| </li> | |
| <li class="flex items-start"> | |
| <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
| <span>Respond with empathy and understanding</span> | |
| </li> | |
| <li class="flex items-start"> | |
| <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
| <span>Be respectful and kind to others</span> | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="mb-6"> | |
| <h2 class="font-bold text-lg text-indigo-700 mb-2">Players</h2> | |
| <div id="players-list" class="space-y-2"> | |
| <div class="flex items-center space-x-2"> | |
| <div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600"> | |
| <i class="fas fa-user"></i> | |
| </div> | |
| <span class="text-sm">You</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-auto"> | |
| <div id="game-status" class="p-3 rounded-lg bg-indigo-50 text-indigo-700 text-sm"> | |
| <p>Waiting for more players to join...</p> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- Main Chat Area --> | |
| <section class="flex-1 flex flex-col bg-gray-50"> | |
| <!-- Prompt Area --> | |
| <div id="prompt-area" class="p-4 bg-white border-b border-gray-200"> | |
| <div class="max-w-3xl mx-auto"> | |
| <div class="bg-indigo-50 p-4 rounded-lg"> | |
| <h3 class="font-bold text-indigo-700 mb-2">Today's Theme: Overcoming Challenges</h3> | |
| <p class="text-gray-700">Share a time when you faced a difficult situation and how you handled it. Then respond to others with empathy and understanding.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Messages Container --> | |
| <div id="messages" class="message-container flex-1 overflow-y-auto p-4 space-y-4"> | |
| <div class="max-w-3xl mx-auto"> | |
| <div class="text-center py-8"> | |
| <div class="inline-block p-4 bg-indigo-100 rounded-full"> | |
| <i class="fas fa-heart text-indigo-500 text-3xl"></i> | |
| </div> | |
| <h3 class="text-xl font-bold text-indigo-700 mt-4">Welcome to Empathy Connection</h3> | |
| <p class="text-gray-600 mt-2">Share your story and connect with others through empathy</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Input Area --> | |
| <div class="p-4 bg-white border-t border-gray-200"> | |
| <div class="max-w-3xl mx-auto relative"> | |
| <div id="typing-indicator" class="text-xs text-gray-500 mb-1 hidden"> | |
| <span class="typing-indicator">Someone is typing</span> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button id="emoji-button" class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-500 hover:bg-gray-200"> | |
| <i class="far fa-smile"></i> | |
| </button> | |
| <div class="flex-1 relative"> | |
| <textarea id="message-input" rows="1" class="w-full p-3 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 resize-none" placeholder="Share your experience..."></textarea> | |
| <button id="send-button" class="absolute right-2 bottom-2 w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center hover:bg-indigo-200 disabled:opacity-50" disabled> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <emoji-picker id="emoji-picker" class="emoji-picker hidden"></emoji-picker> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Connection Modal --> | |
| <div id="connection-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> | |
| <div class="bg-white rounded-lg p-6 max-w-md w-full mx-4"> | |
| <div class="text-center"> | |
| <div class="w-16 h-16 bg-indigo-100 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i class="fas fa-plug text-indigo-500 text-2xl"></i> | |
| </div> | |
| <h3 class="text-xl font-bold text-gray-800 mb-2">Connecting to Game</h3> | |
| <p class="text-gray-600 mb-6">Please wait while we connect you to other players...</p> | |
| <div class="w-full bg-gray-200 rounded-full h-2"> | |
| <div id="connection-progress" class="bg-indigo-600 h-2 rounded-full w-0"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Welcome Modal --> | |
| <div id="welcome-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-lg p-6 max-w-md w-full mx-4"> | |
| <div class="text-center"> | |
| <div class="w-16 h-16 bg-indigo-100 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i class="fas fa-heart text-indigo-500 text-2xl"></i> | |
| </div> | |
| <h3 class="text-xl font-bold text-gray-800 mb-2">Welcome to Empathy Connection</h3> | |
| <p class="text-gray-600 mb-4">Practice empathy by sharing experiences and responding to others with understanding.</p> | |
| <div class="mb-4"> | |
| <label for="username" class="block text-left text-sm font-medium text-gray-700 mb-1">Your Name</label> | |
| <input type="text" id="username" class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter your name"> | |
| </div> | |
| <button id="start-game" class="w-full bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"> | |
| Join the Game | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Game state | |
| const gameState = { | |
| players: {}, | |
| currentUser: null, | |
| socket: null, | |
| isTyping: false, | |
| typingTimeout: null | |
| }; | |
| // DOM elements | |
| const elements = { | |
| connectionModal: document.getElementById('connection-modal'), | |
| welcomeModal: document.getElementById('welcome-modal'), | |
| usernameInput: document.getElementById('username'), | |
| startGameButton: document.getElementById('start-game'), | |
| messageInput: document.getElementById('message-input'), | |
| sendButton: document.getElementById('send-button'), | |
| messagesContainer: document.getElementById('messages'), | |
| playersList: document.getElementById('players-list'), | |
| userCount: document.getElementById('user-count'), | |
| typingIndicator: document.getElementById('typing-indicator'), | |
| emojiButton: document.getElementById('emoji-button'), | |
| emojiPicker: document.getElementById('emoji-picker'), | |
| gameStatus: document.getElementById('game-status'), | |
| connectionProgress: document.getElementById('connection-progress') | |
| }; | |
| // Initialize the game | |
| function initGame() { | |
| // Show welcome modal | |
| setTimeout(() => { | |
| elements.connectionModal.classList.add('hidden'); | |
| elements.welcomeModal.classList.remove('hidden'); | |
| }, 2000); | |
| // Simulate connection progress | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += 5; | |
| elements.connectionProgress.style.width = `${progress}%`; | |
| if (progress >= 100) { | |
| clearInterval(interval); | |
| } | |
| }, 100); | |
| // Set up event listeners | |
| setupEventListeners(); | |
| } | |
| // Set up all event listeners | |
| function setupEventListeners() { | |
| // Start game button | |
| elements.startGameButton.addEventListener('click', () => { | |
| const username = elements.usernameInput.value.trim(); | |
| if (username) { | |
| joinGame(username); | |
| } | |
| }); | |
| // Message input events | |
| elements.messageInput.addEventListener('input', handleMessageInput); | |
| elements.messageInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| }); | |
| // Send button | |
| elements.sendButton.addEventListener('click', sendMessage); | |
| // Emoji picker | |
| elements.emojiButton.addEventListener('click', toggleEmojiPicker); | |
| elements.emojiPicker.addEventListener('emoji-click', (event) => { | |
| const emoji = event.detail.unicode; | |
| const input = elements.messageInput; | |
| const startPos = input.selectionStart; | |
| const endPos = input.selectionEnd; | |
| input.value = input.value.substring(0, startPos) + emoji + input.value.substring(endPos); | |
| input.focus(); | |
| input.selectionStart = startPos + emoji.length; | |
| input.selectionEnd = startPos + emoji.length; | |
| // Trigger input event to update send button state | |
| input.dispatchEvent(new Event('input')); | |
| }); | |
| // Close emoji picker when clicking outside | |
| document.addEventListener('click', (e) => { | |
| if (!elements.emojiButton.contains(e.target) && !elements.emojiPicker.contains(e.target)) { | |
| elements.emojiPicker.classList.add('hidden'); | |
| } | |
| }); | |
| } | |
| // Toggle emoji picker visibility | |
| function toggleEmojiPicker() { | |
| elements.emojiPicker.classList.toggle('hidden'); | |
| } | |
| // Handle message input changes | |
| function handleMessageInput() { | |
| const message = elements.messageInput.value.trim(); | |
| elements.sendButton.disabled = message === ''; | |
| // Update typing status | |
| if (message && !gameState.isTyping) { | |
| gameState.isTyping = true; | |
| sendTypingStatus(true); | |
| } else if (!message && gameState.isTyping) { | |
| gameState.isTyping = false; | |
| sendTypingStatus(false); | |
| } | |
| // Reset typing timeout | |
| if (gameState.typingTimeout) { | |
| clearTimeout(gameState.typingTimeout); | |
| } | |
| if (message) { | |
| gameState.typingTimeout = setTimeout(() => { | |
| gameState.isTyping = false; | |
| sendTypingStatus(false); | |
| }, 2000); | |
| } | |
| // Auto-resize textarea | |
| autoResizeTextarea(); | |
| } | |
| // Auto-resize textarea based on content | |
| function autoResizeTextarea() { | |
| const textarea = elements.messageInput; | |
| textarea.style.height = 'auto'; | |
| textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`; | |
| } | |
| // Send typing status to server | |
| function sendTypingStatus(isTyping) { | |
| if (gameState.socket) { | |
| gameState.socket.send(JSON.stringify({ | |
| type: 'typing', | |
| userId: gameState.currentUser.id, | |
| isTyping: isTyping | |
| })); | |
| } | |
| } | |
| // Join the game | |
| function joinGame(username) { | |
| elements.welcomeModal.classList.add('hidden'); | |
| // Create user | |
| gameState.currentUser = { | |
| id: generateId(), | |
| name: username, | |
| color: getRandomColor(), | |
| avatar: getRandomAvatar() | |
| }; | |
| // Simulate WebSocket connection | |
| setTimeout(() => { | |
| connectWebSocket(); | |
| }, 500); | |
| // Add user to players list | |
| addPlayer(gameState.currentUser); | |
| // Show welcome message | |
| addSystemMessage(`Welcome, ${username}! Share your experience when you're ready.`); | |
| } | |
| // Connect to WebSocket (simulated in this demo) | |
| function connectWebSocket() { | |
| // In a real app, this would connect to your WebSocket server | |
| console.log("Connecting to WebSocket server..."); | |
| gameState.socket = { | |
| send: function(data) { | |
| // Simulate receiving messages with a delay | |
| setTimeout(() => { | |
| const message = JSON.parse(data); | |
| handleIncomingMessage(message); | |
| }, 300); | |
| } | |
| }; | |
| // Simulate other players joining | |
| simulateOtherPlayers(); | |
| } | |
| // Simulate other players joining the game | |
| function simulateOtherPlayers() { | |
| const names = ["Alex", "Jordan", "Taylor", "Morgan", "Casey"]; | |
| const avatars = ["user", "user-tie", "user-graduate", "user-nurse", "user-astronaut"]; | |
| // Add 3-5 simulated players | |
| const numPlayers = Math.floor(Math.random() * 3) + 3; | |
| for (let i = 0; i < numPlayers; i++) { | |
| setTimeout(() => { | |
| const player = { | |
| id: generateId(), | |
| name: names[i], | |
| color: getRandomColor(), | |
| avatar: `fa-${avatars[i]}` | |
| }; | |
| gameState.players[player.id] = player; | |
| addPlayer(player); | |
| addSystemMessage(`${player.name} has joined the game`); | |
| // Update user count | |
| updateUserCount(); | |
| // Simulate some messages from other players | |
| if (i === 0) { | |
| setTimeout(() => { | |
| simulatePlayerMessage(player, "Hi everyone! I'm excited to share and listen today."); | |
| }, 1500); | |
| } | |
| if (i === 1) { | |
| setTimeout(() => { | |
| simulatePlayerMessage(player, "This is my first time here. Looking forward to connecting!"); | |
| }, 3000); | |
| } | |
| if (i === 2) { | |
| setTimeout(() => { | |
| simulatePlayerTyping(player); | |
| }, 4500); | |
| } | |
| }, i * 1000); | |
| } | |
| // Update game status | |
| setTimeout(() => { | |
| elements.gameStatus.innerHTML = ` | |
| <p class="font-medium">Game in progress</p> | |
| <p class="text-xs mt-1">Share your story and respond to others</p> | |
| `; | |
| }, numPlayers * 1000); | |
| } | |
| // Simulate a player typing | |
| function simulatePlayerTyping(player) { | |
| // Show typing indicator | |
| const typingEvent = { | |
| type: 'typing', | |
| userId: player.id, | |
| isTyping: true | |
| }; | |
| handleIncomingMessage(typingEvent); | |
| // Send message after delay | |
| setTimeout(() => { | |
| simulatePlayerMessage(player, "I once faced a big challenge when I had to speak in front of a large audience. I was so nervous but I practiced a lot and it went better than I expected!"); | |
| // Hide typing indicator | |
| const stopTypingEvent = { | |
| type: 'typing', | |
| userId: player.id, | |
| isTyping: false | |
| }; | |
| handleIncomingMessage(stopTypingEvent); | |
| }, 2000); | |
| } | |
| // Simulate a message from another player | |
| function simulatePlayerMessage(player, text) { | |
| const message = { | |
| type: 'message', | |
| userId: player.id, | |
| text: text, | |
| timestamp: new Date().toISOString() | |
| }; | |
| handleIncomingMessage(message); | |
| } | |
| // Handle incoming messages from WebSocket | |
| function handleIncomingMessage(message) { | |
| switch (message.type) { | |
| case 'message': | |
| addPlayerMessage(message.userId, message.text, message.timestamp); | |
| break; | |
| case 'typing': | |
| handleTypingIndicator(message.userId, message.isTyping); | |
| break; | |
| case 'user-joined': | |
| addPlayer(message.user); | |
| addSystemMessage(`${message.user.name} has joined the game`); | |
| updateUserCount(); | |
| break; | |
| case 'user-left': | |
| removePlayer(message.userId); | |
| addSystemMessage(`${gameState.players[message.userId]?.name || 'A player'} has left the game`); | |
| updateUserCount(); | |
| break; | |
| } | |
| } | |
| // Send a message | |
| function sendMessage() { | |
| const text = elements.messageInput.value.trim(); | |
| if (!text || !gameState.socket) return; | |
| // Create message | |
| const message = { | |
| type: 'message', | |
| userId: gameState.currentUser.id, | |
| text: text, | |
| timestamp: new Date().toISOString() | |
| }; | |
| // Add to chat | |
| addPlayerMessage(gameState.currentUser.id, text, message.timestamp); | |
| // Clear input | |
| elements.messageInput.value = ''; | |
| elements.sendButton.disabled = true; | |
| autoResizeTextarea(); | |
| // Send to server | |
| gameState.socket.send(JSON.stringify(message)); | |
| // Stop typing | |
| if (gameState.isTyping) { | |
| gameState.isTyping = false; | |
| sendTypingStatus(false); | |
| } | |
| // Simulate responses from other players | |
| simulateEmpatheticResponses(text); | |
| } | |
| // Simulate empathetic responses from other players | |
| function simulateEmpatheticResponses(text) { | |
| const players = Object.values(gameState.players); | |
| if (players.length === 0) return; | |
| // Randomly select 1-2 players to respond | |
| const numResponses = Math.floor(Math.random() * 2) + 1; | |
| const responders = []; | |
| for (let i = 0; i < numResponses && i < players.length; i++) { | |
| let randomPlayer; | |
| do { | |
| randomPlayer = players[Math.floor(Math.random() * players.length)]; | |
| } while (randomPlayer.id === gameState.currentUser.id || responders.includes(randomPlayer)); | |
| responders.push(randomPlayer); | |
| // Generate response based on message content | |
| let response; | |
| if (text.toLowerCase().includes("happy") || text.toLowerCase().includes("excited")) { | |
| response = "That's wonderful to hear! I'm so happy for you!"; | |
| } else if (text.toLowerCase().includes("sad") || text.toLowerCase().includes("difficult")) { | |
| response = "I'm really sorry to hear that. That must have been really hard."; | |
| } else if (text.toLowerCase().includes("nervous") || text.toLowerCase().includes("anxious")) { | |
| response = "I can relate to that feeling. It takes courage to face those situations."; | |
| } else { | |
| const responses = [ | |
| "Thank you for sharing that. I appreciate your openness.", | |
| "That's really interesting. I hadn't thought about it that way before.", | |
| "I can understand how that would feel. Thanks for helping me see your perspective.", | |
| "That sounds like a meaningful experience. I appreciate you sharing it with us." | |
| ]; | |
| response = responses[Math.floor(Math.random() * responses.length)]; | |
| } | |
| // Delay the response | |
| setTimeout(() => { | |
| simulatePlayerTyping(randomPlayer); | |
| setTimeout(() => { | |
| simulatePlayerMessage(randomPlayer, response); | |
| // Occasionally trigger confetti for positive messages | |
| if (text.toLowerCase().includes("happy") || text.toLowerCase().includes("excited")) { | |
| if (Math.random() > 0.7) { | |
| triggerConfetti(); | |
| } | |
| } | |
| }, 1500); | |
| }, Math.random() * 3000 + 1000); | |
| } | |
| } | |
| // Add a player to the list | |
| function addPlayer(player) { | |
| gameState.players[player.id] = player; | |
| const playerElement = document.createElement('div'); | |
| playerElement.className = 'flex items-center space-x-2'; | |
| playerElement.id = `player-${player.id}`; | |
| playerElement.innerHTML = ` | |
| <div class="w-8 h-8 rounded-full flex items-center justify-center text-white" style="background-color: ${player.color}"> | |
| <i class="fas ${player.avatar}"></i> | |
| </div> | |
| <span class="text-sm">${player.name}</span> | |
| `; | |
| elements.playersList.appendChild(playerElement); | |
| updateUserCount(); | |
| } | |
| // Remove a player from the list | |
| function removePlayer(playerId) { | |
| const playerElement = document.getElementById(`player-${playerId}`); | |
| if (playerElement) { | |
| playerElement.remove(); | |
| } | |
| delete gameState.players[playerId]; | |
| updateUserCount(); | |
| } | |
| // Update the user count display | |
| function updateUserCount() { | |
| const count = Object.keys(gameState.players).length + 1; // +1 for current user | |
| elements.userCount.querySelector('span').textContent = count; | |
| } | |
| // Add a system message to the chat | |
| function addSystemMessage(text) { | |
| const messageElement = document.createElement('div'); | |
| messageElement.className = 'text-center my-4'; | |
| messageElement.innerHTML = ` | |
| <span class="inline-block px-3 py-1 bg-gray-100 text-gray-600 rounded-full text-sm"> | |
| ${text} | |
| </span> | |
| `; | |
| elements.messagesContainer.appendChild(messageElement); | |
| scrollToBottom(); | |
| } | |
| // Add a player message to the chat | |
| function addPlayerMessage(userId, text, timestamp) { | |
| const isCurrentUser = userId === gameState.currentUser?.id; | |
| const player = isCurrentUser ? gameState.currentUser : gameState.players[userId]; | |
| if (!player) return; | |
| const time = new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| const messageElement = document.createElement('div'); | |
| messageElement.className = `flex ${isCurrentUser ? 'justify-end' : 'justify-start'} mb-4`; | |
| if (isCurrentUser) { | |
| messageElement.innerHTML = ` | |
| <div class="max-w-xs md:max-w-md lg:max-w-lg"> | |
| <div class="bg-indigo-100 text-gray-800 p-3 rounded-lg rounded-tr-none"> | |
| <p>${text}</p> | |
| <div class="text-right mt-1"> | |
| <span class="text-xs text-gray-500">${time}</span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } else { | |
| messageElement.innerHTML = ` | |
| <div class="flex space-x-2 max-w-xs md:max-w-md lg:max-w-lg"> | |
| <div class="w-8 h-8 rounded-full flex items-center justify-center text-white" style="background-color: ${player.color}"> | |
| <i class="fas ${player.avatar}"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium text-xs text-gray-600 mb-1">${player.name}</div> | |
| <div class="bg-white border border-gray-200 text-gray-800 p-3 rounded-lg rounded-tl-none"> | |
| <p>${text}</p> | |
| <div class="text-right mt-1"> | |
| <span class="text-xs text-gray-500">${time}</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| elements.messagesContainer.appendChild(messageElement); | |
| scrollToBottom(); | |
| } | |
| // Handle typing indicator | |
| function handleTypingIndicator(userId, isTyping) { | |
| if (userId === gameState.currentUser?.id) return; | |
| const player = gameState.players[userId]; | |
| if (!player) return; | |
| if (isTyping) { | |
| elements.typingIndicator.innerHTML = ` | |
| <span class="typing-indicator">${player.name} is typing</span> | |
| `; | |
| elements.typingIndicator.classList.remove('hidden'); | |
| } else { | |
| elements.typingIndicator.classList.add('hidden'); | |
| } | |
| } | |
| // Scroll chat to bottom | |
| function scrollToBottom() { | |
| elements.messagesContainer.scrollTop = elements.messagesContainer.scrollHeight; | |
| } | |
| // Generate random ID | |
| function generateId() { | |
| return Math.random().toString(36).substr(2, 9); | |
| } | |
| // Get random color for player avatar | |
| function getRandomColor() { | |
| const colors = [ | |
| '#EF4444', '#F59E0B', '#10B981', '#3B82F6', '#6366F1', '#8B5CF6', '#EC4899' | |
| ]; | |
| return colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| // Get random avatar icon | |
| function getRandomAvatar() { | |
| const avatars = [ | |
| 'fa-user', 'fa-user-tie', 'fa-user-graduate', 'fa-user-nurse', | |
| 'fa-user-astronaut', 'fa-user-secret', 'fa-user-md', 'fa-user-ninja' | |
| ]; | |
| return avatars[Math.floor(Math.random() * avatars.length)]; | |
| } | |
| // Trigger confetti effect | |
| function triggerConfetti() { | |
| const colors = ['#EF4444', '#F59E0B', '#10B981', '#3B82F6', '#6366F1', '#8B5CF6', '#EC4899']; | |
| for (let i = 0; i < 50; i++) { | |
| const confetti = document.createElement('div'); | |
| confetti.className = 'confetti'; | |
| confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; | |
| confetti.style.left = `${Math.random() * 100}%`; | |
| confetti.style.transform = `rotate(${Math.random() * 360}deg)`; | |
| // Random size | |
| const size = Math.random() * 10 + 5; | |
| confetti.style.width = `${size}px`; | |
| confetti.style.height = `${size}px`; | |
| document.body.appendChild(confetti); | |
| // Animate | |
| const animationDuration = Math.random() * 3 + 2; | |
| const animationDelay = Math.random() * 0.5; | |
| confetti.style.animation = ` | |
| confetti-fall ${animationDuration}s ease-in ${animationDelay}s forwards | |
| `; | |
| // Remove after animation | |
| setTimeout(() => { | |
| confetti.remove(); | |
| }, (animationDuration + animationDelay) * 1000); | |
| } | |
| // Add confetti animation styles | |
| const style = document.createElement('style'); | |
| style.innerHTML = ` | |
| @keyframes confetti-fall { | |
| 0% { | |
| opacity: 1; | |
| transform: translateY(-100vh) rotate(0deg); | |
| } | |
| 100% { | |
| opacity: 0; | |
| transform: translateY(100vh) rotate(360deg); | |
| } | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| // Initialize the game when the page loads | |
| window.addEventListener('DOMContentLoaded', initGame); | |
| </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=C50BARZ/empathy-game" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |