Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>VIGO - Connect with Friends</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> | |
| .chat-bubble { | |
| max-width: 70%; | |
| border-radius: 1rem; | |
| padding: 0.75rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| .sender { | |
| background-color: #dcf8c6; | |
| align-self: flex-end; | |
| } | |
| .receiver { | |
| background-color: #ffffff; | |
| align-self: flex-start; | |
| } | |
| .status-viewer { | |
| height: 400px; | |
| background-size: cover; | |
| background-position: center; | |
| } | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #25d366 0%, #128c7e 100%); | |
| } | |
| .slide-enter-active, .slide-leave-active { | |
| transition: all 0.3s ease; | |
| } | |
| .slide-enter-from, .slide-leave-to { | |
| transform: translateX(100%); | |
| opacity: 0; | |
| } | |
| .fade-enter-active, .fade-leave-active { | |
| transition: opacity 0.3s ease; | |
| } | |
| .fade-enter-from, .fade-leave-to { | |
| opacity: 0; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 font-sans"> | |
| <div id="app" class="min-h-screen"> | |
| <!-- Auth Screen --> | |
| <div v-if="!isAuthenticated" class="flex items-center justify-center min-h-screen gradient-bg"> | |
| <div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-md mx-4"> | |
| <div class="text-center mb-8"> | |
| <img src="https://via.placeholder.com/80" alt="VIGO Logo" class="mx-auto mb-4 rounded-full"> | |
| <h1 class="text-3xl font-bold text-gray-800">VIGO</h1> | |
| <p class="text-gray-600">Connect with friends and family</p> | |
| </div> | |
| <div v-if="authMode === 'login'" class="space-y-4"> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Phone or Email</label> | |
| <input v-model="loginIdentifier" type="text" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500"> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Password</label> | |
| <input v-model="loginPassword" type="password" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500"> | |
| </div> | |
| <button @click="login" class="w-full bg-green-500 text-white py-2 rounded-lg hover:bg-green-600 transition duration-200"> | |
| Log In | |
| </button> | |
| <p class="text-center text-gray-600 mt-4"> | |
| Don't have an account? | |
| <a @click="authMode = 'signup'" class="text-green-500 cursor-pointer hover:underline">Sign up</a> | |
| </p> | |
| </div> | |
| <div v-if="authMode === 'signup'" class="space-y-4"> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Full Name</label> | |
| <input v-model="signupName" type="text" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500"> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Phone or Email</label> | |
| <input v-model="signupIdentifier" type="text" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500"> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Password</label> | |
| <input v-model="signupPassword" type="password" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500"> | |
| </div> | |
| <button @click="signup" class="w-full bg-green-500 text-white py-2 rounded-lg hover:bg-green-600 transition duration-200"> | |
| Sign Up | |
| </button> | |
| <p class="text-center text-gray-600 mt-4"> | |
| Already have an account? | |
| <a @click="authMode = 'login'" class="text-green-500 cursor-pointer hover:underline">Log in</a> | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main App --> | |
| <div v-if="isAuthenticated" class="flex h-screen"> | |
| <!-- Sidebar --> | |
| <div class="w-1/4 border-r bg-white flex flex-col"> | |
| <!-- Header --> | |
| <div class="p-3 bg-gray-100 flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <img :src="currentUser.avatar" alt="Profile" class="w-10 h-10 rounded-full mr-2"> | |
| <span class="font-semibold">{{ currentUser.name }}</span> | |
| </div> | |
| <div class="flex space-x-4 text-gray-600"> | |
| <i class="fas fa-search cursor-pointer"></i> | |
| <i class="fas fa-ellipsis-v cursor-pointer"></i> | |
| </div> | |
| </div> | |
| <!-- Tabs --> | |
| <div class="flex border-b"> | |
| <button @click="activeTab = 'chats'" :class="{'text-green-500 border-b-2 border-green-500': activeTab === 'chats'}" class="flex-1 py-3 font-medium"> | |
| Chats | |
| </button> | |
| <button @click="activeTab = 'status'" :class="{'text-green-500 border-b-2 border-green-500': activeTab === 'status'}" class="flex-1 py-3 font-medium"> | |
| Status | |
| </button> | |
| <button @click="activeTab = 'news'" :class="{'text-green-500 border-b-2 border-green-500': activeTab === 'news'}" class="flex-1 py-3 font-medium"> | |
| News | |
| </button> | |
| </div> | |
| <!-- Chats List --> | |
| <div v-if="activeTab === 'chats'" class="flex-1 overflow-y-auto"> | |
| <div v-for="chat in chats" :key="chat.id" @click="openChat(chat)" class="p-3 border-b flex items-center hover:bg-gray-100 cursor-pointer"> | |
| <img :src="chat.avatar" alt="Profile" class="w-12 h-12 rounded-full mr-3"> | |
| <div class="flex-1"> | |
| <div class="flex justify-between"> | |
| <span class="font-semibold">{{ chat.name }}</span> | |
| <span class="text-xs text-gray-500">{{ chat.lastMessageTime }}</span> | |
| </div> | |
| <p class="text-sm text-gray-600 truncate">{{ chat.lastMessage }}</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Status List --> | |
| <div v-if="activeTab === 'status'" class="flex-1 overflow-y-auto"> | |
| <div class="p-3 border-b"> | |
| <div class="flex items-center mb-3"> | |
| <div class="relative"> | |
| <img :src="currentUser.avatar" alt="Profile" class="w-12 h-12 rounded-full mr-3"> | |
| <div class="absolute bottom-0 right-2 bg-green-500 rounded-full w-4 h-4 flex items-center justify-center"> | |
| <i class="fas fa-plus text-white text-xs"></i> | |
| </div> | |
| </div> | |
| <div> | |
| <p class="font-semibold">My Status</p> | |
| <p class="text-sm text-gray-600">Tap to add status update</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="p-3"> | |
| <p class="text-gray-500 uppercase text-xs mb-2">Recent updates</p> | |
| <div v-for="status in statusUpdates" :key="status.id" @click="viewStatus(status)" class="flex items-center mb-3 hover:bg-gray-100 p-2 rounded cursor-pointer"> | |
| <div class="relative"> | |
| <img :src="status.user.avatar" alt="Profile" class="w-12 h-12 rounded-full mr-3 border-2 border-green-500"> | |
| </div> | |
| <div> | |
| <p class="font-semibold">{{ status.user.name }}</p> | |
| <p class="text-sm text-gray-600">{{ status.time }}</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- News Feed --> | |
| <div v-if="activeTab === 'news'" class="flex-1 overflow-y-auto p-3"> | |
| <div class="mb-4"> | |
| <div class="flex items-center mb-3"> | |
| <img :src="currentUser.avatar" alt="Profile" class="w-10 h-10 rounded-full mr-2"> | |
| <input type="text" placeholder="What's on your mind?" class="flex-1 bg-gray-100 rounded-full py-2 px-4 focus:outline-none"> | |
| </div> | |
| <div class="flex justify-between border-t pt-2"> | |
| <button class="flex items-center text-gray-600 hover:bg-gray-100 px-2 py-1 rounded"> | |
| <i class="fas fa-image text-green-500 mr-1"></i> | |
| <span>Photo</span> | |
| </button> | |
| <button class="flex items-center text-gray-600 hover:bg-gray-100 px-2 py-1 rounded"> | |
| <i class="fas fa-user-tag text-blue-500 mr-1"></i> | |
| <span>Tag</span> | |
| </button> | |
| <button class="flex items-center text-gray-600 hover:bg-gray-100 px-2 py-1 rounded"> | |
| <i class="fas fa-map-marker-alt text-red-500 mr-1"></i> | |
| <span>Location</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div v-for="post in newsFeed" :key="post.id" class="bg-white rounded-lg shadow mb-4"> | |
| <div class="p-3"> | |
| <div class="flex items-center mb-2"> | |
| <img :src="post.user.avatar" alt="Profile" class="w-8 h-8 rounded-full mr-2"> | |
| <div> | |
| <p class="font-semibold text-sm">{{ post.user.name }}</p> | |
| <p class="text-xs text-gray-500">{{ post.time }}</p> | |
| </div> | |
| </div> | |
| <p class="mb-2">{{ post.content }}</p> | |
| <img v-if="post.image" :src="post.image" alt="Post" class="w-full rounded mb-2"> | |
| <div class="flex justify-between text-gray-500 text-sm border-t pt-2"> | |
| <button class="flex items-center hover:text-green-500"> | |
| <i class="far fa-thumbs-up mr-1"></i> | |
| <span>{{ post.likes }}</span> | |
| </button> | |
| <button class="hover:text-green-500"> | |
| <span>{{ post.comments }} comments</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Chat Area --> | |
| <div v-if="activeChat" class="flex-1 flex flex-col bg-gray-50"> | |
| <!-- Chat Header --> | |
| <div class="p-3 bg-gray-100 flex justify-between items-center border-b"> | |
| <div class="flex items-center"> | |
| <img :src="activeChat.avatar" alt="Profile" class="w-10 h-10 rounded-full mr-3"> | |
| <div> | |
| <p class="font-semibold">{{ activeChat.name }}</p> | |
| <p class="text-xs text-gray-600">Online</p> | |
| </div> | |
| </div> | |
| <div class="flex space-x-4 text-gray-600"> | |
| <i class="fas fa-search cursor-pointer"></i> | |
| <i class="fas fa-phone-alt cursor-pointer"></i> | |
| <i class="fas fa-video cursor-pointer"></i> | |
| <i class="fas fa-ellipsis-v cursor-pointer"></i> | |
| </div> | |
| </div> | |
| <!-- Messages --> | |
| <div class="flex-1 overflow-y-auto p-4 space-y-2 flex flex-col"> | |
| <div v-for="message in activeChat.messages" :key="message.id" :class="{'sender': message.sender === 'me', 'receiver': message.sender !== 'me'}" class="chat-bubble"> | |
| <p>{{ message.text }}</p> | |
| <p class="text-xs text-gray-500 text-right mt-1">{{ message.time }}</p> | |
| </div> | |
| </div> | |
| <!-- Message Input --> | |
| <div class="p-3 bg-white border-t flex items-center"> | |
| <i class="fas fa-smile text-gray-500 text-xl mx-2 cursor-pointer"></i> | |
| <i class="fas fa-paperclip text-gray-500 text-xl mx-2 cursor-pointer"></i> | |
| <input v-model="newMessage" @keyup.enter="sendMessage" type="text" placeholder="Type a message" class="flex-1 border rounded-full py-2 px-4 focus:outline-none"> | |
| <button @click="sendMessage" class="ml-2 bg-green-500 text-white rounded-full w-10 h-10 flex items-center justify-center"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Status Viewer --> | |
| <div v-if="viewingStatus" class="fixed inset-0 bg-black z-50 flex items-center justify-center"> | |
| <div class="status-viewer w-full max-w-md" :style="{'background-image': 'url(' + viewingStatus.image + ')'}"> | |
| <div class="bg-gradient-to-t from-black to-transparent h-full flex flex-col justify-between p-4"> | |
| <div class="flex justify-between items-center"> | |
| <button @click="viewingStatus = null" class="text-white"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| <div class="flex items-center"> | |
| <img :src="viewingStatus.user.avatar" alt="Profile" class="w-8 h-8 rounded-full mr-2"> | |
| <div> | |
| <p class="text-white font-semibold">{{ viewingStatus.user.name }}</p> | |
| <p class="text-white text-xs">{{ viewingStatus.time }}</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-center mb-4"> | |
| <input type="text" placeholder="Send message" class="bg-black bg-opacity-50 text-white rounded-full py-2 px-4 w-full max-w-xs focus:outline-none"> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- No Chat Selected --> | |
| <div v-if="!activeChat && activeTab !== 'news'" class="flex-1 flex flex-col items-center justify-center bg-gray-50"> | |
| <div class="text-center p-8"> | |
| <img src="https://via.placeholder.com/150" alt="VIGO" class="mx-auto mb-4"> | |
| <h2 class="text-2xl font-semibold text-gray-700 mb-2">VIGO Web</h2> | |
| <p class="text-gray-600 mb-6">Send and receive messages without keeping your phone online.</p> | |
| <p class="text-gray-500 text-sm">Select a chat to start messaging</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/vue@3.2.47/dist/vue.global.min.js"></script> | |
| <script> | |
| const { createApp, ref } = Vue; | |
| createApp({ | |
| setup() { | |
| const isAuthenticated = ref(false); | |
| const authMode = ref('login'); | |
| const loginIdentifier = ref(''); | |
| const loginPassword = ref(''); | |
| const signupName = ref(''); | |
| const signupIdentifier = ref(''); | |
| const signupPassword = ref(''); | |
| const activeTab = ref('chats'); | |
| const activeChat = ref(null); | |
| const newMessage = ref(''); | |
| const viewingStatus = ref(null); | |
| const currentUser = ref({ | |
| id: 1, | |
| name: 'John Doe', | |
| avatar: 'https://randomuser.me/api/portraits/men/1.jpg', | |
| phone: '+1234567890', | |
| email: 'john@example.com' | |
| }); | |
| const chats = ref([ | |
| { | |
| id: 1, | |
| name: 'Sarah Smith', | |
| avatar: 'https://randomuser.me/api/portraits/women/1.jpg', | |
| lastMessage: 'Hey, how are you doing?', | |
| lastMessageTime: '10:30 AM', | |
| messages: [ | |
| { id: 1, text: 'Hey there!', sender: 'them', time: '10:20 AM' }, | |
| { id: 2, text: 'How are you?', sender: 'them', time: '10:21 AM' }, | |
| { id: 3, text: "I'm good, thanks! How about you?", sender: 'me', time: '10:25 AM' }, | |
| { id: 4, text: 'Hey, how are you doing?', sender: 'them', time: '10:30 AM' } | |
| ] | |
| }, | |
| { | |
| id: 2, | |
| name: 'Mike Johnson', | |
| avatar: 'https://randomuser.me/api/portraits/men/2.jpg', | |
| lastMessage: 'Meeting at 3 PM tomorrow', | |
| lastMessageTime: 'Yesterday', | |
| messages: [ | |
| { id: 1, text: 'Hi Mike!', sender: 'me', time: '9:00 AM' }, | |
| { id: 2, text: 'Hello! How are you?', sender: 'them', time: '9:05 AM' }, | |
| { id: 3, text: 'Just wanted to confirm our meeting', sender: 'me', time: '9:10 AM' }, | |
| { id: 4, text: 'Meeting at 3 PM tomorrow', sender: 'them', time: '9:15 AM' } | |
| ] | |
| }, | |
| { | |
| id: 3, | |
| name: 'Emma Wilson', | |
| avatar: 'https://randomuser.me/api/portraits/women/2.jpg', | |
| lastMessage: 'The documents are ready', | |
| lastMessageTime: 'Monday', | |
| messages: [ | |
| { id: 1, text: 'Hi Emma, are the documents ready?', sender: 'me', time: '2:00 PM' }, | |
| { id: 2, text: 'Working on them now', sender: 'them', time: '2:30 PM' }, | |
| { id: 3, text: 'Thanks!', sender: 'me', time: '2:35 PM' }, | |
| { id: 4, text: 'The documents are ready', sender: 'them', time: '4:00 PM' } | |
| ] | |
| } | |
| ]); | |
| const statusUpdates = ref([ | |
| { | |
| id: 1, | |
| user: { | |
| id: 2, | |
| name: 'Sarah Smith', | |
| avatar: 'https://randomuser.me/api/portraits/women/1.jpg' | |
| }, | |
| image: 'https://picsum.photos/500/800?random=1', | |
| time: '30 minutes ago' | |
| }, | |
| { | |
| id: 2, | |
| user: { | |
| id: 3, | |
| name: 'Mike Johnson', | |
| avatar: 'https://randomuser.me/api/portraits/men/2.jpg' | |
| }, | |
| image: 'https://picsum.photos/500/800?random=2', | |
| time: '1 hour ago' | |
| }, | |
| { | |
| id: 3, | |
| user: { | |
| id: 4, | |
| name: 'Emma Wilson', | |
| avatar: 'https://randomuser.me/api/portraits/women/2.jpg' | |
| }, | |
| image: 'https://picsum.photos/500/800?random=3', | |
| time: '2 hours ago' | |
| } | |
| ]); | |
| const newsFeed = ref([ | |
| { | |
| id: 1, | |
| user: { | |
| id: 2, | |
| name: 'Sarah Smith', | |
| avatar: 'https://randomuser.me/api/portraits/women/1.jpg' | |
| }, | |
| content: 'Just finished my morning hike! The view was amazing.', | |
| image: 'https://picsum.photos/500/300?random=4', | |
| time: '2 hours ago', | |
| likes: 24, | |
| comments: 5 | |
| }, | |
| { | |
| id: 2, | |
| user: { | |
| id: 3, | |
| name: 'Mike Johnson', | |
| avatar: 'https://randomuser.me/api/portraits/men/2.jpg' | |
| }, | |
| content: 'Working on a new project. Can\'t wait to share it with everyone!', | |
| time: '5 hours ago', | |
| likes: 12, | |
| comments: 3 | |
| }, | |
| { | |
| id: 3, | |
| user: { | |
| id: 4, | |
| name: 'Emma Wilson', | |
| avatar: 'https://randomuser.me/api/portraits/women/2.jpg' | |
| }, | |
| content: 'Beautiful day at the beach with friends!', | |
| image: 'https://picsum.photos/500/300?random=5', | |
| time: '1 day ago', | |
| likes: 42, | |
| comments: 8 | |
| } | |
| ]); | |
| const login = () => { | |
| // Simple validation | |
| if (!loginIdentifier.value || !loginPassword.value) { | |
| alert('Please enter both phone/email and password'); | |
| return; | |
| } | |
| // In a real app, you would make an API call here | |
| isAuthenticated.value = true; | |
| }; | |
| const signup = () => { | |
| // Simple validation | |
| if (!signupName.value || !signupIdentifier.value || !signupPassword.value) { | |
| alert('Please fill in all fields'); | |
| return; | |
| } | |
| // In a real app, you would make an API call here | |
| isAuthenticated.value = true; | |
| }; | |
| const openChat = (chat) => { | |
| activeChat.value = chat; | |
| }; | |
| const sendMessage = () => { | |
| if (!newMessage.value.trim()) return; | |
| const chat = activeChat.value; | |
| const now = new Date(); | |
| const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| chat.messages.push({ | |
| id: chat.messages.length + 1, | |
| text: newMessage.value, | |
| sender: 'me', | |
| time: timeString | |
| }); | |
| chat.lastMessage = newMessage.value; | |
| chat.lastMessageTime = timeString; | |
| newMessage.value = ''; | |
| // Auto-scroll to bottom | |
| setTimeout(() => { | |
| const chatContainer = document.querySelector('.overflow-y-auto'); | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| }, 10); | |
| }; | |
| const viewStatus = (status) => { | |
| viewingStatus.value = status; | |
| }; | |
| return { | |
| isAuthenticated, | |
| authMode, | |
| loginIdentifier, | |
| loginPassword, | |
| signupName, | |
| signupIdentifier, | |
| signupPassword, | |
| activeTab, | |
| activeChat, | |
| newMessage, | |
| viewingStatus, | |
| currentUser, | |
| chats, | |
| statusUpdates, | |
| newsFeed, | |
| login, | |
| signup, | |
| openChat, | |
| sendMessage, | |
| viewStatus | |
| }; | |
| } | |
| }).mount('#app'); | |
| </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=Anthem7b4k/anthem-space" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |