| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Modern Message UI Simulator</title> |
| |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| |
| <style> |
| |
| |
| |
| |
| :root { |
| --primary-color: #4f46e5; |
| --primary-hover: #4338ca; |
| --secondary-bg: #f3f4f6; |
| --white: #ffffff; |
| --text-dark: #111827; |
| --text-gray: #6b7280; |
| --text-light: #9ca3af; |
| --border-color: #e5e7eb; |
| --incoming-bubble: #e5e7eb; |
| --outgoing-bubble: #4f46e5; |
| --outgoing-text: #ffffff; |
| --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); |
| --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
| --font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; |
| --header-height: 60px; |
| } |
| |
| * { |
| box-sizing: border-box; |
| margin: 0; |
| padding: 0; |
| } |
| |
| body { |
| font-family: var(--font-family); |
| background-color: #f9fafb; |
| height: 100vh; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| color: var(--text-dark); |
| } |
| |
| |
| |
| |
| header { |
| background-color: var(--white); |
| height: var(--header-height); |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 0 20px; |
| border-bottom: 1px solid var(--border-color); |
| box-shadow: var(--shadow-sm); |
| z-index: 10; |
| } |
| |
| .logo { |
| font-size: 1.25rem; |
| font-weight: 700; |
| color: var(--primary-color); |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .brand-link { |
| text-decoration: none; |
| color: var(--text-light); |
| font-size: 0.9rem; |
| transition: color 0.2s; |
| } |
| |
| .brand-link:hover { |
| color: var(--primary-color); |
| } |
| |
| |
| |
| |
| |
| main { |
| display: grid; |
| grid-template-columns: 320px 1fr; |
| flex: 1; |
| height: calc(100vh - var(--header-height)); |
| overflow: hidden; |
| } |
| |
| |
| |
| |
| .sidebar { |
| background-color: var(--white); |
| border-right: 1px solid var(--border-color); |
| display: flex; |
| flex-direction: column; |
| overflow-y: auto; |
| } |
| |
| .search-bar { |
| padding: 15px; |
| border-bottom: 1px solid var(--border-color); |
| position: sticky; |
| top: 0; |
| background: var(--white); |
| z-index: 5; |
| } |
| |
| .search-input-wrapper { |
| position: relative; |
| } |
| |
| .search-input-wrapper i { |
| position: absolute; |
| left: 12px; |
| top: 50%; |
| transform: translateY(-50%); |
| color: var(--text-light); |
| font-size: 0.9rem; |
| } |
| |
| .search-input { |
| width: 100%; |
| padding: 10px 10px 10px 35px; |
| border-radius: 8px; |
| border: 1px solid var(--border-color); |
| background-color: var(--secondary-bg); |
| font-size: 0.9rem; |
| outline: none; |
| transition: border-color 0.2s; |
| } |
| |
| .search-input:focus { |
| border-color: var(--primary-color); |
| background-color: var(--white); |
| } |
| |
| .contact-list { |
| list-style: none; |
| } |
| |
| .contact-item { |
| display: flex; |
| align-items: center; |
| padding: 15px; |
| cursor: pointer; |
| transition: background-color 0.2s; |
| border-bottom: 1px solid transparent; |
| } |
| |
| .contact-item:hover { |
| background-color: var(--secondary-bg); |
| } |
| |
| .contact-item.active { |
| background-color: #eef2ff; |
| border-left: 4px solid var(--primary-color); |
| } |
| |
| .avatar { |
| width: 48px; |
| height: 48px; |
| border-radius: 50%; |
| object-fit: cover; |
| margin-right: 15px; |
| border: 2px solid var(--white); |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| } |
| |
| .contact-info { |
| flex: 1; |
| min-width: 0; |
| } |
| |
| .contact-name { |
| font-weight: 600; |
| font-size: 1rem; |
| margin-bottom: 4px; |
| color: var(--text-dark); |
| display: flex; |
| justify-content: space-between; |
| } |
| |
| .last-message-time { |
| font-size: 0.75rem; |
| color: var(--text-light); |
| font-weight: 400; |
| } |
| |
| .last-message { |
| font-size: 0.85rem; |
| color: var(--text-gray); |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| |
| |
| |
| .chat-area { |
| background-color: #f0f2f5; |
| display: flex; |
| flex-direction: column; |
| position: relative; |
| } |
| |
| |
| .chat-header { |
| background-color: var(--white); |
| padding: 15px 20px; |
| display: flex; |
| align-items: center; |
| border-bottom: 1px solid var(--border-color); |
| box-shadow: var(--shadow-sm); |
| } |
| |
| .chat-user-info { |
| display: flex; |
| align-items: center; |
| gap: 15px; |
| } |
| |
| .user-status { |
| display: flex; |
| flex-direction: column; |
| } |
| |
| .status-dot { |
| display: inline-block; |
| width: 8px; |
| height: 8px; |
| background-color: #10b981; |
| border-radius: 50%; |
| margin-right: 5px; |
| } |
| |
| .status-text { |
| font-size: 0.8rem; |
| color: var(--text-gray); |
| } |
| |
| .chat-actions { |
| margin-left: auto; |
| display: flex; |
| gap: 20px; |
| color: var(--primary-color); |
| font-size: 1.2rem; |
| cursor: pointer; |
| } |
| |
| |
| .messages-container { |
| flex: 1; |
| padding: 20px; |
| overflow-y: auto; |
| display: flex; |
| flex-direction: column; |
| gap: 15px; |
| background-image: radial-gradient(#d1d5db 1px, transparent 1px); |
| background-size: 20px 20px; |
| } |
| |
| .message { |
| display: flex; |
| max-width: 70%; |
| animation: fadeIn 0.3s ease; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .message.incoming { |
| align-self: flex-start; |
| } |
| |
| .message.outgoing { |
| align-self: flex-end; |
| flex-direction: row-reverse; |
| } |
| |
| .message-content { |
| padding: 12px 16px; |
| border-radius: 18px; |
| font-size: 0.95rem; |
| line-height: 1.4; |
| position: relative; |
| box-shadow: var(--shadow-sm); |
| } |
| |
| .message.incoming .message-content { |
| background-color: var(--white); |
| color: var(--text-dark); |
| border-top-left-radius: 4px; |
| } |
| |
| .message.outgoing .message-content { |
| background-color: var(--outgoing-bubble); |
| color: var(--outgoing-text); |
| border-top-right-radius: 4px; |
| } |
| |
| .message-time { |
| font-size: 0.7rem; |
| margin-top: 5px; |
| opacity: 0.7; |
| text-align: right; |
| display: block; |
| } |
| |
| .message.outgoing .message-time { |
| color: rgba(255, 255, 255, 0.8); |
| } |
| |
| |
| .input-area { |
| background-color: var(--white); |
| padding: 20px; |
| border-top: 1px solid var(--border-color); |
| display: flex; |
| align-items: center; |
| gap: 15px; |
| } |
| |
| .attach-btn { |
| color: var(--text-gray); |
| font-size: 1.2rem; |
| cursor: pointer; |
| transition: color 0.2s; |
| } |
| |
| .attach-btn:hover { |
| color: var(--primary-color); |
| } |
| |
| .message-input { |
| flex: 1; |
| padding: 12px 20px; |
| border-radius: 25px; |
| border: 1px solid var(--border-color); |
| background-color: var(--secondary-bg); |
| font-size: 1rem; |
| outline: none; |
| transition: all 0.2s; |
| } |
| |
| .message-input:focus { |
| background-color: var(--white); |
| border-color: var(--primary-color); |
| box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1); |
| } |
| |
| .send-btn { |
| background-color: var(--primary-color); |
| color: var(--white); |
| width: 45px; |
| height: 45px; |
| border-radius: 50%; |
| border: none; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: background-color 0.2s, transform 0.1s; |
| } |
| |
| .send-btn:hover { |
| background-color: var(--primary-hover); |
| } |
| |
| .send-btn:active { |
| transform: scale(0.95); |
| } |
| |
| |
| |
| |
| |
| @media (max-width: 768px) { |
| main { |
| grid-template-columns: 1fr; |
| } |
| |
| .sidebar { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| z-index: 20; |
| transform: translateX(0); |
| transition: transform 0.3s ease-in-out; |
| } |
| |
| .sidebar.hidden { |
| transform: translateX(-100%); |
| } |
| |
| .back-btn { |
| display: block !important; |
| margin-right: 10px; |
| cursor: pointer; |
| color: var(--text-dark); |
| } |
| } |
| |
| .back-btn { |
| display: none; |
| font-size: 1.2rem; |
| } |
| |
| |
| .empty-state { |
| display: none; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| height: 100%; |
| color: var(--text-gray); |
| text-align: center; |
| } |
| |
| .empty-state i { |
| font-size: 4rem; |
| margin-bottom: 20px; |
| color: var(--text-light); |
| } |
| |
| .no-chat-selected .empty-state { |
| display: flex; |
| } |
| |
| .no-chat-selected .chat-header, |
| .no-chat-selected .messages-container, |
| .no-chat-selected .input-area { |
| display: none; |
| } |
| |
| </style> |
| </head> |
| <body> |
|
|
| <header> |
| <div class="logo"> |
| <i class="fa-solid fa-comments"></i> |
| <span>MsgReader</span> |
| </div> |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link"> |
| Built with anycoder |
| </a> |
| </header> |
|
|
| <main> |
| |
| <aside class="sidebar" id="sidebar"> |
| <div class="search-bar"> |
| <div class="search-input-wrapper"> |
| <i class="fa-solid fa-search"></i> |
| <input type="text" class="search-input" placeholder="Search conversations..." id="searchInput"> |
| </div> |
| </div> |
| <ul class="contact-list" id="contactList"> |
| |
| </ul> |
| </aside> |
|
|
| |
| <section class="chat-area no-chat-selected" id="chatArea"> |
| |
| <div class="empty-state"> |
| <i class="fa-regular fa-paper-plane"></i> |
| <h2>Select a conversation</h2> |
| <p>Choose a contact from the list to view messages.</p> |
| </div> |
|
|
| |
| <div class="chat-header"> |
| <div class="chat-user-info"> |
| <i class="fa-solid fa-arrow-left back-btn" id="backBtn"></i> |
| <img src="" alt="User" class="avatar" id="currentAvatar"> |
| <div class="user-status"> |
| <span class="contact-name" id="currentName">User Name</span> |
| <span class="status-text"><span class="status-dot"></span>Online</span> |
| </div> |
| </div> |
| <div class="chat-actions"> |
| <i class="fa-solid fa-phone"></i> |
| <i class="fa-solid fa-video"></i> |
| <i class="fa-solid fa-circle-info"></i> |
| </div> |
| </div> |
|
|
| <div class="messages-container" id="messagesContainer"> |
| |
| </div> |
|
|
| <div class="input-area"> |
| <i class="fa-solid fa-paperclip attach-btn"></i> |
| <i class="fa-regular fa-image attach-btn"></i> |
| <input type="text" class="message-input" placeholder="Type a message..." id="messageInput"> |
| <button class="send-btn" id="sendBtn"> |
| <i class="fa-solid fa-paper-plane"></i> |
| </button> |
| </div> |
| </section> |
| </main> |
|
|
| <script> |
| |
| const contactsData = [ |
| { |
| id: 1, |
| name: "Alice Johnson", |
| avatar: "https://picsum.photos/seed/alice/100/100", |
| time: "10:30 AM", |
| messages: [ |
| { id: 1, text: "Hey! Are we still on for lunch?", type: "incoming", time: "10:28 AM" }, |
| { id: 2, text: "Yes, definitely. 12:30 at the usual spot?", type: "outgoing", time: "10:29 AM" }, |
| { id: 3, text: "Perfect. See you there!", type: "incoming", time: "10:30 AM" } |
| ] |
| }, |
| { |
| id: 2, |
| name: "Bob Smith", |
| avatar: "https://picsum.photos/seed/bob/100/100", |
| time: "Yesterday", |
| messages: [ |
| { id: 1, text: "Did you see the game last night?", type: "incoming", time: "Yesterday" }, |
| { id: 2, text: "No, I missed it. Who won?", type: "outgoing", time: "Yesterday" }, |
| { id: 3, text: "It was incredible! We won by 2 points.", type: "incoming", time: "Yesterday" } |
| ] |
| }, |
| { |
| id: 3, |
| name: "Charlie Davis", |
| avatar: "https://picsum.photos/seed/charlie/100/100", |
| time: "Monday", |
| messages: [ |
| { id: 1, text: "Can you send me the project files?", type: "incoming", time: "Monday" }, |
| { id: 2, text: "Sure thing, uploading them now.", type: "outgoing", time: "Monday" } |
| ] |
| }, |
| { |
| id: 4, |
| name: "Diana Evans", |
| avatar: "https://picsum.photos/seed/diana/100/100", |
| time: "Sunday", |
| messages: [ |
| { id: 1, text: "Happy Birthday! 🎂", type: "incoming", time: "Sunday" }, |
| { id: 2, text: "Thank you so much! 🥳", type: "outgoing", time: "Sunday" } |
| ] |
| }, |
| { |
| id: 5, |
| name: "Ethan Hunt", |
| avatar: "https://picsum.photos/seed/ethan/100/100", |
| time: "Jan 20", |
| messages: [ |
| { id: 1, text: "Mission accomplished.", type: "incoming", time: "Jan 20" }, |
| ] |
| } |
| ]; |
| |
| let activeContactId = null; |
| |
| |
| const contactListEl = document.getElementById('contactList'); |
| const messagesContainerEl = document.getElementById('messagesContainer'); |
| const messageInputEl = document.getElementById('messageInput'); |
| const sendBtnEl = document.getElementById('sendBtn'); |
| const currentAvatarEl = document.getElementById('currentAvatar'); |
| const currentNameEl = document.getElementById('currentName'); |
| const chatAreaEl = document.getElementById('chatArea'); |
| const sidebarEl = document.getElementById('sidebar'); |
| const backBtnEl = document.getElementById('backBtn'); |
| const searchInputEl = document.getElementById('searchInput'); |
| |
| |
| function init() { |
| renderContacts(contactsData); |
| setupEventListeners(); |
| } |
| |
| |
| function renderContacts(contacts) { |
| contactListEl.innerHTML = ''; |
| contacts.forEach(contact => { |
| const lastMsg = contact.messages[contact.messages.length - 1]; |
| const li = document.createElement('li'); |
| li.className = `contact-item ${activeContactId === contact.id ? 'active' : ''}`; |
| li.onclick = () => selectContact(contact.id); |
| |
| li.innerHTML = ` |
| <img src="${contact.avatar}" alt="${contact.name}" class="avatar"> |
| <div class="contact-info"> |
| <div class="contact-name"> |
| ${contact.name} |
| <span class="last-message-time">${contact.time}</span> |
| </div> |
| <div class="last-message">${lastMsg ? lastMsg.text : 'No messages'}</div> |
| </div> |
| `; |
| contactListEl.appendChild(li); |
| }); |
| } |
| |
| |
| function selectContact(id) { |
| activeContactId = id; |
| const contact = contactsData.find(c => c.id === id); |
| |
| |
| chatAreaEl.classList.remove('no-chat-selected'); |
| |
| |
| if (window.innerWidth <= 768) { |
| sidebarEl.classList.add('hidden'); |
| } |
| |
| |
| currentNameEl.textContent = contact.name; |
| currentAvatarEl.src = contact.avatar; |
| |
| |
| renderContacts(contactsData.filter(c => c.name.toLowerCase().includes(searchInputEl.value.toLowerCase()))); |
| |
| |
| renderMessages(contact.messages); |
| |
| |
| scrollToBottom(); |
| |
| |
| messageInputEl.focus(); |
| } |
| |
| |
| function renderMessages(messages) { |
| messagesContainerEl.innerHTML = ''; |
| messages.forEach(msg => { |
| const div = document.createElement('div'); |
| div.className = `message ${msg.type}`; |
| div.innerHTML = ` |
| <div class="message-content"> |
| ${msg.text} |
| <span class="message-time">${msg.time}</span> |
| </div> |
| `; |
| messagesContainerEl.appendChild(div); |
| }); |
| } |
| |
| |
| function sendMessage() { |
| const text = messageInputEl.value.trim(); |
| if (!text || !activeContactId) return; |
| |
| const contact = contactsData.find(c => c.id === activeContactId); |
| const now = new Date(); |
| const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); |
| |
| |
| const newMessage = { |
| id: contact.messages.length + 1, |
| text: text, |
| type: 'outgoing', |
| time: timeString |
| }; |
| contact.messages.push(newMessage); |
| |
| |
| renderMessages(contact.messages); |
| messageInputEl.value = ''; |
| scrollToBottom(); |
| |
| |
| renderContacts(contactsData); |
| |
| |
| setTimeout(() => { |
| simulateReply(contact); |
| }, 1500); |
| } |
| |
| |
| function simulateReply(contact) { |
| |
| if (activeContactId !== contact.id) return; |
| |
| const responses = ["That sounds great!", "I'll check and get back to you.", "Interesting...", "Ok.", "Can you elaborate?"]; |
| const randomResponse = responses[Math.floor(Math.random() * responses.length)]; |
| const now = new Date(); |
| const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); |
| |
| const replyMsg = { |
| id: contact.messages.length + 1, |
| text: randomResponse, |
| type: 'incoming', |
| time: timeString |
| }; |
| contact.messages.push(replyMsg); |
| |
| renderMessages(contact.messages); |
| scrollToBottom(); |
| renderContacts(contactsData); |
| } |
| |
| function scrollToBottom() { |
| messagesContainerEl.scrollTop = messagesContainerEl.scrollHeight; |
| } |
| |
| |
| function setupEventListeners() { |
| sendBtnEl.addEventListener('click', sendMessage); |
| |
| messageInputEl.addEventListener('keypress', (e) => { |
| if (e.key === 'Enter') { |
| sendMessage(); |
| } |
| }); |
| |
| |
| searchInputEl.addEventListener('input', (e) => { |
| const term = e.target.value.toLowerCase(); |
| const filtered = contactsData.filter(c => c.name.toLowerCase().includes(term)); |
| renderContacts(filtered); |
| }); |
| |
| |
| backBtnEl.addEventListener('click', () => { |
| sidebarEl.classList.remove('hidden'); |
| activeContactId = null; |
| chatAreaEl.classList.add('no-chat-selected'); |
| |
| document.querySelectorAll('.contact-item').forEach(el => el.classList.remove('active')); |
| }); |
| } |
| |
| |
| init(); |
| |
| </script> |
| </body> |
| </html> |