Exploring the capabilities of Bubble, a no-code platform, can be both exciting and overwhelming. To help you get started, we've compiled a list of ten impressive examples that showcase the versatility and power of Bubble.
From e-commerce sites to social networks, these examples highlight how Bubble can be used to create stunning, functional web applications without writing a single line of code. Dive in to see what's possible and get inspired for your next project.
CODE1
Here's the code:
CODETEXT1
CODE2
Here's the code:
CODETEXT2
CODE3
Here's the code:
CODETEXT3
CODE4
Here's the code:
CODETEXT4
CODE5
Here's the code:
CODETEXT5
Designers and developers, elevate your Bubble projects with Subframe's drag-and-drop interface. Its intuitive, responsive canvas ensures pixel-perfect UI every time, making it a favorite among professionals.
Join the community of satisfied users and start for free today!
CODE6
Here's the code:
CODETEXT6
CODE7
Here's the code:
CODETEXT7
CODE8
Here's the code:
CODETEXT8
CODE9
Here's the code:
CODETEXT9
CODE10
Here's the code:
CODETEXT10
Ready to elevate your UI design game? With Subframe, you can create pixel-perfect interfaces for Bubble and other platforms in minutes. Its drag-and-drop editor ensures efficiency and precision.
Don't waitโstart creating stunning UIs right away. Start for free today!
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f8f9fa; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; padding: 20px; background-image: radial-gradient(circle at 10% 20%, rgba(216, 241, 230, 0.46) 0%, rgba(233, 226, 248, 0.46) 90.1%); } .app-container { width: 100%; max-width: 700px; height: 700px; background-color: #ffffff; border-radius: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); display: flex; flex-direction: column; overflow: hidden; position: relative; } .app-header { padding: 20px; background-color: #ffffff; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; justify-content: space-between; z-index: 10; box-shadow: 0 2px 15px rgba(0, 0, 0, 0.03); } .app-title { font-size: 1.2rem; font-weight: 600; color: #4a4a4a; } .user-info { display: flex; align-items: center; gap: 10px; } .user-avatar { width: 36px; height: 36px; border-radius: 50%; background-color: #e8d4f9; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #9370DB; border: 2px solid #ffffff; box-shadow: 0 2px 5px rgba(147, 112, 219, 0.2); transition: transform 0.3s ease; } .user-avatar:hover { transform: scale(1.1); } .online-status { width: 10px; height: 10px; background-color: #4CAF50; border-radius: 50%; border: 2px solid #ffffff; position: absolute; bottom: 0; right: 0; } .user-name { font-weight: 500; color: #4a4a4a; } .chat-container { flex: 1; padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; background-color: #fafbff; position: relative; } .chat-container::-webkit-scrollbar { width: 6px; } .chat-container::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 3px; } .chat-container::-webkit-scrollbar-thumb { background: #d8d8d8; border-radius: 3px; } .chat-container::-webkit-scrollbar-thumb:hover { background: #c5c5c5; } .date-separator { text-align: center; margin: 15px 0; color: #9e9e9e; font-size: 0.8rem; display: flex; align-items: center; justify-content: center; } .date-separator::before, .date-separator::after { content: ""; flex: 1; border-bottom: 1px solid #e0e0e0; margin: 0 10px; } .message { display: flex; flex-direction: column; max-width: 70%; position: relative; transition: all 0.3s ease; } .message.received { align-self: flex-start; } .message.sent { align-self: flex-end; } .message-content { padding: 12px 16px; border-radius: 18px; position: relative; cursor: pointer; overflow: hidden; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04); } .message.received .message-content { background-color: #E8F5FE; color: #333; border-bottom-left-radius: 4px; } .message.sent .message-content { background-color: #F0E6FF; color: #333; border-bottom-right-radius: 4px; } .message-time { font-size: 0.7rem; color: #9e9e9e; margin-top: 4px; align-self: flex-end; opacity: 0.8; } .message.received .message-time { align-self: flex-start; } .message.expanded .message-content { transform: scale(1.02); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); } .message-avatar { width: 28px; height: 28px; border-radius: 50%; position: absolute; bottom: -5px; display: flex; align-items: center; justify-content: center; font-size: 0.8rem; font-weight: bold; color: white; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .message.received .message-avatar { background-color: #c4e7ff; color: #1976D2; left: -10px; } .message.sent .message-avatar { background-color: #e8d4f9; color: #9370DB; right: -10px; } .message-reactions { display: flex; margin-top: 8px; gap: 5px; opacity: 0; transform: translateY(10px); transition: all 0.3s ease; } .message:hover .message-reactions { opacity: 1; transform: translateY(0); } .reaction { font-size: 0.9rem; padding: 3px 8px; background-color: #ffffff; border-radius: 12px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); cursor: pointer; transition: all 0.2s ease; } .reaction:hover { transform: scale(1.1); } .message.sent .message-reactions { justify-content: flex-end; } .emoji-popup { display: none; position: absolute; background-color: white; border-radius: 10px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); padding: 8px; gap: 8px; z-index: 100; bottom: 100%; margin-bottom: 10px; } .message.received .emoji-popup { left: 0; } .message.sent .emoji-popup { right: 0; } .emoji { cursor: pointer; font-size: 1.2rem; transition: transform 0.2s ease; padding: 5px; } .emoji:hover { transform: scale(1.2); } .typing-indicator { display: flex; align-items: center; gap: 4px; padding: 12px 16px; background-color: #E8F5FE; color: #333; border-radius: 18px; border-bottom-left-radius: 4px; max-width: fit-content; margin-top: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04); animation: fadeIn 0.5s ease; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .typing-dot { width: 8px; height: 8px; background-color: #9e9e9e; border-radius: 50%; } .typing-dot:nth-child(1) { animation: bounce 1.2s infinite 0s; } .typing-dot:nth-child(2) { animation: bounce 1.2s infinite 0.2s; } .typing-dot:nth-child(3) { animation: bounce 1.2s infinite 0.4s; } @keyframes bounce { 0%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-6px); } } .message-input-container { padding: 15px 20px; background-color: #ffffff; border-top: 1px solid #f0f0f0; display: flex; align-items: center; gap: 10px; box-shadow: 0 -2px 15px rgba(0, 0, 0, 0.03); } .message-input { flex: 1; padding: 12px 15px; border: none; border-radius: 20px; background-color: #f8f9fa; color: #4a4a4a; font-size: 0.95rem; outline: none; transition: all 0.3s ease; } .message-input:focus { box-shadow: 0 0 0 2px rgba(147, 112, 219, 0.2); background-color: #ffffff; } .input-actions { display: flex; align-items: center; gap: 10px; } .action-button { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background-color: transparent; border: none; color: #9e9e9e; cursor: pointer; transition: all 0.2s ease; } .action-button:hover { background-color: #f8f9fa; color: #9370DB; } .action-button.send { background-color: #9370DB; color: white; } .action-button.send:hover { background-color: #8a60d4; transform: translateY(-2px); box-shadow: 0 3px 10px rgba(147, 112, 219, 0.3); } .action-button.send:active { transform: translateY(0); } .action-button svg { width: 20px; height: 20px; } /* Added for tooltip-like text */ .expand-hint { position: absolute; bottom: 110%; left: 50%; transform: translateX(-50%); background-color: rgba(0,0,0,0.7); color: white; padding: 5px 10px; border-radius: 5px; font-size: 0.7rem; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; white-space: nowrap; } .message-content:hover .expand-hint { opacity: 1; } .message.expanded .expand-hint { display: none; } /* Notification dot */ .notification-dot { position: absolute; top: -3px; right: -3px; width: 8px; height: 8px; border-radius: 50%; background-color: #FF5722; animation: pulse 1.5s infinite; } @keyframes pulse { 0% { transform: scale(0.8); opacity: 0.8; } 50% { transform: scale(1); opacity: 1; } 100% { transform: scale(0.8); opacity: 0.8; } } /* Animation for new messages */ .message.new-message { animation: slideIn 0.5s ease; } @keyframes slideIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } /* Reaction animation */ .reaction-added { animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } @keyframes popIn { 0% { transform: scale(0); } 70% { transform: scale(1.2); } 100% { transform: scale(1); } } /* Emoji container */ .emoji-container { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; } .emoji-badge { background-color: #fff; border-radius: 12px; padding: 3px 8px; font-size: 0.8rem; display: flex; align-items: center; gap: 3px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .emoji-count { font-size: 0.7rem; color: #666; } </style> </head> <body> <div class="app-container"> <div class="app-header"> <div class="app-title">Bubble Chat</div> <div class="user-info"> <div class="user-name">Emma Wilson</div> <div class="user-avatar" style="position: relative;"> EW <div class="online-status"></div> </div> </div> </div> <div class="chat-container" id="chatContainer"> <div class="date-separator">Yesterday</div> <div class="message received"> <div class="message-content"> <span class="expand-hint">Tap to expand</span> Hey Emma! Just wanted to check if we're still on for brunch this weekend? The new cafรฉ on Main Street has those pastel-themed pastries you love! <div class="message-avatar">JS</div> </div> <div class="message-time">9:42 AM</div> <div class="message-reactions"> <div class="reaction" onclick="toggleEmojiPopup(this)">๐</div> <div class="emoji-popup"> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">โค๏ธ</span> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">๐ฎ</span> <span class="emoji" onclick="addReaction(this)">๐</span> </div> </div> </div> <div class="message sent"> <div class="message-content"> <span class="expand-hint">Tap to expand</span> Absolutely! I've been looking forward to it all week. Those lavender macarons looked amazing in their stories! Should we meet there directly at 11? <div class="message-avatar">EW</div> </div> <div class="message-time">9:45 AM</div> <div class="message-reactions"> <div class="reaction" onclick="toggleEmojiPopup(this)">๐ซถ</div> <div class="emoji-popup"> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">โค๏ธ</span> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">๐ฎ</span> <span class="emoji" onclick="addReaction(this)">๐</span> </div> </div> </div> <div class="date-separator">Today</div> <div class="message received"> <div class="message-content"> <span class="expand-hint">Tap to expand</span> Perfect! 11 works for me. Also, have you seen the new bubble chat feature they added? You can tap on messages to expand them and react with different emojis. Super cute with these pastel colors! <div class="message-avatar">JS</div> </div> <div class="message-time">10:18 AM</div> <div class="emoji-container"> <div class="emoji-badge"> <span>โค๏ธ</span> <span class="emoji-count">1</span> </div> </div> <div class="message-reactions"> <div class="reaction" onclick="toggleEmojiPopup(this)">๐</div> <div class="emoji-popup"> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">โค๏ธ</span> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">๐ฎ</span> <span class="emoji" onclick="addReaction(this)">๐</span> </div> </div> </div> <div class="message sent"> <div class="message-content"> <span class="expand-hint">Tap to expand</span> I know, right? I'm loving how the bubbles have these soft edges and expand so smoothly. The hover effects are subtle but so satisfying. Makes chatting feel more interactive! <div class="message-avatar">EW</div> </div> <div class="message-time">10:20 AM</div> <div class="emoji-container"> <div class="emoji-badge"> <span>๐</span> <span class="emoji-count">2</span> </div> </div> <div class="message-reactions"> <div class="reaction" onclick="toggleEmojiPopup(this)">๐ซถ</div> <div class="emoji-popup"> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">โค๏ธ</span> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">๐ฎ</span> <span class="emoji" onclick="addReaction(this)">๐</span> </div> </div> </div> <div class="message received"> <div class="message-content"> <span class="expand-hint">Tap to expand</span> BTW, did you try using dark mode? The pastel colors look even better against the darker background. The little notification dots are super cute too! <div class="message-avatar">JS</div> </div> <div class="message-time">10:22 AM</div> <div class="message-reactions"> <div class="reaction" onclick="toggleEmojiPopup(this)">๐</div> <div class="emoji-popup"> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">โค๏ธ</span> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">๐ฎ</span> <span class="emoji" onclick="addReaction(this)">๐</span> </div> </div> </div> <div class="typing-indicator"> <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> </div> </div> <div class="message-input-container"> <input type="text" class="message-input" placeholder="Type a message..." id="messageInput"> <div class="input-actions"> <button class="action-button"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> </button> <button class="action-button"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" /> </svg> </button> <button class="action-button send" id="sendButton"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /> </svg> </button> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { // Make messages expandable const messages = document.querySelectorAll('.message-content'); messages.forEach(message => { message.addEventListener('click', () => { const messageParent = message.closest('.message'); if (messageParent.classList.contains('expanded')) { messageParent.classList.remove('expanded'); } else { messageParent.classList.add('expanded'); } }); }); // Handle send message function const sendButton = document.getElementById('sendButton'); const messageInput = document.getElementById('messageInput'); const chatContainer = document.getElementById('chatContainer'); sendButton.addEventListener('click', sendMessage); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendMessage(); } }); function sendMessage() { const messageText = messageInput.value.trim(); if (messageText !== '') { // Remove typing indicator first const typingIndicator = document.querySelector('.typing-indicator'); if (typingIndicator) { typingIndicator.remove(); } // Create new message element const messageElement = document.createElement('div'); messageElement.className = 'message sent new-message'; messageElement.innerHTML = ` <div class="message-content"> <span class="expand-hint">Tap to expand</span> ${messageText} <div class="message-avatar">EW</div> </div> <div class="message-time">${getCurrentTime()}</div> <div class="message-reactions"> <div class="reaction" onclick="toggleEmojiPopup(this)">๐ซถ</div> <div class="emoji-popup"> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">โค๏ธ</span> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">๐ฎ</span> <span class="emoji" onclick="addReaction(this)">๐</span> </div> </div> `; chatContainer.appendChild(messageElement); // Make the new message expandable const newMessageContent = messageElement.querySelector('.message-content'); newMessageContent.addEventListener('click', () => { const messageParent = newMessageContent.closest('.message'); messageParent.classList.toggle('expanded'); }); // Clear input messageInput.value = ''; // Scroll to bottom chatContainer.scrollTop = chatContainer.scrollHeight; // Simulate response after a delay setTimeout(() => { showTypingIndicator(); }, 1000); setTimeout(() => { showResponse(); }, 3000); } } function getCurrentTime() { const now = new Date(); let hours = now.getHours(); const minutes = now.getMinutes(); const ampm = hours >= 12 ? 'PM' : 'AM'; hours = hours % 12; hours = hours ? hours : 12; // the hour '0' should be '12' const minutesStr = minutes < 10 ? '0' + minutes : minutes; return `${hours}:${minutesStr} ${ampm}`; } function showTypingIndicator() { const typingElement = document.createElement('div'); typingElement.className = 'typing-indicator'; typingElement.innerHTML = ` <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> `; chatContainer.appendChild(typingElement); chatContainer.scrollTop = chatContainer.scrollHeight; } function showResponse() { // Remove typing indicator const typingIndicator = document.querySelector('.typing-indicator'); if (typingIndicator) { typingIndicator.remove(); } const responses = [ "I haven't tried dark mode yet! Will check it out tonight. Does it automatically switch based on system settings?", "Those notification dots are so cute! I love how they pulse gently to get your attention without being too distracting.", "The gradient background is really nice too. Makes the whole chat feel more cozy somehow.", "Looking forward to our brunch tomorrow! Should I bring those cute stickers I found?" ]; const randomResponse = responses[Math.floor(Math.random() * responses.length)]; const responseElement = document.createElement('div'); responseElement.className = 'message received new-message'; responseElement.innerHTML = ` <div class="message-content"> <span class="expand-hint">Tap to expand</span> ${randomResponse} <div class="message-avatar">JS</div> </div> <div class="message-time">${getCurrentTime()}</div> <div class="message-reactions"> <div class="reaction" onclick="toggleEmojiPopup(this)">๐</div> <div class="emoji-popup"> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">โค๏ธ</span> <span class="emoji" onclick="addReaction(this)">๐</span> <span class="emoji" onclick="addReaction(this)">๐ฎ</span> <span class="emoji" onclick="addReaction(this)">๐</span> </div> </div> `; chatContainer.appendChild(responseElement); // Make the new response expandable const newResponseContent = responseElement.querySelector('.message-content'); newResponseContent.addEventListener('click', () => { const messageParent = newResponseContent.closest('.message'); messageParent.classList.toggle('expanded'); }); // Scroll to bottom chatContainer.scrollTop = chatContainer.scrollHeight; } // Scroll to bottom initially chatContainer.scrollTop = chatContainer.scrollHeight; }); // Toggle emoji popup function toggleEmojiPopup(element) { const popup = element.nextElementSibling; const isOpen = popup.style.display === 'flex'; // Close all emoji popups first document.querySelectorAll('.emoji-popup').forEach(p => { p.style.display = 'none'; }); // Toggle the clicked one if (!isOpen) { popup.style.display = 'flex'; } } // Add reaction to message function addReaction(emojiElement) { const emoji = emojiElement.innerText; const messageElement = emojiElement.closest('.message'); // Check if emoji container exists let emojiContainer = messageElement.querySelector('.emoji-container'); if (!emojiContainer) { emojiContainer = document.createElement('div'); emojiContainer.className = 'emoji-container'; // Insert after message-time const messageTime = messageElement.querySelector('.message-time'); messageTime.after(emojiContainer); } // Check if this emoji already exists let emojiBadge = Array.from(emojiContainer.querySelectorAll('.emoji-badge')).find(badge => badge.querySelector('span').innerText === emoji ); if (emojiBadge) { // Increment count const countElement = emojiBadge.querySelector('.emoji-count'); let count = parseInt(countElement.innerText); countElement.innerText = count + 1; emojiBadge.classList.add('reaction-added'); setTimeout(() => { emojiBadge.classList.remove('reaction-added'); }, 300); } else { // Create new badge const newBadge = document.createElement('div'); newBadge.className = 'emoji-badge reaction-added'; newBadge.innerHTML = ` <span>${emoji}</span> <span class="emoji-count">1</span> `; emojiContainer.appendChild(newBadge); setTimeout(() => { newBadge.classList.remove('reaction-added'); }, 300); } // Close the emoji popup const popup = emojiElement.closest('.emoji-popup'); popup.style.display = 'none'; } // Close emoji popups when clicking outside document.addEventListener('click', (e) => { if (!e.target.classList.contains('reaction') && !e.target.classList.contains('emoji')) { document.querySelectorAll('.emoji-popup').forEach(popup => { popup.style.display = 'none'; }); } }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dashboard Notification Bubbles</title> <style> :root { --primary: #2A5CAA; --secondary: #FF6B6B; --accent: #44D7B6; --warning: #FFC107; --error: #FF5252; --bg-dark: #1E2239; --bg-light: #F5F8FF; --text-light: #ffffff; --text-dark: #333647; --shadow: 0 4px 20px rgba(0, 0, 0, 0.15); } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { width: 100%; height: 100vh; background: linear-gradient(145deg, var(--bg-dark), #2D325A); display: flex; align-items: center; justify-content: center; overflow: hidden; color: var(--text-light); } .dashboard-container { width: 100%; max-width: 680px; height: 600px; background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(10px); border-radius: 24px; box-shadow: var(--shadow); display: flex; flex-direction: column; overflow: hidden; position: relative; } .dashboard-header { padding: 20px 30px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .dashboard-header h1 { font-size: 1.5rem; font-weight: 600; } .dashboard-user { display: flex; align-items: center; gap: 12px; } .user-avatar { width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(135deg, var(--accent), var(--primary)); display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: 600; } .dashboard-content { display: flex; flex: 1; overflow: hidden; } .sidebar { width: 80px; height: 100%; background: rgba(255, 255, 255, 0.05); display: flex; flex-direction: column; align-items: center; padding: 25px 0; gap: 20px; } .icon-container { position: relative; width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; border-radius: 12px; background: transparent; transition: all 0.3s ease; cursor: pointer; } .icon-container:hover { background: rgba(255, 255, 255, 0.1); } .icon-container.active { background: rgba(255, 255, 255, 0.15); } .sidebar-icon { width: 24px; height: 24px; stroke: var(--text-light); stroke-width: 1.5; fill: none; transition: all 0.3s ease; } .main-content { flex: 1; padding: 30px; overflow-y: auto; } .notification-bubble { position: absolute; top: 2px; right: 2px; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2); opacity: 0; transform: scale(0); cursor: pointer; z-index: 10; } .notification-info { position: absolute; width: 240px; background: rgba(255, 255, 255, 0.95); color: var(--text-dark); border-radius: 12px; box-shadow: var(--shadow); padding: 12px; left: 75px; top: 0; opacity: 0; transform: translateX(-10px); pointer-events: none; transition: all 0.3s ease; z-index: 20; } .notification-info-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .notification-info-title { font-weight: 600; font-size: 14px; } .notification-info-time { font-size: 11px; color: #777; } .notification-info-content { font-size: 12px; line-height: 1.5; margin-bottom: 10px; } .notification-info-actions { display: flex; justify-content: flex-end; gap: 8px; } .notification-info-button { background: var(--primary); color: white; border: none; border-radius: 6px; padding: 5px 10px; font-size: 11px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } .notification-info-button:hover { background: #3A6BC5; } .notification-info-button.secondary { background: transparent; color: var(--text-dark); } .notification-info-button.secondary:hover { background: rgba(0, 0, 0, 0.05); } .data-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 20px; } .data-card { background: rgba(255, 255, 255, 0.07); border-radius: 16px; padding: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; } .data-card:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); } .data-card h3 { font-size: 1rem; font-weight: 500; margin-bottom: 15px; } .data-value { font-size: 1.8rem; font-weight: 600; margin-bottom: 10px; } .data-trend { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; } .trend-up { color: var(--accent); } .trend-down { color: var(--secondary); } .data-chart { height: 60px; margin-top: 15px; display: flex; align-items: flex-end; gap: 2px; } .chart-bar { flex: 1; background: rgba(255, 255, 255, 0.15); border-radius: 3px 3px 0 0; transition: all 0.4s ease; } .welcome-message { background: linear-gradient(135deg, rgba(68, 215, 182, 0.3), rgba(42, 92, 170, 0.3)); border-radius: 16px; padding: 25px; margin-bottom: 25px; display: flex; justify-content: space-between; align-items: center; } .welcome-text h2 { font-size: 1.5rem; margin-bottom: 10px; } .welcome-text p { font-size: 0.95rem; opacity: 0.9; max-width: 400px; } .activity-log { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(30, 34, 57, 0.9); padding: 10px 20px; border-radius: 12px; box-shadow: var(--shadow); display: flex; align-items: center; gap: 10px; opacity: 0; transition: all 0.3s ease; } .activity-log-icon { width: 20px; height: 20px; animation: pulse 2s infinite; } .activity-log-text { font-size: 14px; } @keyframes pulse { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.1); opacity: 0.8; } 100% { transform: scale(1); opacity: 1; } } @keyframes bubbleBounce { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } /* Responsive adjustments */ @media (max-width: 700px) { .dashboard-container { height: 100%; border-radius: 0; max-width: 100%; } .data-grid { grid-template-columns: 1fr; } .welcome-message { flex-direction: column; text-align: center; gap: 20px; } } </style> </head> <body> <div class="dashboard-container"> <div class="dashboard-header"> <h1>DataSense Hub</h1> <div class="dashboard-user"> <span>Alex Chen</span> <div class="user-avatar">AC</div> </div> </div> <div class="dashboard-content"> <div class="sidebar"> <div class="icon-container active" data-tooltip="Dashboard"> <svg class="sidebar-icon" viewBox="0 0 24 24"> <path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"></path> </svg> </div> <div class="icon-container" data-tooltip="Data Sources" id="data-sources"> <svg class="sidebar-icon" viewBox="0 0 24 24"> <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"></path> </svg> <div class="notification-bubble" id="data-sources-bubble" style="background: var(--warning);">3</div> <div class="notification-info" id="data-sources-info"> <div class="notification-info-header"> <div class="notification-info-title">Data Source Updates</div> <div class="notification-info-time">10 min ago</div> </div> <div class="notification-info-content"> 3 of your data connectors need attention: Google Analytics API deprecation, MySQL server idle timeout, and Salesforce token expiration. </div> <div class="notification-info-actions"> <button class="notification-info-button secondary">Dismiss</button> <button class="notification-info-button">Review</button> </div> </div> </div> <div class="icon-container" data-tooltip="Reports" id="reports"> <svg class="sidebar-icon" viewBox="0 0 24 24"> <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"></path> </svg> <div class="notification-bubble" id="reports-bubble" style="background: var(--primary);">2</div> <div class="notification-info" id="reports-info"> <div class="notification-info-header"> <div class="notification-info-title">Report Generation Complete</div> <div class="notification-info-time">Just now</div> </div> <div class="notification-info-content"> Your weekly performance reports and monthly revenue analysis are ready for review. Insights show a 12% improvement in conversion rates. </div> <div class="notification-info-actions"> <button class="notification-info-button secondary">Later</button> <button class="notification-info-button">View Reports</button> </div> </div> </div> <div class="icon-container" data-tooltip="Alerts" id="alerts"> <svg class="sidebar-icon" viewBox="0 0 24 24"> <path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path> </svg> <div class="notification-bubble" id="alerts-bubble" style="background: var(--error);">1</div> <div class="notification-info" id="alerts-info"> <div class="notification-info-header"> <div class="notification-info-title">Critical Performance Alert</div> <div class="notification-info-time">5 min ago</div> </div> <div class="notification-info-content"> Database query times exceeded threshold by 230%. Server CPU utilization at 92%. Automatic scaling initiated to handle increased load. </div> <div class="notification-info-actions"> <button class="notification-info-button secondary">Ignore</button> <button class="notification-info-button">Investigate</button> </div> </div> </div> <div class="icon-container" data-tooltip="Users" id="users"> <svg class="sidebar-icon" viewBox="0 0 24 24"> <path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"></path> </svg> <div class="notification-bubble" id="users-bubble" style="background: var(--accent);">5</div> <div class="notification-info" id="users-info"> <div class="notification-info-header"> <div class="notification-info-title">New Team Members</div> <div class="notification-info-time">1 hour ago</div> </div> <div class="notification-info-content"> 5 new team members have joined your workspace. Pending access requests for Marketing dashboard (3), Sales analytics (1), and Developer metrics (1). </div> <div class="notification-info-actions"> <button class="notification-info-button secondary">Later</button> <button class="notification-info-button">Manage Access</button> </div> </div> </div> <div class="icon-container" data-tooltip="Settings"> <svg class="sidebar-icon" viewBox="0 0 24 24"> <path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"></path> </svg> </div> </div> <div class="main-content"> <div class="welcome-message"> <div class="welcome-text"> <h2>Welcome back, Alex</h2> <p>You have several notifications that require your attention across your data management environment.</p> </div> </div> <h2>Analytics Overview</h2> <div class="data-grid"> <div class="data-card"> <h3>Active Data Sources</h3> <div class="data-value">16</div> <div class="data-trend trend-down"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M7 10l5 5 5-5"></path> </svg> -2 since yesterday </div> <div class="data-chart"> <div class="chart-bar" style="height: 60%;"></div> <div class="chart-bar" style="height: 80%;"></div> <div class="chart-bar" style="height: 65%;"></div> <div class="chart-bar" style="height: 75%;"></div> <div class="chart-bar" style="height: 85%;"></div> <div class="chart-bar" style="height: 75%;"></div> <div class="chart-bar" style="height: 60%;"></div> </div> </div> <div class="data-card"> <h3>Query Performance</h3> <div class="data-value">218ms</div> <div class="data-trend trend-up"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M7 14l5-5 5 5"></path> </svg> 12% improvement </div> <div class="data-chart"> <div class="chart-bar" style="height: 40%;"></div> <div class="chart-bar" style="height: 60%;"></div> <div class="chart-bar" style="height: 45%;"></div> <div class="chart-bar" style="height: 70%;"></div> <div class="chart-bar" style="height: 55%;"></div> <div class="chart-bar" style="height: 65%;"></div> <div class="chart-bar" style="height: 75%;"></div> </div> </div> <div class="data-card"> <h3>Storage Usage</h3> <div class="data-value">64%</div> <div class="data-trend trend-up"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M7 14l5-5 5 5"></path> </svg> +8% this week </div> <div class="data-chart"> <div class="chart-bar" style="height: 40%;"></div> <div class="chart-bar" style="height: 45%;"></div> <div class="chart-bar" style="height: 50%;"></div> <div class="chart-bar" style="height: 55%;"></div> <div class="chart-bar" style="height: 60%;"></div> <div class="chart-bar" style="height: 63%;"></div> <div class="chart-bar" style="height: 64%;"></div> </div> </div> <div class="data-card"> <h3>Active Users</h3> <div class="data-value">237</div> <div class="data-trend trend-up"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M7 14l5-5 5 5"></path> </svg> +22 since yesterday </div> <div class="data-chart"> <div class="chart-bar" style="height: 60%;"></div> <div class="chart-bar" style="height: 55%;"></div> <div class="chart-bar" style="height: 65%;"></div> <div class="chart-bar" style="height: 70%;"></div> <div class="chart-bar" style="height: 72%;"></div> <div class="chart-bar" style="height: 80%;"></div> <div class="chart-bar" style="height: 90%;"></div> </div> </div> </div> </div> </div> </div> <div class="activity-log"> <svg class="activity-log-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="10"></circle> <path d="M12 6v6l4 2"></path> </svg> <span class="activity-log-text">Investigating database performance issue...</span> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Demo variables for animation timings let demoNotificationTimes = [ { id: 'data-sources-bubble', delay: 2000 }, { id: 'reports-bubble', delay: 4000 }, { id: 'alerts-bubble', delay: 1000 }, { id: 'users-bubble', delay: 3000 } ]; // Function to show notification bubble with animation function showNotificationBubble(id, delay) { setTimeout(() => { const bubble = document.getElementById(id); if (bubble) { bubble.style.opacity = '1'; bubble.style.transform = 'scale(1)'; bubble.style.animation = 'bubbleBounce 0.6s ease-in-out'; // Add slight continuous bounce animation after initial appearance setTimeout(() => { bubble.style.animation = 'bubbleBounce 2s ease-in-out infinite'; }, 600); } }, delay); } // Show notification bubbles one by one with delays demoNotificationTimes.forEach(item => { showNotificationBubble(item.id, item.delay); }); // Set up notification info hover/click behaviors const iconContainers = document.querySelectorAll('.icon-container'); iconContainers.forEach(container => { const id = container.id; if (!id) return; const bubble = document.getElementById(`${id}-bubble`); const info = document.getElementById(`${id}-info`); if (bubble && info) { // Show notification info on bubble hover bubble.addEventListener('mouseover', () => { info.style.opacity = '1'; info.style.transform = 'translateX(0)'; info.style.pointerEvents = 'auto'; }); // Show notification info on container hover container.addEventListener('mouseover', () => { if (bubble.style.opacity === '1') { info.style.opacity = '1'; info.style.transform = 'translateX(0)'; info.style.pointerEvents = 'auto'; } }); // Hide notification info when mouse leaves container.addEventListener('mouseleave', () => { info.style.opacity = '0'; info.style.transform = 'translateX(-10px)'; info.style.pointerEvents = 'none'; }); info.addEventListener('mouseleave', () => { info.style.opacity = '0'; info.style.transform = 'translateX(-10px)'; info.style.pointerEvents = 'none'; }); // Handle notification bubble click bubble.addEventListener('click', (e) => { e.stopPropagation(); container.classList.add('active'); // Toggle other containers iconContainers.forEach(ic => { if (ic !== container) { ic.classList.remove('active'); } }); // Remove the notification bubble bubble.style.opacity = '0'; bubble.style.transform = 'scale(0)'; // Show a temporary activity log const activityLog = document.querySelector('.activity-log'); activityLog.style.opacity = '1'; activityLog.style.transform = 'translateX(-50%) translateY(0)'; setTimeout(() => { activityLog.style.opacity = '0'; activityLog.style.transform = 'translateX(-50%) translateY(20px)'; }, 3000); }); // Handle notification info button clicks const buttons = info.querySelectorAll('.notification-info-button'); buttons.forEach(button => { button.addEventListener('click', () => { info.style.opacity = '0'; info.style.transform = 'translateX(-10px)'; bubble.style.opacity = '0'; bubble.style.transform = 'scale(0)'; // Show a temporary activity log const activityLog = document.querySelector('.activity-log'); activityLog.textContent = `Processing ${button.textContent} action...`; activityLog.style.opacity = '1'; activityLog.style.transform = 'translateX(-50%) translateY(0)'; setTimeout(() => { activityLog.style.opacity = '0'; activityLog.style.transform = 'translateX(-50%) translateY(20px)'; }, 3000); }); }); } }); // Animate chart bars const chartBars = document.querySelectorAll('.chart-bar'); chartBars.forEach(bar => { const originalHeight = bar.style.height; bar.style.height = '0'; setTimeout(() => { bar.style.height = originalHeight; }, 300 + Math.random() * 700); }); }); </script> </body> </html>
<html> <head> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; user-select: none; } body { background: #1a0b2e; background: linear-gradient(135deg, #1a0b2e 0%, #4c1d95 100%); height: 100vh; overflow: hidden; display: flex; justify-content: center; align-items: center; perspective: 1000px; } .container { position: relative; width: 100%; max-width: 700px; height: 700px; display: flex; justify-content: center; align-items: center; } .bubbles-container { position: relative; width: 100%; height: 100%; } .bubble { position: absolute; border-radius: 50%; display: flex; justify-content: center; align-items: center; color: white; font-weight: 600; cursor: pointer; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.3s ease; overflow: hidden; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.18); } .bubble::before { content: ''; position: absolute; width: 100%; height: 100%; border-radius: 50%; background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.4), transparent 50%); z-index: -1; } .bubble:hover { transform: scale(1.1); box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3); } .bubble.selected { animation: bounce 0.6s ease-in-out; } .bubble-content { text-align: center; z-index: 2; padding: 10px; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; } .bubble-icon { font-size: 2rem; margin-bottom: 8px; } .bubble-text { font-size: 0.9rem; font-weight: 500; opacity: 0.9; } .game-preview { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 300px; height: 200px; background: rgba(255, 255, 255, 0.1); border-radius: 15px; opacity: 0; transition: opacity 0.3s ease; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.18); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); display: flex; flex-direction: column; overflow: hidden; z-index: 20; } .game-preview.active { opacity: 1; } .preview-header { background: rgba(255, 255, 255, 0.1); padding: 12px; text-align: center; color: white; font-weight: 600; } .preview-content { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 15px; color: white; } .preview-description { text-align: center; font-size: 0.9rem; margin-bottom: 20px; } .preview-action { padding: 8px 20px; background: rgba(255, 255, 255, 0.2); border: none; border-radius: 20px; color: white; font-weight: 600; cursor: pointer; transition: all 0.3s ease; } .preview-action:hover { background: rgba(255, 255, 255, 0.3); transform: scale(1.05); } .close-preview { position: absolute; top: 10px; right: 10px; background: none; border: none; color: white; font-size: 1.2rem; cursor: pointer; z-index: 21; } @keyframes bounce { 0%, 100% { transform: scale(1); } 40% { transform: scale(1.2); } 60% { transform: scale(0.9); } 80% { transform: scale(1.1); } } .floating { animation-name: floating; animation-duration: 5s; animation-iteration-count: infinite; animation-timing-function: ease-in-out; } @keyframes floating { 0% { transform: translate(0, 0px); } 50% { transform: translate(0, 15px); } 100% { transform: translate(0, 0px); } } .particle { position: absolute; width: 8px; height: 8px; border-radius: 50%; pointer-events: none; opacity: 0.8; } .logo { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); font-size: 2rem; font-weight: 700; color: white; text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); letter-spacing: 2px; } .pulse { animation: pulse 2s infinite; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); } } </style> </head> <body> <div class="container"> <div class="logo pulse">NEXUS GAMES</div> <div class="bubbles-container" id="bubblesContainer"></div> <div class="game-preview" id="gamePreview"> <button class="close-preview" id="closePreview">ร</button> <div class="preview-header" id="previewHeader">Adventure Quest</div> <div class="preview-content"> <p class="preview-description" id="previewDescription">Embark on an epic journey through mystical lands and battle fierce creatures.</p> <button class="preview-action" id="previewAction">PLAY NOW</button> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { const bubblesContainer = document.getElementById('bubblesContainer'); const gamePreview = document.getElementById('gamePreview'); const previewHeader = document.getElementById('previewHeader'); const previewDescription = document.getElementById('previewDescription'); const closePreview = document.getElementById('closePreview'); // Menu items data const menuItems = [ { id: 'adventure', name: 'Adventure', icon: '๐๏ธ', color: '#FF6B6B', description: 'Explore vast worlds and complete epic quests in our collection of adventure games!' }, { id: 'action', name: 'Action', icon: 'โ๏ธ', color: '#4ECDC4', description: 'Test your reflexes in fast-paced combat and intense challenges!' }, { id: 'puzzle', name: 'Puzzle', icon: '๐งฉ', color: '#FFD166', description: 'Exercise your brain with ingenious puzzles that will challenge your logic!' }, { id: 'strategy', name: 'Strategy', icon: '๐ง ', color: '#6A0572', description: 'Plan, build, and conquer in games that reward clever thinking!' }, { id: 'racing', name: 'Racing', icon: '๐๏ธ', color: '#F25F5C', description: 'Speed through tracks and outrace opponents in high-octane competitions!' }, { id: 'rpg', name: 'RPG', icon: '๐', color: '#247BA0', description: 'Develop your character and embark on immersive story-driven adventures!' }, { id: 'arcade', name: 'Arcade', icon: '๐น๏ธ', color: '#70C1B3', description: 'Experience the nostalgic fun of classic arcade-style games!' }, { id: 'sports', name: 'Sports', icon: 'โฝ', color: '#50514F', description: 'Compete in realistic sports simulations and become a virtual champion!' } ]; // Create bubbles menuItems.forEach((item, index) => { const bubble = document.createElement('div'); bubble.className = 'bubble floating'; bubble.id = item.id; bubble.style.backgroundColor = item.color; // Calculate position const angle = (2 * Math.PI * index) / menuItems.length; const radius = window.innerWidth < 600 ? 150 : 220; const x = Math.cos(angle) * radius + bubblesContainer.clientWidth / 2 - 50; const y = Math.sin(angle) * radius + bubblesContainer.clientHeight / 2 - 50; bubble.style.left = `${x}px`; bubble.style.top = `${y}px`; bubble.style.width = '100px'; bubble.style.height = '100px'; bubble.style.animationDelay = `${index * 0.2}s`; bubble.innerHTML = ` <div class="bubble-content"> <div class="bubble-icon">${item.icon}</div> <div class="bubble-text">${item.name}</div> </div> `; bubble.addEventListener('click', () => { // Remove selected class from all bubbles document.querySelectorAll('.bubble').forEach(b => b.classList.remove('selected')); // Add selected class to clicked bubble bubble.classList.add('selected'); // Create particles createParticles(x + 50, y + 50, item.color); // Update preview previewHeader.textContent = item.name + ' Games'; previewDescription.textContent = item.description; gamePreview.classList.add('active'); }); bubblesContainer.appendChild(bubble); }); closePreview.addEventListener('click', () => { gamePreview.classList.remove('active'); document.querySelectorAll('.bubble').forEach(b => b.classList.remove('selected')); }); // Create particles effect function createParticles(x, y, color) { for (let i = 0; i < 15; i++) { const particle = document.createElement('div'); particle.className = 'particle'; particle.style.backgroundColor = color; // Position the particle at the center of the clicked bubble particle.style.left = `${x}px`; particle.style.top = `${y}px`; // Random direction and speed const angle = Math.random() * Math.PI * 2; const speed = 2 + Math.random() * 3; const dx = Math.cos(angle) * speed; const dy = Math.sin(angle) * speed; // Add to container bubblesContainer.appendChild(particle); // Animate the particle let opacity = 0.8; let posX = x; let posY = y; const animate = () => { opacity -= 0.02; posX += dx; posY += dy; if (opacity <= 0) { particle.remove(); return; } particle.style.opacity = opacity; particle.style.left = `${posX}px`; particle.style.top = `${posY}px`; requestAnimationFrame(animate); }; requestAnimationFrame(animate); } } // Make it responsive window.addEventListener('resize', () => { menuItems.forEach((item, index) => { const bubble = document.getElementById(item.id); const angle = (2 * Math.PI * index) / menuItems.length; const radius = window.innerWidth < 600 ? 150 : 220; const x = Math.cos(angle) * radius + bubblesContainer.clientWidth / 2 - 50; const y = Math.sin(angle) * radius + bubblesContainer.clientHeight / 2 - 50; bubble.style.left = `${x}px`; bubble.style.top = `${y}px`; }); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Analytics Bubble Chart</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { background-color: #f9fafc; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; } .container { width: 100%; max-width: 700px; height: 700px; position: relative; background-color: #ffffff; border-radius: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05); padding: 30px; overflow: hidden; } .header { text-align: center; margin-bottom: 20px; } h1 { font-size: 28px; font-weight: 700; color: #1a1a2e; margin-bottom: 10px; } .subtitle { font-size: 14px; color: #64748b; max-width: 500px; margin: 0 auto; line-height: 1.5; } .chart-container { position: relative; width: 100%; height: calc(100% - 100px); overflow: hidden; } .bubble { position: absolute; border-radius: 50%; cursor: pointer; transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease; display: flex; justify-content: center; align-items: center; color: white; font-weight: 600; box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); will-change: transform; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); } .bubble:hover { transform: scale(1.05); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); z-index: 100; } .bubble-content { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 15px; opacity: 0.9; } .bubble-title { font-size: 16px; font-weight: 700; margin-bottom: 5px; } .bubble-value { font-size: 22px; font-weight: 800; } .tooltip { position: absolute; background: rgba(255, 255, 255, 0.95); border-radius: 10px; padding: 15px; pointer-events: none; opacity: 0; transform: translateY(10px); transition: opacity 0.3s, transform 0.3s; z-index: 1000; width: 220px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.05); } .tooltip.active { opacity: 1; transform: translateY(0); } .tooltip-title { font-weight: 700; font-size: 16px; color: #1a1a2e; margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } .tooltip-content { font-size: 14px; color: #4b5563; line-height: 1.5; } .tooltip-stat { display: flex; justify-content: space-between; margin-top: 5px; font-size: 13px; } .tooltip-stat strong { color: #1a1a2e; } .time-period { display: flex; justify-content: center; margin-top: 20px; gap: 10px; } .time-btn { background: none; border: none; padding: 5px 12px; font-size: 13px; color: #64748b; cursor: pointer; border-radius: 20px; transition: all 0.2s; } .time-btn.active { background-color: #f0f4ff; color: #3b82f6; font-weight: 600; } .time-btn:hover:not(.active) { background-color: #f1f5f9; } .loading-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: white; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 1000; transition: opacity 0.5s ease-out; } .loading-spinner { width: 40px; height: 40px; border: 3px solid #f3f3f3; border-top: 3px solid #3b82f6; border-radius: 50%; animation: spin 1.5s linear infinite; } .loading-text { margin-top: 15px; font-size: 16px; color: #64748b; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .legend { display: flex; justify-content: center; gap: 15px; margin-top: 15px; } .legend-item { display: flex; align-items: center; font-size: 12px; color: #64748b; } .legend-color { width: 12px; height: 12px; border-radius: 50%; margin-right: 5px; } .annotations { position: absolute; bottom: 10px; left: 10px; font-size: 11px; color: #a1a1aa; } .refresh-btn { position: absolute; top: 30px; right: 30px; background: none; border: none; width: 30px; height: 30px; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background-color 0.2s; } .refresh-btn:hover { background-color: #f1f5f9; } .refresh-icon { width: 18px; height: 18px; color: #64748b; } @keyframes bubble-enter { 0% { opacity: 0; transform: scale(0.5); } 50% { opacity: 1; } 100% { transform: scale(1); } } @media (max-width: 600px) { .container { padding: 20px; height: 100%; border-radius: 0; } h1 { font-size: 24px; } .bubble-title { font-size: 13px; } .bubble-value { font-size: 18px; } .tooltip { width: 180px; padding: 10px; } } </style> </head> <body> <div class="container"> <div class="loading-screen"> <div class="loading-spinner"></div> <div class="loading-text">Analyzing metrics data...</div> </div> <button class="refresh-btn" title="Refresh data"> <svg class="refresh-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> </svg> </button> <div class="header"> <h1>Product Performance Metrics</h1> <div class="subtitle">Interactive visualization of key metrics across product lines. Bubble size represents relative impact on quarterly goals.</div> </div> <div class="time-period"> <button class="time-btn" data-period="weekly">Weekly</button> <button class="time-btn active" data-period="monthly">Monthly</button> <button class="time-btn" data-period="quarterly">Quarterly</button> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color" style="background: linear-gradient(to right, #4facfe, #00f2fe);"></div> <span>Engagement</span> </div> <div class="legend-item"> <div class="legend-color" style="background: linear-gradient(to right, #f093fb, #f5576c);"></div> <span>Revenue</span> </div> <div class="legend-item"> <div class="legend-color" style="background: linear-gradient(to right, #43e97b, #38f9d7);"></div> <span>Growth</span> </div> </div> <div class="chart-container" id="chart"> <!-- Bubbles will be dynamically added here --> </div> <div class="tooltip" id="tooltip"> <div class="tooltip-title">Metric Title</div> <div class="tooltip-content"> Detailed information about this metric will appear here. <div class="tooltip-stat"> <span>Previous period:</span> <strong>0</strong> </div> <div class="tooltip-stat"> <span>YoY change:</span> <strong>0%</strong> </div> </div> </div> <div class="annotations">Data last updated: Today at 09:45 AM</div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Bubble data for different time periods const data = { weekly: [ { id: 1, title: "Active Users", value: "8.4K", size: 120, category: "engagement", details: "Weekly active users across all platforms. Mobile usage increased by 15% from last week.", prevPeriod: "7.8K", yoyChange: "+24%", x: 0.3, y: 0.45 }, { id: 2, title: "Conversion Rate", value: "3.2%", size: 90, category: "revenue", details: "Weekly conversion rate from free to premium subscriptions. Landing page optimizations implemented last week.", prevPeriod: "2.9%", yoyChange: "+18%", x: 0.6, y: 0.3 }, { id: 3, title: "Session Time", value: "6.7m", size: 100, category: "engagement", details: "Average session duration per user. New content features driving longer engagement.", prevPeriod: "6.2m", yoyChange: "+8%", x: 0.8, y: 0.7 }, { id: 4, title: "New Sign-ups", value: "982", size: 80, category: "growth", details: "New user registrations this week. Social media campaign delivering strong results.", prevPeriod: "841", yoyChange: "+17%", x: 0.2, y: 0.7 }, { id: 5, title: "Revenue", value: "$24K", size: 110, category: "revenue", details: "Weekly revenue from all sources. Subscription tier changes impacting positively.", prevPeriod: "$21K", yoyChange: "+14%", x: 0.5, y: 0.6 } ], monthly: [ { id: 1, title: "Active Users", value: "42.6K", size: 140, category: "engagement", details: "Monthly active users across all platforms. New feature adoption driving engagement metrics up.", prevPeriod: "38.2K", yoyChange: "+32%", x: 0.3, y: 0.5 }, { id: 2, title: "Conversion Rate", value: "4.7%", size: 110, category: "revenue", details: "Monthly conversion rate showing strong improvement. A/B testing on pricing page delivering results.", prevPeriod: "3.8%", yoyChange: "+23%", x: 0.65, y: 0.35 }, { id: 3, title: "Retention Rate", value: "87%", size: 130, category: "engagement", details: "User retention at 30 days. Significant improvement from Q1 after onboarding improvements.", prevPeriod: "82%", yoyChange: "+9%", x: 0.7, y: 0.7 }, { id: 4, title: "New Sign-ups", value: "5.8K", size: 100, category: "growth", details: "Monthly user acquisition. Partner marketing channels driving 42% of new sign-ups.", prevPeriod: "4.9K", yoyChange: "+18%", x: 0.2, y: 0.3 }, { id: 5, title: "Revenue", value: "$148K", size: 150, category: "revenue", details: "Monthly revenue from all product lines. Enterprise tier seeing fastest growth.", prevPeriod: "$124K", yoyChange: "+19%", x: 0.45, y: 0.65 }, { id: 6, title: "Avg Order Value", value: "$68", size: 90, category: "revenue", details: "Average transaction value. Add-on features adoption increasing baseline value.", prevPeriod: "$59", yoyChange: "+15%", x: 0.55, y: 0.15 }, { id: 7, title: "Feature Usage", value: "76%", size: 95, category: "engagement", details: "Core feature adoption rate. Improved UI/UX showing measurable impact.", prevPeriod: "69%", yoyChange: "+10%", x: 0.15, y: 0.6 } ], quarterly: [ { id: 1, title: "Active Users", value: "127K", size: 160, category: "engagement", details: "Quarterly active users reached record levels. International markets showing strongest growth at 42%.", prevPeriod: "96K", yoyChange: "+32%", x: 0.3, y: 0.45 }, { id: 2, title: "Conversion Rate", value: "5.3%", size: 120, category: "revenue", details: "Quarterly conversion rate exceeding targets. Premium content strategy delivering measurable impact.", prevPeriod: "4.1%", yoyChange: "+29%", x: 0.6, y: 0.2 }, { id: 3, title: "Retention Rate", value: "91%", size: 140, category: "engagement", details: "Quarterly user retention at 90 days. Personalization features contributing to loyalty metrics.", prevPeriod: "85%", yoyChange: "+7%", x: 0.75, y: 0.65 }, { id: 4, title: "New Sign-ups", value: "16.4K", size: 130, category: "growth", details: "Quarterly user acquisition. Referral program generating 28% of new users with 2.3x higher retention.", prevPeriod: "12.3K", yoyChange: "+33%", x: 0.15, y: 0.25 }, { id: 5, title: "Revenue", value: "$467K", size: 170, category: "revenue", details: "Quarterly revenue showing strong recurring component at 78%. Annual subscriptions up 42%.", prevPeriod: "$384K", yoyChange: "+22%", x: 0.5, y: 0.6 }, { id: 6, title: "Customer LTV", value: "$840", size: 125, category: "revenue", details: "Customer lifetime value showing positive trend. Expanded product offerings increasing monetization opportunities.", prevPeriod: "$740", yoyChange: "+14%", x: 0.4, y: 0.3 }, { id: 7, title: "Platform Growth", value: "36%", size: 110, category: "growth", details: "Quarterly growth in platform ecosystem. API partners increased by 14 this quarter.", prevPeriod: "29%", yoyChange: "+24%", x: 0.2, y: 0.7 }, { id: 8, title: "Mobile Usage", value: "64%", size: 115, category: "engagement", details: "Percentage of usage on mobile platforms. Android showing fastest growth at 47% YoY.", prevPeriod: "58%", yoyChange: "+10%", x: 0.85, y: 0.4 } ] }; const chartContainer = document.getElementById('chart'); const tooltip = document.getElementById('tooltip'); const timeButtons = document.querySelectorAll('.time-btn'); const loadingScreen = document.querySelector('.loading-screen'); const refreshBtn = document.querySelector('.refresh-btn'); let activePeriod = 'monthly'; let currentData = []; // Get gradient for different categories function getGradient(category) { switch(category) { case 'engagement': return 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)'; case 'revenue': return 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)'; case 'growth': return 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)'; default: return 'linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%)'; } } // Create bubble elements function createBubbles(data) { chartContainer.innerHTML = ''; const containerRect = chartContainer.getBoundingClientRect(); const containerWidth = containerRect.width; const containerHeight = containerRect.height; data.forEach((item, index) => { const bubble = document.createElement('div'); bubble.className = 'bubble'; bubble.id = `bubble-${item.id}`; bubble.style.width = `${item.size}px`; bubble.style.height = `${item.size}px`; // Position bubble (with some margins) const leftPos = item.x * (containerWidth - item.size); const topPos = item.y * (containerHeight - item.size); bubble.style.left = `${leftPos}px`; bubble.style.top = `${topPos}px`; bubble.style.background = getGradient(item.category); // Animation delay based on index bubble.style.animation = `bubble-enter 0.6s ease-out ${index * 0.1}s backwards`; const content = document.createElement('div'); content.className = 'bubble-content'; const title = document.createElement('div'); title.className = 'bubble-title'; title.textContent = item.title; const value = document.createElement('div'); value.className = 'bubble-value'; value.textContent = item.value; content.appendChild(title); content.appendChild(value); bubble.appendChild(content); // Hover events for tooltip bubble.addEventListener('mouseenter', function(e) { showTooltip(e, item); }); bubble.addEventListener('mousemove', function(e) { updateTooltipPosition(e); }); bubble.addEventListener('mouseleave', function() { hideTooltip(); }); chartContainer.appendChild(bubble); }); } // Show tooltip with data function showTooltip(e, item) { const tooltipTitle = tooltip.querySelector('.tooltip-title'); const tooltipContent = tooltip.querySelector('.tooltip-content'); tooltipTitle.textContent = item.title; tooltipContent.innerHTML = ` ${item.details} <div class="tooltip-stat"> <span>Previous period:</span> <strong>${item.prevPeriod}</strong> </div> <div class="tooltip-stat"> <span>YoY change:</span> <strong>${item.yoyChange}</strong> </div> `; tooltip.classList.add('active'); updateTooltipPosition(e); } // Update tooltip position function updateTooltipPosition(e) { const containerRect = chartContainer.getBoundingClientRect(); const tooltipWidth = tooltip.offsetWidth; const tooltipHeight = tooltip.offsetHeight; // Calculate position relative to chart container let left = e.clientX - containerRect.left + 15; let top = e.clientY - containerRect.top + 15; // Adjust if tooltip would go off the right edge if (left + tooltipWidth > containerRect.width) { left = e.clientX - containerRect.left - tooltipWidth - 15; } // Adjust if tooltip would go off the bottom edge if (top + tooltipHeight > containerRect.height) { top = e.clientY - containerRect.top - tooltipHeight - 15; } tooltip.style.left = `${left}px`; tooltip.style.top = `${top}px`; } // Hide tooltip function hideTooltip() { tooltip.classList.remove('active'); } // Switch between time periods function switchTimePeriod(period) { activePeriod = period; // Update active button timeButtons.forEach(btn => { if (btn.dataset.period === period) { btn.classList.add('active'); } else { btn.classList.remove('active'); } }); // Show loading screen for a smoother transition loadingScreen.style.opacity = 1; loadingScreen.style.display = 'flex'; setTimeout(() => { currentData = data[period]; createBubbles(currentData); // Hide loading screen with a fade setTimeout(() => { loadingScreen.style.opacity = 0; setTimeout(() => { loadingScreen.style.display = 'none'; }, 500); }, 800); }, 600); } // Set up time period buttons timeButtons.forEach(btn => { btn.addEventListener('click', function() { const period = this.dataset.period; switchTimePeriod(period); }); }); // Refresh button refreshBtn.addEventListener('click', function() { switchTimePeriod(activePeriod); }); // Handle window resize window.addEventListener('resize', function() { createBubbles(currentData); }); // Initialize with monthly data setTimeout(() => { currentData = data[activePeriod]; createBubbles(currentData); // Hide loading screen with a fade loadingScreen.style.opacity = 0; setTimeout(() => { loadingScreen.style.display = 'none'; }, 500); }, 1500); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> :root { --success: #16a34a; --success-light: rgba(22, 163, 74, 0.1); --warning: #f59e0b; --warning-light: rgba(245, 158, 11, 0.1); --danger: #dc2626; --danger-light: rgba(220, 38, 38, 0.1); --info: #3b82f6; --info-light: rgba(59, 130, 246, 0.1); --neutral: #6b7280; --neutral-light: rgba(107, 114, 128, 0.1); --bg: #f9fafb; --text: #1f2937; --border: #e5e7eb; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { background-color: var(--bg); color: var(--text); display: flex; flex-direction: column; align-items: center; justify-content: flex-start; min-height: 100vh; padding: 1.5rem; } .app-container { max-width: 700px; width: 100%; background-color: white; border-radius: 16px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); overflow: hidden; } .app-header { background-color: #0f172a; color: white; padding: 1.5rem; position: relative; } .app-header h1 { font-size: 1.2rem; font-weight: 600; margin-bottom: 0.5rem; } .app-header p { font-size: 0.85rem; opacity: 0.8; } .account-balance { margin-top: 1rem; font-size: 1.8rem; font-weight: 700; } .account-number { font-size: 0.8rem; opacity: 0.7; margin-top: 0.5rem; } .transactions-section { padding: 1.5rem; } .section-title { font-size: 0.9rem; font-weight: 600; color: var(--neutral); margin-bottom: 1rem; display: flex; align-items: center; justify-content: space-between; } .toggle-controls { display: flex; gap: 0.5rem; } .toggle-btn { background: none; border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px; font-size: 0.7rem; cursor: pointer; transition: all 0.2s ease; color: var(--neutral); } .toggle-btn.active { background-color: var(--text); color: white; border-color: var(--text); } .toggle-btn:hover:not(.active) { background-color: var(--neutral-light); } .transaction-list { display: flex; flex-direction: column; gap: 0.75rem; } .transaction { background-color: white; border: 1px solid var(--border); border-radius: 12px; padding: 1rem; display: flex; align-items: center; gap: 1rem; position: relative; transition: transform 0.2s ease, box-shadow 0.2s ease; } .transaction:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); } .transaction-icon { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .transaction-icon svg { width: 20px; height: 20px; } .transaction-details { flex-grow: 1; } .transaction-title { font-weight: 600; font-size: 0.9rem; margin-bottom: 0.25rem; } .transaction-timestamp { font-size: 0.7rem; color: var(--neutral); } .transaction-amount { font-weight: 600; font-size: 0.95rem; } .status-bubble { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 0.6rem; font-weight: 600; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); z-index: 2; } .status-bubble-success { background-color: var(--success); animation: pulse-success 2s infinite; } .status-bubble-warning { background-color: var(--warning); animation: pulse-warning 2s infinite; } .status-bubble-danger { background-color: var(--danger); animation: pulse-danger 2s infinite; } .status-bubble-info { background-color: var(--info); animation: pulse-info 2s infinite; } @keyframes pulse-success { 0% { box-shadow: 0 0 0 0 rgba(22, 163, 74, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(22, 163, 74, 0); } 100% { box-shadow: 0 0 0 0 rgba(22, 163, 74, 0); } } @keyframes pulse-warning { 0% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); } 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); } } @keyframes pulse-danger { 0% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(220, 38, 38, 0); } 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); } } @keyframes pulse-info { 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } } .status-tooltip { position: absolute; top: calc(100% + 10px); right: 0; background-color: white; border-radius: 8px; padding: 0.75rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); width: 200px; z-index: 10; font-size: 0.8rem; opacity: 0; visibility: hidden; transition: opacity 0.2s ease, visibility 0.2s ease; pointer-events: none; } .status-tooltip::before { content: ''; position: absolute; top: -6px; right: 10px; width: 12px; height: 12px; background-color: white; transform: rotate(45deg); } .status-bubble:hover + .status-tooltip { opacity: 1; visibility: visible; } .tooltip-title { font-weight: 600; margin-bottom: 0.25rem; display: flex; align-items: center; gap: 0.25rem; } .tooltip-title.success { color: var(--success); } .tooltip-title.warning { color: var(--warning); } .tooltip-title.danger { color: var(--danger); } .tooltip-title.info { color: var(--info); } .tooltip-desc { color: var(--neutral); font-size: 0.75rem; } .action-pills { display: flex; gap: 0.5rem; margin-top: 0.5rem; } .action-pill { background-color: var(--neutral-light); color: var(--text); border: none; border-radius: 20px; padding: 4px 8px; font-size: 0.7rem; cursor: pointer; transition: all 0.2s ease; } .action-pill:hover { background-color: var(--text); color: white; } .empty-state { text-align: center; padding: 2rem; color: var(--neutral); } .empty-icon { margin-bottom: 1rem; opacity: 0.5; } .empty-text { font-size: 0.9rem; margin-bottom: 1rem; } .notification-manager { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 100; display: flex; flex-direction: column; gap: 0.5rem; width: 90%; max-width: 400px; } .notification { padding: 1rem; border-radius: 12px; display: flex; align-items: center; gap: 0.75rem; animation: slideUp 0.3s ease forwards, fadeOut 0.3s ease 3.7s forwards; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); background-color: white; } .notification-icon { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .notification-icon.success { background-color: var(--success-light); color: var(--success); } .notification-icon.warning { background-color: var(--warning-light); color: var(--warning); } .notification-icon.danger { background-color: var(--danger-light); color: var(--danger); } .notification-content { flex-grow: 1; } .notification-title { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.25rem; } .notification-desc { font-size: 0.75rem; color: var(--neutral); } @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .status-indicator { position: absolute; top: -6px; right: -6px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } /* Media queries */ @media (max-width: 500px) { .transaction { padding: 0.75rem; } .transaction-icon { width: 32px; height: 32px; } .transaction-title { font-size: 0.8rem; } .transaction-amount { font-size: 0.85rem; } } </style> </head> <body> <div class="app-container"> <div class="app-header"> <h1>Metro Financial</h1> <p>Your trusted banking partner</p> <div class="account-balance">$8,243.67</div> <div class="account-number">โขโขโขโข7845</div> </div> <div class="transactions-section"> <div class="section-title"> Recent Transactions <div class="toggle-controls"> <button class="toggle-btn active" data-filter="all">All</button> <button class="toggle-btn" data-filter="success">Completed</button> <button class="toggle-btn" data-filter="pending">Pending</button> <button class="toggle-btn" data-filter="failed">Failed</button> </div> </div> <div class="transaction-list"> <div class="transaction" data-status="success"> <div class="transaction-icon" style="background-color: var(--success-light); color: var(--success);"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z" /> </svg> </div> <div class="transaction-details"> <div class="transaction-title">Direct Deposit - Payroll</div> <div class="transaction-timestamp">Today, 9:45 AM</div> </div> <div class="transaction-amount" style="color: var(--success);">+$2,456.78</div> <div class="status-bubble status-bubble-success">โ</div> <div class="status-tooltip"> <div class="tooltip-title success"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="14" height="14"> <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> Transaction Complete </div> <div class="tooltip-desc">Funds have been deposited to your account and are available for immediate use.</div> <div class="action-pills"> <button class="action-pill" onclick="showNotification('success', 'Receipt Sent', 'Transaction receipt has been emailed to you.')">Get Receipt</button> </div> </div> </div> <div class="transaction" data-status="pending"> <div class="transaction-icon" style="background-color: var(--warning-light); color: var(--warning);"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007zM8.625 10.5a.375.375 0 11-.75 0 .375.375 0 01.75 0zm7.5 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" /> </svg> </div> <div class="transaction-details"> <div class="transaction-title">Amazon.com Purchase</div> <div class="transaction-timestamp">Yesterday, 5:32 PM</div> </div> <div class="transaction-amount">-$127.49</div> <div class="status-bubble status-bubble-warning">!</div> <div class="status-tooltip"> <div class="tooltip-title warning"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="14" height="14"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /> </svg> Authorization Pending </div> <div class="tooltip-desc">This transaction is being processed and funds have been reserved. Final settlement pending.</div> <div class="action-pills"> <button class="action-pill" onclick="showNotification('warning', 'Transaction Details', 'Merchant details have been emailed to you.')">View Details</button> </div> </div> </div> <div class="transaction" data-status="failed"> <div class="transaction-icon" style="background-color: var(--danger-light); color: var(--danger);"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" /> </svg> </div> <div class="transaction-details"> <div class="transaction-title">Wire Transfer to J. Smith</div> <div class="transaction-timestamp">Sep 15, 10:23 AM</div> </div> <div class="transaction-amount" style="color: var(--danger);">-$1,200.00</div> <div class="status-bubble status-bubble-danger">ร</div> <div class="status-tooltip"> <div class="tooltip-title danger"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="14" height="14"> <path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> Transaction Failed </div> <div class="tooltip-desc">This transfer could not be completed due to incorrect account information. No funds were deducted.</div> <div class="action-pills"> <button class="action-pill" onclick="showNotification('danger', 'Error Details', 'Error report has been sent to your email.')">View Error</button> <button class="action-pill" onclick="retryTransaction()">Retry</button> </div> </div> </div> <div class="transaction" data-status="info"> <div class="transaction-icon" style="background-color: var(--info-light); color: var(--info);"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v10.5a2.25 2.25 0 002.25 2.25zm.75-12h9v9h-9v-9z" /> </svg> </div> <div class="transaction-details"> <div class="transaction-title">Recurring Subscription - Netflix</div> <div class="transaction-timestamp">Sep 14, 2:05 PM</div> </div> <div class="transaction-amount">-$14.99</div> <div class="status-bubble status-bubble-info">i</div> <div class="status-tooltip"> <div class="tooltip-title info"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="14" height="14"> <path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" /> </svg> Recurring Payment </div> <div class="tooltip-desc">This is an automatic monthly payment. Next charge scheduled for Oct 14.</div> <div class="action-pills"> <button class="action-pill" onclick="showNotification('info', 'Subscription Info', 'Subscription details sent to your email.')">Details</button> <button class="action-pill" onclick="showNotification('warning', 'Manage Subscription', 'Please use the merchant website to manage this subscription.')">Manage</button> </div> </div> </div> </div> </div> </div> <div class="notification-manager" id="notification-manager"></div> <script> // Toggle functionality for transaction filters const toggleBtns = document.querySelectorAll('.toggle-btn'); const transactions = document.querySelectorAll('.transaction'); toggleBtns.forEach(btn => { btn.addEventListener('click', () => { // Remove active class from all buttons toggleBtns.forEach(b => b.classList.remove('active')); // Add active class to clicked button btn.classList.add('active'); const filter = btn.dataset.filter; // Show/hide transactions based on filter transactions.forEach(transaction => { if (filter === 'all') { transaction.style.display = 'flex'; } else if (filter === 'success' && transaction.dataset.status === 'success') { transaction.style.display = 'flex'; } else if (filter === 'pending' && transaction.dataset.status === 'pending') { transaction.style.display = 'flex'; } else if (filter === 'failed' && transaction.dataset.status === 'failed') { transaction.style.display = 'flex'; } else { transaction.style.display = 'none'; } }); // Check if any transactions are visible const visibleTransactions = Array.from(transactions).filter(t => t.style.display !== 'none'); // If no transactions are visible, show empty state const transactionList = document.querySelector('.transaction-list'); if (visibleTransactions.length === 0 && !document.querySelector('.empty-state')) { const emptyState = document.createElement('div'); emptyState.className = 'empty-state'; emptyState.innerHTML = ` <div class="empty-icon"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="48" height="48"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m6.75 12l-3-3m0 0l-3 3m3-3v6m-1.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /> </svg> </div> <div class="empty-text">No ${filter === 'all' ? '' : filter} transactions found</div> `; transactionList.appendChild(emptyState); } else if (visibleTransactions.length > 0) { const emptyState = document.querySelector('.empty-state'); if (emptyState) { emptyState.remove(); } } }); }); // Function to show notifications function showNotification(type, title, message) { const notificationManager = document.getElementById('notification-manager'); const notification = document.createElement('div'); notification.className = 'notification'; let iconSvg = ''; switch (type) { case 'success': iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>'; break; case 'warning': iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /></svg>'; break; case 'danger': iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>'; break; case 'info': iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="16" height="16"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" /></svg>'; break; } notification.innerHTML = ` <div class="notification-icon ${type}"> ${iconSvg} </div> <div class="notification-content"> <div class="notification-title">${title}</div> <div class="notification-desc">${message}</div> </div> `; notificationManager.appendChild(notification); // Remove notification after animation completes setTimeout(() => { notification.remove(); }, 4000); } // Function to retry a transaction function retryTransaction() { showNotification('info', 'Retrying Transaction', 'Your transaction is being reprocessed. Please wait.'); // Simulate processing setTimeout(() => { showNotification('success', 'Transaction Successful', 'Your transfer has been successfully processed.'); }, 2000); } // Add hover effect to transactions transactions.forEach(transaction => { transaction.addEventListener('mouseenter', () => { const tooltip = transaction.querySelector('.status-tooltip'); tooltip.style.opacity = '1'; tooltip.style.visibility = 'visible'; }); transaction.addEventListener('mouseleave', () => { const tooltip = transaction.querySelector('.status-tooltip'); tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; }); }); // Show an initial notification when the page loads setTimeout(() => { showNotification('info', 'Welcome to Metro Financial', 'Your account has been updated with recent transactions.'); }, 1000); </script> </body> </html>
<html> <head> <style> :root { --primary: #5C67DE; --secondary: #FF6B6B; --correct: #4CAF50; --incorrect: #FF5252; --neutral: #ECEFF1; --dark: #37474F; --light: #FFFFFF; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { display: flex; flex-direction: column; justify-content: center; align-items: center; min-height: 100vh; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); padding: 20px; overflow: hidden; } .container { width: 100%; max-width: 650px; background-color: var(--light); border-radius: 24px; box-shadow: 0 20px 40px rgba(92, 103, 222, 0.1); padding: 30px; position: relative; overflow: hidden; z-index: 1; } .progress-container { height: 8px; width: 100%; background-color: var(--neutral); border-radius: 10px; margin-bottom: 20px; overflow: hidden; } .progress-bar { height: 100%; width: 0; background: linear-gradient(90deg, var(--primary), #7E84F7); border-radius: 10px; transition: width 0.5s ease; } h1 { color: var(--dark); margin-bottom: 10px; font-size: 26px; position: relative; } h1::after { content: ''; position: absolute; bottom: -8px; left: 0; width: 60px; height: 4px; background-color: var(--primary); border-radius: 2px; } .question { font-size: 18px; color: var(--dark); margin: 25px 0 30px; line-height: 1.5; } .bubbles-container { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 25px; } .bubble { position: relative; padding: 16px; background-color: var(--neutral); border-radius: 18px; display: flex; align-items: center; cursor: pointer; transition: all 0.3s ease; overflow: hidden; border: 2px solid transparent; } .bubble:hover { transform: translateY(-3px); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05); border-color: var(--primary); } .bubble-letter { display: flex; justify-content: center; align-items: center; width: 40px; height: 40px; border-radius: 50%; background-color: var(--primary); color: white; font-weight: bold; margin-right: 12px; flex-shrink: 0; transition: all 0.3s ease; } .bubble-text { flex-grow: 1; font-size: 16px; color: var(--dark); transition: all 0.3s ease; } .bubble.correct { background-color: rgba(76, 175, 80, 0.1); border-color: var(--correct); } .bubble.correct .bubble-letter { background-color: var(--correct); } .bubble.incorrect { background-color: rgba(255, 82, 82, 0.1); border-color: var(--incorrect); } .bubble.incorrect .bubble-letter { background-color: var(--incorrect); } .bubble::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background-color: rgba(255, 255, 255, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: all 0.5s ease; } .bubble:active::before { width: 300px; height: 300px; opacity: 0; } .feedback { margin-top: 20px; padding: 15px; border-radius: 15px; text-align: center; font-size: 18px; font-weight: 500; transform: translateY(20px); opacity: 0; transition: all 0.3s ease; } .feedback.show { transform: translateY(0); opacity: 1; } .feedback.correct { background-color: rgba(76, 175, 80, 0.1); color: var(--correct); border: 2px solid var(--correct); } .feedback.incorrect { background-color: rgba(255, 82, 82, 0.1); color: var(--incorrect); border: 2px solid var(--incorrect); } .next-button { display: block; margin: 25px auto 0; padding: 14px 30px; background-color: var(--primary); color: white; border: none; border-radius: 50px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; transform: scale(0); opacity: 0; } .next-button.show { transform: scale(1); opacity: 1; } .next-button:hover { background-color: #4750C8; box-shadow: 0 10px 20px rgba(92, 103, 222, 0.3); } .next-button:active { transform: scale(0.95); } .result-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--light); display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 40px; text-align: center; opacity: 0; pointer-events: none; transition: opacity 0.5s ease; z-index: 10; } .result-container.show { opacity: 1; pointer-events: auto; } .confetti { position: absolute; width: 10px; height: 10px; background-color: #f00; border-radius: 0; animation: fall 3s ease-in infinite; opacity: 0; } @keyframes fall { 0% { opacity: 1; top: -10%; transform: translateX(0) rotate(0deg); } 100% { opacity: 0; top: 100%; transform: translateX(100px) rotate(360deg); } } .result-score { font-size: 72px; font-weight: bold; color: var(--primary); margin-bottom: 20px; } .result-text { font-size: 24px; color: var(--dark); margin-bottom: 30px; } .restart-button { padding: 15px 35px; background-color: var(--primary); color: white; border: none; border-radius: 50px; font-size: 18px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 10px 25px rgba(92, 103, 222, 0.3); } .restart-button:hover { background-color: #4750C8; transform: translateY(-3px); } .restart-button:active { transform: translateY(-1px); } .loading-indicator { position: absolute; top: 0; left: 0; width: 100%; height: 5px; background: linear-gradient(90deg, transparent, var(--primary), transparent); background-size: 200% 100%; animation: loading 1.5s linear infinite; display: none; } @keyframes loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } .loading-indicator.show { display: block; } .bubble-icon { position: absolute; right: 15px; display: none; font-size: 20px; } .bubble.correct .bubble-icon.correct { display: block; color: var(--correct); } .bubble.incorrect .bubble-icon.incorrect { display: block; color: var(--incorrect); } @media (max-width: 600px) { .container { padding: 20px; border-radius: 20px; } .bubbles-container { grid-template-columns: 1fr; gap: 12px; } h1 { font-size: 22px; } .question { font-size: 16px; margin: 20px 0 25px; } .bubble { padding: 14px; } .bubble-letter { width: 35px; height: 35px; } .bubble-text { font-size: 15px; } } </style> </head> <body> <div class="container"> <div class="loading-indicator"></div> <div class="progress-container"> <div class="progress-bar"></div> </div> <h1>Astronomy Quiz</h1> <div class="question">What is the closest star to Earth?</div> <div class="bubbles-container"> <div class="bubble" data-correct="false"> <div class="bubble-letter">A</div> <div class="bubble-text">Proxima Centauri</div> <div class="bubble-icon correct">โ</div> <div class="bubble-icon incorrect">โ</div> </div> <div class="bubble" data-correct="true"> <div class="bubble-letter">B</div> <div class="bubble-text">The Sun</div> <div class="bubble-icon correct">โ</div> <div class="bubble-icon incorrect">โ</div> </div> <div class="bubble" data-correct="false"> <div class="bubble-letter">C</div> <div class="bubble-text">Alpha Centauri</div> <div class="bubble-icon correct">โ</div> <div class="bubble-icon incorrect">โ</div> </div> <div class="bubble" data-correct="false"> <div class="bubble-letter">D</div> <div class="bubble-text">Sirius</div> <div class="bubble-icon correct">โ</div> <div class="bubble-icon incorrect">โ</div> </div> </div> <div class="feedback"></div> <button class="next-button">Next Question</button> <div class="result-container"> <div class="result-score">0/5</div> <div class="result-text">Great job exploring the cosmos!</div> <button class="restart-button">Try Again</button> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const quizData = [ { question: "What is the closest star to Earth?", options: ["Proxima Centauri", "The Sun", "Alpha Centauri", "Sirius"], correctIndex: 1, explanation: "The Sun is actually the closest star to Earth, at about 93 million miles away." }, { question: "Which planet has the Great Red Spot?", options: ["Saturn", "Mars", "Jupiter", "Neptune"], correctIndex: 2, explanation: "Jupiter's Great Red Spot is a massive storm that has been raging for at least 400 years." }, { question: "What is the name of our galaxy?", options: ["Andromeda", "Triangulum", "Milky Way", "Messier 87"], correctIndex: 2, explanation: "We live in the Milky Way galaxy, which contains between 100-400 billion stars." }, { question: "What phenomenon causes the northern lights?", options: ["Solar flares", "Solar wind", "Meteor showers", "Lunar reflection"], correctIndex: 1, explanation: "The solar wind's charged particles interact with Earth's magnetosphere, creating the aurora." }, { question: "Which planet rotates on its side?", options: ["Venus", "Uranus", "Mercury", "Mars"], correctIndex: 1, explanation: "Uranus has an axial tilt of about 98 degrees, causing it to spin nearly horizontally." } ]; const progressBar = document.querySelector('.progress-bar'); const questionElement = document.querySelector('.question'); const bubblesContainer = document.querySelector('.bubbles-container'); const feedback = document.querySelector('.feedback'); const nextButton = document.querySelector('.next-button'); const resultContainer = document.querySelector('.result-container'); const resultScore = document.querySelector('.result-score'); const resultText = document.querySelector('.result-text'); const restartButton = document.querySelector('.restart-button'); const loadingIndicator = document.querySelector('.loading-indicator'); let currentQuestionIndex = 0; let score = 0; let selectedAnswer = null; let answeredCorrectly = false; function startQuiz() { currentQuestionIndex = 0; score = 0; resultContainer.classList.remove('show'); showQuestion(quizData[currentQuestionIndex]); updateProgressBar(); } function showQuestion(questionData) { resetState(); questionElement.textContent = questionData.question; questionData.options.forEach((option, index) => { const bubble = document.createElement('div'); bubble.classList.add('bubble'); bubble.dataset.correct = index === questionData.correctIndex; const letter = document.createElement('div'); letter.classList.add('bubble-letter'); letter.textContent = String.fromCharCode(65 + index); // A, B, C, D const text = document.createElement('div'); text.classList.add('bubble-text'); text.textContent = option; const correctIcon = document.createElement('div'); correctIcon.classList.add('bubble-icon', 'correct'); correctIcon.textContent = 'โ'; const incorrectIcon = document.createElement('div'); incorrectIcon.classList.add('bubble-icon', 'incorrect'); incorrectIcon.textContent = 'โ'; bubble.appendChild(letter); bubble.appendChild(text); bubble.appendChild(correctIcon); bubble.appendChild(incorrectIcon); bubble.addEventListener('click', () => selectAnswer(bubble)); bubblesContainer.appendChild(bubble); }); } function resetState() { nextButton.classList.remove('show'); feedback.classList.remove('show', 'correct', 'incorrect'); feedback.textContent = ''; while (bubblesContainer.firstChild) { bubblesContainer.removeChild(bubblesContainer.firstChild); } } function selectAnswer(selectedBubble) { if (selectedAnswer !== null) return; // Prevent multiple selections selectedAnswer = selectedBubble; const isCorrect = selectedBubble.dataset.correct === 'true'; answeredCorrectly = isCorrect; if (isCorrect) { selectedBubble.classList.add('correct'); score++; feedback.classList.add('correct'); feedback.textContent = 'โ ' + quizData[currentQuestionIndex].explanation; } else { selectedBubble.classList.add('incorrect'); feedback.classList.add('incorrect'); feedback.textContent = 'โ ' + quizData[currentQuestionIndex].explanation; // Show the correct answer const allBubbles = document.querySelectorAll('.bubble'); allBubbles.forEach(bubble => { if (bubble.dataset.correct === 'true') { bubble.classList.add('correct'); } }); } feedback.classList.add('show'); nextButton.classList.add('show'); // Disable all bubbles after selection document.querySelectorAll('.bubble').forEach(bubble => { bubble.style.pointerEvents = 'none'; }); } function nextQuestion() { selectedAnswer = null; currentQuestionIndex++; if (currentQuestionIndex < quizData.length) { loadingIndicator.classList.add('show'); setTimeout(() => { loadingIndicator.classList.remove('show'); showQuestion(quizData[currentQuestionIndex]); updateProgressBar(); }, 800); } else { showResult(); } } function updateProgressBar() { const progressPercentage = (currentQuestionIndex / quizData.length) * 100; progressBar.style.width = `${progressPercentage}%`; } function showResult() { resetState(); resultScore.textContent = `${score}/${quizData.length}`; let feedbackMessage = ""; if (score === quizData.length) { feedbackMessage = "Stellar performance! You're a cosmic genius!"; createConfetti(); } else if (score >= quizData.length * 0.7) { feedbackMessage = "Great job exploring the cosmos!"; } else if (score >= quizData.length * 0.5) { feedbackMessage = "Not bad! The universe is vast and complex."; } else { feedbackMessage = "Keep learning! Space is full of mysteries."; } resultText.textContent = feedbackMessage; resultContainer.classList.add('show'); } function createConfetti() { const confettiCount = 100; const container = document.querySelector('.container'); const colors = ['#5C67DE', '#FF6B6B', '#4CAF50', '#FFC107', '#03A9F4']; for (let i = 0; i < confettiCount; i++) { const confetti = document.createElement('div'); confetti.classList.add('confetti'); // Random position, color, and shape confetti.style.left = Math.random() * 100 + '%'; confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; confetti.style.width = Math.random() * 10 + 5 + 'px'; confetti.style.height = Math.random() * 10 + 5 + 'px'; // Random shape const shapeRandom = Math.random(); if (shapeRandom < 0.33) { confetti.style.borderRadius = '50%'; // Circle } else if (shapeRandom < 0.66) { confetti.style.borderRadius = '0'; // Square } else { confetti.style.width = '0'; confetti.style.height = '0'; confetti.style.borderLeft = '5px solid transparent'; confetti.style.borderRight = '5px solid transparent'; confetti.style.borderBottom = '10px solid ' + colors[Math.floor(Math.random() * colors.length)]; confetti.style.backgroundColor = 'transparent'; } // Random animation duration and delay confetti.style.animationDuration = (Math.random() * 3 + 2) + 's'; confetti.style.animationDelay = Math.random() * 5 + 's'; container.appendChild(confetti); // Remove after animation setTimeout(() => { confetti.remove(); }, 8000); } } nextButton.addEventListener('click', nextQuestion); restartButton.addEventListener('click', startQuiz); // Add ripple effect to buttons const buttons = document.querySelectorAll('button'); buttons.forEach(button => { button.addEventListener('click', function(e) { const rect = this.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const ripple = document.createElement('span'); ripple.style.position = 'absolute'; ripple.style.width = '0'; ripple.style.height = '0'; ripple.style.top = y + 'px'; ripple.style.left = x + 'px'; ripple.style.backgroundColor = 'rgba(255, 255, 255, 0.4)'; ripple.style.borderRadius = '50%'; ripple.style.transform = 'translate(-50%, -50%)'; ripple.style.transition = 'all 0.6s'; this.appendChild(ripple); setTimeout(() => { ripple.style.width = '200px'; ripple.style.height = '200px'; ripple.style.opacity = '0'; }, 10); setTimeout(() => { ripple.remove(); }, 600); }); }); // Start the quiz startQuiz(); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fitness Statistic Bubbles</title> <style> :root { --primary: #3a86ff; --secondary: #ff006e; --tertiary: #8338ec; --quaternary: #fb5607; --quinary: #06d6a0; --background: #f8f9fa; --text: #212529; --light-text: #6c757d; --white: #ffffff; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } body { background-color: var(--background); color: var(--text); display: flex; flex-direction: column; justify-content: flex-start; align-items: center; height: 100vh; width: 100%; padding: 1rem; overflow-x: hidden; } .container { max-width: 700px; width: 100%; height: 700px; display: flex; flex-direction: column; position: relative; } header { text-align: center; margin-bottom: 1.5rem; padding: 1rem; } h1 { font-size: 1.8rem; margin-bottom: 0.5rem; color: var(--text); font-weight: 700; } .subtitle { color: var(--light-text); font-size: 0.9rem; margin-bottom: 1rem; } .date-selector { display: flex; justify-content: center; margin-bottom: 1rem; } .date-selector button { background: none; border: none; color: var(--light-text); font-size: 0.9rem; cursor: pointer; padding: 0.5rem 1rem; transition: all 0.3s ease; position: relative; } .date-selector button.active { color: var(--text); font-weight: 600; } .date-selector button.active::after { content: ""; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 20px; height: 3px; background-color: var(--primary); border-radius: 2px; } .bubbles-container { position: relative; flex-grow: 1; display: flex; justify-content: center; align-items: center; padding: 1rem; overflow: hidden; } .bubble { position: absolute; border-radius: 50%; display: flex; flex-direction: column; justify-content: center; align-items: center; color: var(--white); cursor: pointer; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1); transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.3s ease; overflow: hidden; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); } .bubble::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: radial-gradient(circle at center, rgba(255, 255, 255, 0.15), transparent 70%); opacity: 0.5; } .bubble:hover { z-index: 10; transform: scale(1.05); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); } .bubble-icon { font-size: 1.5rem; margin-bottom: 0.5rem; transition: transform 0.3s ease; } .bubble:hover .bubble-icon { transform: translateY(-5px); } .bubble-title { font-weight: 600; font-size: 1rem; margin-bottom: 0.25rem; text-align: center; } .bubble-value { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem; transition: transform 0.3s ease; } .bubble:hover .bubble-value { transform: scale(1.1); } .bubble-subtitle { font-size: 0.7rem; opacity: 0.9; text-align: center; max-width: 80%; transition: opacity 0.3s ease; } .stats-detail { position: absolute; background: rgba(255, 255, 255, 0.95); border-radius: 1rem; padding: 1.5rem; width: 220px; box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1); z-index: 100; opacity: 0; visibility: hidden; transition: all 0.3s ease; transform: translateY(10px); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); } .stats-detail.active { opacity: 1; visibility: visible; transform: translateY(0); } .stats-detail h3 { margin-bottom: 1rem; font-weight: 600; font-size: 1.1rem; color: var(--text); border-bottom: 1px solid rgba(0, 0, 0, 0.1); padding-bottom: 0.5rem; } .stat-item { display: flex; justify-content: space-between; margin-bottom: 0.75rem; } .stat-label { color: var(--light-text); font-size: 0.85rem; } .stat-value { font-weight: 600; font-size: 0.85rem; } .progress-container { height: 4px; background-color: rgba(0, 0, 0, 0.05); border-radius: 2px; margin-top: 0.5rem; overflow: hidden; } .progress-bar { height: 100%; border-radius: 2px; transition: width 0.8s cubic-bezier(0.075, 0.82, 0.165, 1); } .close-stats { position: absolute; top: 0.75rem; right: 0.75rem; background: none; border: none; font-size: 1rem; color: var(--light-text); cursor: pointer; transition: all 0.3s ease; } .close-stats:hover { color: var(--text); transform: rotate(90deg); } .daily-summary { background-color: var(--white); border-radius: 1rem; padding: 1.2rem; margin-top: 1rem; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05); width: 100%; } .summary-title { font-size: 1rem; font-weight: 600; margin-bottom: 0.5rem; display: flex; justify-content: space-between; align-items: center; } .summary-date { font-size: 0.8rem; color: var(--light-text); font-weight: normal; } .summary-stats { display: flex; justify-content: space-between; margin-top: 0.75rem; } .summary-stat { text-align: center; flex: 1; } .summary-stat-value { font-size: 1.1rem; font-weight: 700; margin-bottom: 0.25rem; } .summary-stat-label { font-size: 0.7rem; color: var(--light-text); } /* Pulse Animation */ @keyframes pulse { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.05); opacity: 0.8; } 100% { transform: scale(1); opacity: 1; } } .pulse { animation: pulse 2s infinite; } /* Responsive adjustments */ @media (max-width: 600px) { h1 { font-size: 1.5rem; } .bubble-title { font-size: 0.85rem; } .bubble-value { font-size: 1.2rem; } .bubble-subtitle { font-size: 0.65rem; } .stats-detail { width: 200px; padding: 1.2rem; } .summary-stat-value { font-size: 0.9rem; } } @media (max-width: 400px) { .date-selector button { padding: 0.5rem 0.5rem; font-size: 0.8rem; } .stats-detail { width: 180px; padding: 1rem; } } </style> </head> <body> <div class="container"> <header> <h1>Fitness Activity Dashboard</h1> <p class="subtitle">Your workout intensity visualized</p> <div class="date-selector"> <button data-period="daily" class="active">Daily</button> <button data-period="weekly">Weekly</button> <button data-period="monthly">Monthly</button> </div> </header> <div class="bubbles-container" id="bubbles-container"> <!-- Bubbles will be generated by JS --> </div> <div class="stats-detail" id="stats-detail"> <button class="close-stats" id="close-stats">ร</button> <h3 id="stats-title">Activity Details</h3> <div id="stats-content"></div> </div> <div class="daily-summary"> <div class="summary-title"> Today's Overview <span class="summary-date" id="current-date">June 8, 2023</span> </div> <div class="summary-stats"> <div class="summary-stat"> <div class="summary-stat-value">2,458</div> <div class="summary-stat-label">CALORIES</div> </div> <div class="summary-stat"> <div class="summary-stat-value">76 min</div> <div class="summary-stat-label">ACTIVE TIME</div> </div> <div class="summary-stat"> <div class="summary-stat-value">7,341</div> <div class="summary-stat-label">STEPS</div> </div> <div class="summary-stat"> <div class="summary-stat-value">3</div> <div class="summary-stat-label">WORKOUTS</div> </div> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Set current date const today = new Date(); const dateOptions = { weekday: 'short', month: 'short', day: 'numeric' }; document.getElementById('current-date').textContent = today.toLocaleDateString('en-US', dateOptions); // Define workout data const workoutData = { daily: [ { id: 1, title: "Running", icon: "๐", value: "5.2 km", subtitle: "Morning run", color: "#3a86ff", intensity: 0.85, details: { duration: "32 min", pace: "6:09 min/km", calories: "412", heartRate: "152 bpm", elevation: "42m", steps: "6,874" } }, { id: 2, title: "Strength", icon: "๐ช", value: "45 min", subtitle: "Upper body focus", color: "#ff006e", intensity: 0.7, details: { sets: "18 sets", reps: "142 reps", calories: "372", heartRate: "128 bpm", restTime: "45 sec", maxWeight: "65 kg" } }, { id: 3, title: "Cycling", icon: "๐ด", value: "12.4 km", subtitle: "Evening ride", color: "#8338ec", intensity: 0.65, details: { duration: "38 min", pace: "19.6 km/h", calories: "286", heartRate: "142 bpm", elevation: "87m", resistance: "Level 5" } }, { id: 4, title: "Steps", icon: "๐ฃ", value: "7,341", subtitle: "Daily goal: 10,000", color: "#fb5607", intensity: 0.55, details: { distance: "5.8 km", calories: "312", activeTime: "76 min", floors: "9", goalProgress: "73%", trend: "+12% vs. avg" } }, { id: 5, title: "Stretching", icon: "๐ง", value: "15 min", subtitle: "Post-workout", color: "#06d6a0", intensity: 0.35, details: { flexibility: "Improved", focusArea: "Hamstrings & Back", calories: "48", heartRate: "82 bpm", routines: "6 poses", holdTime: "45 sec avg" } } ], weekly: [ { id: 6, title: "Total Distance", icon: "๐", value: "38.7 km", subtitle: "+12% from last week", color: "#3a86ff", intensity: 0.85, details: { running: "22.4 km", cycling: "48.2 km", walking: "18.1 km", highestDay: "Sunday (12.6 km)", lowestDay: "Friday (3.2 km)", trend: "Upward ๐" } }, { id: 7, title: "Active Time", icon: "โฑ๏ธ", value: "6.4 hrs", subtitle: "Across 12 sessions", color: "#ff006e", intensity: 0.75, details: { cardio: "3.2 hrs", strength: "1.8 hrs", flexibility: "1.4 hrs", mostActive: "Tuesday (1.5 hrs)", leastActive: "Thursday (0.4 hrs)", dailyAverage: "54 min" } }, { id: 8, title: "Calories", icon: "๐ฅ", value: "4,872", subtitle: "Weekly burn", color: "#8338ec", intensity: 0.65, details: { dailyAvg: "696 cal", bestDay: "Monday (827 cal)", worstDay: "Saturday (412 cal)", cardioContrib: "62%", strengthContrib: "28%", otherContrib: "10%" } }, { id: 9, title: "Workouts", icon: "๐๏ธ", value: "9 sessions", subtitle: "2 rest days", color: "#fb5607", intensity: 0.6, details: { running: "3 sessions", cycling: "2 sessions", strength: "3 sessions", yoga: "1 session", avgDuration: "42 min", mostFrequent: "Running" } }, { id: 10, title: "Heart Points", icon: "โค๏ธ", value: "138 pts", subtitle: "Weekly target: 150", color: "#06d6a0", intensity: 0.45, details: { moderate: "86 pts", vigorous: "52 pts", avgHeartRate: "132 bpm", peakHeartRate: "174 bpm", targetCompletion: "92%", zoneDuration: "204 min" } } ], monthly: [ { id: 11, title: "Running", icon: "๐", value: "87.3 km", subtitle: "Monthly distance", color: "#3a86ff", intensity: 0.9, details: { sessions: "12 runs", avgDistance: "7.3 km", totalTime: "8.4 hrs", avgPace: "5:47 min/km", bestPace: "5:12 min/km", calories: "6,782" } }, { id: 12, title: "Strength", icon: "๐ช", value: "14 workouts", subtitle: "Full body focus", color: "#ff006e", intensity: 0.8, details: { totalSets: "224 sets", totalReps: "1,842 reps", avgDuration: "52 min", topExercise: "Squat (320 reps)", progression: "+8% weight", calories: "4,256" } }, { id: 13, title: "Activity Score", icon: "๐", value: "824", subtitle: "Top 12% of users", color: "#8338ec", intensity: 0.75, details: { consistency: "91%", intensity: "76%", variety: "84%", restDays: "4 days", improvement: "+7%", streak: "18 days" } }, { id: 14, title: "Calories", icon: "๐ฅ", value: "18,742", subtitle: "Monthly burn", color: "#fb5607", intensity: 0.7, details: { dailyAvg: "625 cal", bestWeek: "Week 3 (5,142 cal)", worstWeek: "Week 1 (3,874 cal)", cardioContrib: "58%", strengthContrib: "32%", otherContrib: "10%" } }, { id: 15, title: "Steps", icon: "๐ฃ", value: "284,362", subtitle: "Avg: 9,479/day", color: "#06d6a0", intensity: 0.65, details: { distance: "213.2 km", dailyGoalHits: "24 days", bestDay: "15th (15,742 steps)", worstDay: "28th (4,213 steps)", calories: "12,246", trend: "Consistent" } }, { id: 16, title: "Recovery", icon: "๐ด", value: "78%", subtitle: "Sleep & rest quality", color: "#ffbe0b", intensity: 0.5, details: { avgSleep: "7.2 hrs", deepSleep: "1.6 hrs avg", restDays: "4 days", recoveryTrend: "Improving", readiness: "Good", recommendation: "More hydration" } } ] }; // Initialize with daily data let currentPeriod = 'daily'; renderBubbles(workoutData[currentPeriod]); // Period selector event listeners document.querySelectorAll('.date-selector button').forEach(button => { button.addEventListener('click', function() { document.querySelectorAll('.date-selector button').forEach(btn => { btn.classList.remove('active'); }); this.classList.add('active'); currentPeriod = this.dataset.period; renderBubbles(workoutData[currentPeriod]); // Close stats detail if open const statsDetail = document.getElementById('stats-detail'); statsDetail.classList.remove('active'); }); }); // Close button for stats detail document.getElementById('close-stats').addEventListener('click', function() { document.getElementById('stats-detail').classList.remove('active'); }); // Function to render bubbles function renderBubbles(data) { const container = document.getElementById('bubbles-container'); container.innerHTML = ''; // Calculate container dimensions const containerWidth = container.offsetWidth; const containerHeight = container.offsetHeight; const centerX = containerWidth / 2; const centerY = containerHeight / 2; // Create bubbles with random positions but ensure they don't overlap too much data.forEach((item, index) => { // Create bubble element const bubble = document.createElement('div'); bubble.className = 'bubble'; bubble.dataset.id = item.id; // Calculate bubble size based on intensity const minSize = Math.min(containerWidth, containerHeight) * 0.15; const maxSize = Math.min(containerWidth, containerHeight) * 0.28; const size = minSize + (maxSize - minSize) * item.intensity; // Set bubble style bubble.style.width = `${size}px`; bubble.style.height = `${size}px`; bubble.style.backgroundColor = item.color; // Determine position for the bubble // Use a circular layout for more predictable spacing const bubbleCount = data.length; const angle = (2 * Math.PI * index) / bubbleCount; const radius = Math.min(containerWidth, containerHeight) * 0.3; let posX = centerX + radius * Math.cos(angle) - size / 2; let posY = centerY + radius * Math.sin(angle) - size / 2; // Add some randomness for more natural look posX += (Math.random() - 0.5) * 30; posY += (Math.random() - 0.5) * 30; // Ensure bubbles stay within container bounds posX = Math.max(10, Math.min(containerWidth - size - 10, posX)); posY = Math.max(10, Math.min(containerHeight - size - 10, posY)); bubble.style.left = `${posX}px`; bubble.style.top = `${posY}px`; // Add content to bubble bubble.innerHTML = ` <div class="bubble-icon">${item.icon}</div> <div class="bubble-title">${item.title}</div> <div class="bubble-value">${item.value}</div> <div class="bubble-subtitle">${item.subtitle}</div> `; // Add click event to show details bubble.addEventListener('click', function(e) { e.stopPropagation(); showStats(item, posX + size / 2, posY + size / 2); }); // Add animation for most intense activity if (index === 0) { bubble.classList.add('pulse'); } container.appendChild(bubble); }); } // Function to show statistics for a bubble function showStats(item, bubbleX, bubbleY) { const statsDetail = document.getElementById('stats-detail'); const statsContent = document.getElementById('stats-content'); const statsTitle = document.getElementById('stats-title'); // Set the title statsTitle.textContent = `${item.icon} ${item.title} Details`; // Clear previous content statsContent.innerHTML = ''; // Add stats from details object let statsHTML = ''; for (const [key, value] of Object.entries(item.details)) { statsHTML += ` <div class="stat-item"> <div class="stat-label">${key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())}</div> <div class="stat-value">${value}</div> </div> `; } // Add a progress bar for applicable metrics if (item.intensity) { statsHTML += ` <div class="stat-item"> <div class="stat-label">Intensity</div> <div class="stat-value">${Math.round(item.intensity * 100)}%</div> </div> <div class="progress-container"> <div class="progress-bar" style="width: ${item.intensity * 100}%; background-color: ${item.color}"></div> </div> `; } statsContent.innerHTML = statsHTML; // Position the stats window near the bubble const container = document.getElementById('bubbles-container'); const containerRect = container.getBoundingClientRect(); const statsWidth = 220; // Width of stats detail box const statsHeight = 270; // Approximate height // Calculate position relative to container let posX = bubbleX - statsWidth / 2; let posY = bubbleY - statsHeight - 20; // Position above bubble with a gap // Adjust if the stats would go outside the container if (posX < 10) posX = 10; if (posX + statsWidth > containerRect.width - 10) posX = containerRect.width - statsWidth - 10; // If it would go above the container, position it below the bubble instead if (posY < 10) { posY = bubbleY + 20; // Position below bubble with a gap } // Ensure it's within container bounds if (posY + statsHeight > containerRect.height - 10) posY = containerRect.height - statsHeight - 10; statsDetail.style.left = `${posX}px`; statsDetail.style.top = `${posY}px`; // Show the stats detail statsDetail.classList.add('active'); // Add click event to close when clicking outside setTimeout(() => { document.addEventListener('click', closeStatsOutsideClick); }, 10); } // Function to close stats when clicking outside function closeStatsOutsideClick(e) { const statsDetail = document.getElementById('stats-detail'); if (!statsDetail.contains(e.target) && !e.target.classList.contains('bubble')) { statsDetail.classList.remove('active'); document.removeEventListener('click', closeStatsOutsideClick); } } }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>E-commerce Highlight Bubbles</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Poppins', sans-serif; } body { background-color: #f8f9fa; display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow: hidden; padding: 15px; } .container { position: relative; width: 100%; max-width: 670px; height: 670px; background-color: #ffffff; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); overflow: hidden; } .store-header { padding: 20px; background: linear-gradient(135deg, #6e8efb, #4a6cf7); color: white; text-align: center; position: relative; } .store-header h1 { font-size: 1.8rem; margin-bottom: 5px; font-weight: 600; } .store-header p { font-size: 0.9rem; opacity: 0.9; } .products-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; padding: 20px; position: relative; } .product-card { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.06); transition: transform 0.3s ease, box-shadow 0.3s ease; cursor: pointer; position: relative; } .product-card:hover { transform: translateY(-5px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); } .product-img { height: 140px; background-position: center; background-size: cover; background-repeat: no-repeat; } .product-details { padding: 15px; } .product-title { font-size: 0.9rem; font-weight: 600; margin-bottom: 5px; color: #333; } .product-price { display: flex; align-items: center; font-size: 1rem; margin-bottom: 10px; } .original-price { text-decoration: line-through; color: #999; margin-right: 10px; font-size: 0.85rem; } .discounted-price { color: #4a6cf7; font-weight: 600; } .product-meta { display: flex; justify-content: space-between; align-items: center; font-size: 0.75rem; } .product-rating { color: #ffa41c; } .product-stock { color: #28a745; } /* Highlight Bubbles */ .bubble-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; } .highlight-bubble { position: absolute; border-radius: 50%; padding: 5px; display: flex; justify-content: center; align-items: center; transform-origin: center; cursor: pointer; pointer-events: auto; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); animation: bubble-pulse 2s infinite; opacity: 0; transition: opacity 0.5s ease, transform 0.3s ease; } .highlight-bubble.visible { opacity: 1; } .highlight-bubble:hover { transform: scale(1.1); animation-play-state: paused; } .bubble-inner { width: 100%; height: 100%; border-radius: 50%; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 10px; transition: transform 0.3s ease; } .bubble-title { font-weight: 600; margin-bottom: 2px; font-size: 0.8rem; color: white; } .bubble-subtitle { font-size: 0.65rem; font-weight: 500; color: rgba(255, 255, 255, 0.9); } .bubble-timer { font-size: 0.7rem; margin-top: 3px; color: rgba(255, 255, 255, 0.9); font-weight: 500; } .flash-sale { background: linear-gradient(135deg, #ff7043, #ff5722); width: 80px; height: 80px; } .new-arrival { background: linear-gradient(135deg, #26c6da, #00acc1); width: 70px; height: 70px; } .bundle-deal { background: linear-gradient(135deg, #ab47bc, #8e24aa); width: 85px; height: 85px; } .limited-offer { background: linear-gradient(135deg, #ffa726, #fb8c00); width: 75px; height: 75px; } .close-bubble { position: absolute; top: 0; right: 0; width: 18px; height: 18px; display: flex; justify-content: center; align-items: center; background: rgba(255, 255, 255, 0.3); border-radius: 50%; font-size: 0.6rem; color: white; cursor: pointer; opacity: 0; transition: opacity 0.2s; } .highlight-bubble:hover .close-bubble { opacity: 1; } /* Extended bubble */ .bubble-extended { position: absolute; border-radius: 8px; background: white; box-shadow: 0 5px 25px rgba(0, 0, 0, 0.15); padding: 15px; z-index: 100; width: 220px; pointer-events: auto; opacity: 0; transform: scale(0.9); transition: opacity 0.3s, transform 0.3s; display: none; } .bubble-extended.active { opacity: 1; transform: scale(1); display: block; } .extended-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .extended-title { font-weight: 600; font-size: 0.95rem; } .extended-close { width: 20px; height: 20px; border-radius: 50%; display: flex; justify-content: center; align-items: center; background: #f5f5f5; cursor: pointer; font-size: 0.7rem; color: #555; } .extended-details { margin-bottom: 12px; } .extended-discount { font-size: 1.2rem; font-weight: 700; margin-bottom: 5px; color: #ff5722; } .extended-desc { font-size: 0.8rem; color: #666; line-height: 1.4; margin-bottom: 10px; } .extended-timer { display: flex; justify-content: center; gap: 5px; margin-bottom: 12px; } .timer-unit { background: #f5f5f5; border-radius: 4px; padding: 4px 6px; display: flex; flex-direction: column; align-items: center; min-width: 40px; } .timer-value { font-weight: 600; font-size: 0.85rem; color: #333; } .timer-label { font-size: 0.6rem; color: #888; } .extended-cta { background: #4a6cf7; color: white; border: none; border-radius: 6px; padding: 8px 12px; font-size: 0.85rem; font-weight: 500; cursor: pointer; width: 100%; transition: background 0.2s; display: flex; justify-content: center; align-items: center; gap: 5px; } .extended-cta:hover { background: #3a5be6; } .cta-icon { font-size: 0.75rem; } @keyframes bubble-pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } /* Notification */ .notification { position: absolute; bottom: -60px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 10px 20px; border-radius: 4px; font-size: 0.85rem; transition: bottom 0.3s ease; z-index: 1000; } .notification.show { bottom: 20px; } /* Responsive adjustments */ @media (max-width: 480px) { .products-grid { grid-template-columns: 1fr; } .store-header h1 { font-size: 1.5rem; } .highlight-bubble { transform: scale(0.85); } .highlight-bubble:hover { transform: scale(0.95); } .bubble-extended { width: 80%; max-width: 220px; } } </style> </head> <body> <div class="container"> <div class="store-header"> <h1>Trendy Finds</h1> <p>Discover special deals just for you</p> </div> <div class="products-grid"> <div class="product-card" id="product1"> <div class="product-img" style="background-image: url('https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=300&q=80');"></div> <div class="product-details"> <div class="product-title">Premium Wireless Headphones</div> <div class="product-price"> <span class="original-price">$129.99</span> <span class="discounted-price">$79.99</span> </div> <div class="product-meta"> <div class="product-rating">โ โ โ โ โ (128)</div> <div class="product-stock">In Stock</div> </div> </div> </div> <div class="product-card" id="product2"> <div class="product-img" style="background-image: url('https://images.unsplash.com/photo-1523275335684-37898b6baf30?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=300&q=80');"></div> <div class="product-details"> <div class="product-title">Smart Watch Pro Series</div> <div class="product-price"> <span class="original-price">$199.99</span> <span class="discounted-price">$149.99</span> </div> <div class="product-meta"> <div class="product-rating">โ โ โ โ โ (94)</div> <div class="product-stock">Limited Stock</div> </div> </div> </div> <div class="product-card" id="product3"> <div class="product-img" style="background-image: url('https://images.unsplash.com/photo-1583394838336-acd977736f90?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=300&q=80');"></div> <div class="product-details"> <div class="product-title">Ultra HD Portable Speaker</div> <div class="product-price"> <span class="original-price">$89.99</span> <span class="discounted-price">$69.99</span> </div> <div class="product-meta"> <div class="product-rating">โ โ โ โ โ (76)</div> <div class="product-stock">In Stock</div> </div> </div> </div> <div class="product-card" id="product4"> <div class="product-img" style="background-image: url('https://images.unsplash.com/photo-1546868871-7041f2a55e12?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=300&q=80');"></div> <div class="product-details"> <div class="product-title">Noise Canceling Earbuds</div> <div class="product-price"> <span class="original-price">$159.99</span> <span class="discounted-price">$119.99</span> </div> <div class="product-meta"> <div class="product-rating">โ โ โ โ โ (143)</div> <div class="product-stock">In Stock</div> </div> </div> </div> </div> <div class="bubble-container"> <!-- Bubbles will be created dynamically --> </div> <div class="notification" id="notification">Added to cart!</div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Bubble data const bubbles = [ { id: 'flash-sale', type: 'flash-sale', title: 'FLASH', subtitle: '40% OFF', product: 'product1', position: { top: '30%', left: '20%' }, extendedTitle: '6-Hour Flash Sale', extendedDesc: 'Premium Wireless Headphones with noise cancellation and 40-hour battery life. Limited time deal!', discount: '40% OFF', endTime: new Date(Date.now() + 6 * 60 * 60 * 1000), // 6 hours from now ctaText: 'Add to Cart' }, { id: 'new-arrival', type: 'new-arrival', title: 'NEW', subtitle: 'ARRIVAL', product: 'product2', position: { top: '25%', right: '15%' }, extendedTitle: 'Just Launched', extendedDesc: 'The Smart Watch Pro Series features health tracking, notifications, and up to 7 days of battery life.', discount: '25% OFF', endTime: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now ctaText: 'Explore Features' }, { id: 'bundle-deal', type: 'bundle-deal', title: 'BUNDLE', subtitle: 'SAVE 30%', product: 'product3', position: { bottom: '20%', left: '25%' }, extendedTitle: 'Speaker Bundle Deal', extendedDesc: 'Buy the Ultra HD Portable Speaker and get a carrying case + power bank at 30% off the bundle price.', discount: '30% BUNDLE', endTime: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), // 3 days from now ctaText: 'Get Bundle' }, { id: 'limited-offer', type: 'limited-offer', title: 'LIMITED', subtitle: 'OFFER', product: 'product4', position: { bottom: '25%', right: '20%' }, extendedTitle: 'Limited Stock Deal', extendedDesc: 'Only 8 units left! Noise Canceling Earbuds with transparency mode and wireless charging case.', discount: 'SAVE $40', endTime: new Date(Date.now() + 12 * 60 * 60 * 1000), // 12 hours from now ctaText: 'Claim Offer' } ]; // Create bubbles const bubbleContainer = document.querySelector('.bubble-container'); bubbles.forEach(bubble => { createBubble(bubble); }); // Create extended info for bubbles bubbles.forEach(bubble => { createExtendedBubble(bubble); }); // Set random delays for bubbles to appear setTimeout(() => { document.querySelectorAll('.highlight-bubble').forEach((bubble, index) => { setTimeout(() => { bubble.classList.add('visible'); }, index * 500 + Math.random() * 500); }); }, 1000); // Create bubble function function createBubble(bubbleData) { const bubble = document.createElement('div'); bubble.classList.add('highlight-bubble', bubbleData.type); bubble.id = bubbleData.id; // Set position for (const [key, value] of Object.entries(bubbleData.position)) { bubble.style[key] = value; } // Create inner content bubble.innerHTML = ` <div class="bubble-inner"> <div class="bubble-title">${bubbleData.title}</div> <div class="bubble-subtitle">${bubbleData.subtitle}</div> </div> <div class="close-bubble">ร</div> `; // Add click event bubble.addEventListener('click', function(e) { if (e.target.classList.contains('close-bubble')) { bubble.classList.remove('visible'); setTimeout(() => bubble.remove(), 300); return; } const extendedBubble = document.getElementById(`extended-${bubbleData.id}`); // Position the extended bubble const bubbleRect = bubble.getBoundingClientRect(); const containerRect = bubbleContainer.getBoundingClientRect(); // Default positioning let left = bubbleRect.left - containerRect.left; let top = bubbleRect.top - containerRect.top; // Adjust if the extended bubble would go outside container if (window.innerWidth < 480) { // For mobile left = Math.max(10, Math.min(left, containerRect.width - 230)); if (bubbleRect.top > window.innerHeight / 2) { top = top - 230; // Show above the bubble } else { top = top + bubbleRect.height + 10; // Show below the bubble } } else { // For desktop if (bubbleRect.left > containerRect.width / 2) { left = left - 230; // Show to the left } else { left = left + bubbleRect.width + 10; // Show to the right } // Vertical adjustment top = Math.max(10, Math.min(top, containerRect.height - 230)); } extendedBubble.style.left = `${left}px`; extendedBubble.style.top = `${top}px`; extendedBubble.classList.add('active'); // Update timer updateTimer(bubbleData.id); }); bubbleContainer.appendChild(bubble); } // Create extended bubble function function createExtendedBubble(bubbleData) { const extendedBubble = document.createElement('div'); extendedBubble.classList.add('bubble-extended'); extendedBubble.id = `extended-${bubbleData.id}`; extendedBubble.innerHTML = ` <div class="extended-header"> <div class="extended-title">${bubbleData.extendedTitle}</div> <div class="extended-close">ร</div> </div> <div class="extended-details"> <div class="extended-discount">${bubbleData.discount}</div> <div class="extended-desc">${bubbleData.extendedDesc}</div> </div> <div class="extended-timer" id="timer-${bubbleData.id}"> <div class="timer-unit"> <div class="timer-value" id="hours-${bubbleData.id}">05</div> <div class="timer-label">Hours</div> </div> <div class="timer-unit"> <div class="timer-value" id="mins-${bubbleData.id}">45</div> <div class="timer-label">Mins</div> </div> <div class="timer-unit"> <div class="timer-value" id="secs-${bubbleData.id}">22</div> <div class="timer-label">Secs</div> </div> </div> <button class="extended-cta" data-product="${bubbleData.product}"> <span class="cta-icon">โ</span> ${bubbleData.ctaText} </button> `; bubbleContainer.appendChild(extendedBubble); // Close extended bubble extendedBubble.querySelector('.extended-close').addEventListener('click', function() { extendedBubble.classList.remove('active'); }); // CTA button action extendedBubble.querySelector('.extended-cta').addEventListener('click', function() { const notification = document.getElementById('notification'); notification.textContent = 'Added to cart successfully!'; notification.classList.add('show'); setTimeout(() => { notification.classList.remove('show'); }, 3000); extendedBubble.classList.remove('active'); }); } // Update timer function function updateTimer(bubbleId) { const bubble = bubbles.find(b => b.id === bubbleId); const endTime = bubble.endTime; const timerInterval = setInterval(() => { const now = new Date(); const distance = endTime - now; if (distance < 0) { clearInterval(timerInterval); document.getElementById(`hours-${bubbleId}`).textContent = '00'; document.getElementById(`mins-${bubbleId}`).textContent = '00'; document.getElementById(`secs-${bubbleId}`).textContent = '00'; return; } const hours = Math.floor(distance / (1000 * 60 * 60)); const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((distance % (1000 * 60)) / 1000); document.getElementById(`hours-${bubbleId}`).textContent = hours.toString().padStart(2, '0'); document.getElementById(`mins-${bubbleId}`).textContent = minutes.toString().padStart(2, '0'); document.getElementById(`secs-${bubbleId}`).textContent = seconds.toString().padStart(2, '0'); }, 1000); // Initial update const now = new Date(); const distance = endTime - now; const hours = Math.floor(distance / (1000 * 60 * 60)); const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((distance % (1000 * 60)) / 1000); document.getElementById(`hours-${bubbleId}`).textContent = hours.toString().padStart(2, '0'); document.getElementById(`mins-${bubbleId}`).textContent = minutes.toString().padStart(2, '0'); document.getElementById(`secs-${bubbleId}`).textContent = seconds.toString().padStart(2, '0'); } // Close extended bubbles when clicking outside document.addEventListener('click', function(e) { if (!e.target.closest('.highlight-bubble') && !e.target.closest('.bubble-extended')) { document.querySelectorAll('.bubble-extended.active').forEach(bubble => { bubble.classList.remove('active'); }); } }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> :root { --primary: #5a7d9a; --stable: #68b097; --critical: #e78486; --caution: #efc15f; --improving: #8b9fe3; --neutral: #d2d5e1; --background: #f5f9fc; --text: #2e3c48; --text-light: #5d6c7b; --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } * { margin: 0; padding: 0; box-sizing: border-box; } body { background-color: var(--background); font-family: var(--font); color: var(--text); height: 100vh; display: flex; justify-content: center; align-items: center; } .container { width: 650px; padding: 24px; background-color: white; border-radius: 16px; box-shadow: 0 12px 30px rgba(90, 125, 154, 0.1); } h1 { font-size: 1.6rem; margin-bottom: 0.5rem; color: var(--text); font-weight: 600; } .subtitle { color: var(--text-light); font-size: 0.9rem; margin-bottom: 1.5rem; } .patients-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 18px; margin-bottom: 30px; } .patient-card { background-color: var(--background); border-radius: 12px; padding: 16px; position: relative; transition: transform 0.2s ease, box-shadow 0.2s ease; cursor: pointer; } .patient-card:hover { transform: translateY(-5px); box-shadow: 0 8px 16px rgba(90, 125, 154, 0.12); } .patient-header { display: flex; align-items: center; margin-bottom: 12px; } .patient-photo { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; margin-right: 10px; border: 2px solid white; } .patient-name { font-weight: 600; font-size: 0.95rem; line-height: 1.3; } .patient-status { display: flex; align-items: center; margin-top: 2px; font-size: 0.8rem; color: var(--text-light); } .status-bubble { width: 12px; height: 12px; border-radius: 50%; margin-right: 6px; position: relative; } .status-bubble::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 50%; animation: pulse 2s infinite; z-index: 0; } @keyframes pulse { 0% { transform: scale(1); opacity: 0.7; } 70% { transform: scale(1.5); opacity: 0; } 100% { transform: scale(1.5); opacity: 0; } } .stable { background-color: var(--stable); } .stable::after { background-color: var(--stable); animation-duration: 3s; } .critical { background-color: var(--critical); } .critical::after { background-color: var(--critical); animation-duration: 1.5s; } .caution { background-color: var(--caution); } .caution::after { background-color: var(--caution); animation-duration: 2s; } .improving { background-color: var(--improving); } .improving::after { background-color: var(--improving); animation-duration: 2.5s; } .neutral { background-color: var(--neutral); } .vital-signs { display: flex; flex-wrap: wrap; gap: 8px; } .vital-item { background-color: white; border-radius: 8px; padding: 6px 10px; font-size: 0.75rem; display: flex; align-items: center; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); color: var(--text); } .vital-icon { width: 14px; height: 14px; margin-right: 6px; opacity: 0.7; } .legend { display: flex; gap: 16px; margin-bottom: 12px; flex-wrap: wrap; border-top: 1px solid rgba(0, 0, 0, 0.06); padding-top: 16px; } .legend-item { display: flex; align-items: center; font-size: 0.8rem; color: var(--text-light); } .tooltip { position: absolute; background-color: rgba(46, 60, 72, 0.95); color: white; padding: 10px 14px; border-radius: 6px; font-size: 0.8rem; bottom: 100%; left: 50%; transform: translateX(-50%) translateY(-8px); opacity: 0; visibility: hidden; transition: all 0.2s ease; pointer-events: none; width: max-content; max-width: 200px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 100; } .tooltip::after { content: ''; position: absolute; top: 100%; left: 50%; margin-left: -6px; border-width: 6px; border-style: solid; border-color: rgba(46, 60, 72, 0.95) transparent transparent transparent; } .patient-card:hover .tooltip { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(-12px); } .update-time { font-size: 0.7rem; color: var(--text-light); opacity: 0.8; margin-top: 8px; text-align: right; } .visual-status { height: 3px; width: 100%; background-color: var(--neutral); border-radius: 3px; overflow: hidden; margin-top: 10px; } .visual-status-bar { height: 100%; width: 80%; border-radius: 3px; } .stable-bar { background-color: var(--stable); animation: statusPulse 3s infinite; } .critical-bar { background-color: var(--critical); animation: statusPulse 1.5s infinite; width: 95%; } .caution-bar { background-color: var(--caution); animation: statusPulse 2s infinite; width: 60%; } .improving-bar { background-color: var(--improving); animation: statusPulse 2.5s infinite; width: 75%; } @keyframes statusPulse { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } } .button-container { display: flex; justify-content: flex-end; gap: 12px; } .action-button { border: none; background-color: var(--primary); color: white; padding: 10px 16px; border-radius: 8px; font-size: 0.9rem; cursor: pointer; transition: background-color 0.2s ease; font-weight: 500; display: flex; align-items: center; } .action-button svg { margin-right: 8px; } .action-button:hover { background-color: #4a6b88; } .secondary-button { background-color: transparent; color: var(--primary); border: 1px solid var(--primary); } .secondary-button:hover { background-color: rgba(90, 125, 154, 0.08); } .notification-dot { position: absolute; top: -4px; right: -4px; width: 8px; height: 8px; background-color: var(--critical); border-radius: 50%; border: 2px solid white; } @media (max-width: 600px) { .container { width: 100%; padding: 16px; border-radius: 12px; } .patients-grid { grid-template-columns: 1fr 1fr; gap: 12px; } h1 { font-size: 1.4rem; } } @media (max-width: 400px) { .patients-grid { grid-template-columns: 1fr; } .button-container { flex-direction: column; } .action-button { width: 100%; } } </style> </head> <body> <div class="container"> <h1>Patient Monitoring</h1> <p class="subtitle">Real-time vital signs and patient status indicators</p> <div class="patients-grid"> <div class="patient-card" data-status="critical"> <div class="notification-dot"></div> <div class="tooltip">Heart rate elevated for over 30 minutes. Respiratory rate decreasing.</div> <div class="patient-header"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='100' height='100'%3E%3Ccircle cx='50' cy='40' r='25' fill='%23e78486'/%3E%3Cpath d='M50 80 Q 30 55 15 75 Q 15 100 50 100 Q 85 100 85 75 Q 70 55 50 80' fill='%23e78486'/%3E%3C/svg%3E" alt="Patient" class="patient-photo"> <div> <div class="patient-name">Robert Chen</div> <div class="patient-status"> <div class="status-bubble critical"></div> Critical </div> </div> </div> <div class="vital-signs"> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z"></path> </svg> 142 BPM </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 2a8 8 0 0 0-8 8c0 5.4 7.4 11.5 7.6 11.7.3.2.7.2 1 0 .1-.1 7.6-6.3 7.6-11.7a8 8 0 0 0-8-8z"></path> <circle cx="12" cy="10" r="3"></circle> </svg> 38.7ยฐC </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M2 12h5l2-6 4 12 2-6h5"></path> </svg> 10 resp/min </div> </div> <div class="visual-status"> <div class="visual-status-bar critical-bar"></div> </div> <div class="update-time">Updated 2 minutes ago</div> </div> <div class="patient-card" data-status="stable"> <div class="patient-header"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='100' height='100'%3E%3Ccircle cx='50' cy='40' r='25' fill='%2368b097'/%3E%3Cpath d='M50 80 Q 30 55 15 75 Q 15 100 50 100 Q 85 100 85 75 Q 70 55 50 80' fill='%2368b097'/%3E%3C/svg%3E" alt="Patient" class="patient-photo"> <div> <div class="patient-name">Sarah Johnson</div> <div class="patient-status"> <div class="status-bubble stable"></div> Stable </div> </div> </div> <div class="tooltip">All vital signs within normal range. Consistent readings for the past 24 hours.</div> <div class="vital-signs"> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z"></path> </svg> 72 BPM </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 2a8 8 0 0 0-8 8c0 5.4 7.4 11.5 7.6 11.7.3.2.7.2 1 0 .1-.1 7.6-6.3 7.6-11.7a8 8 0 0 0-8-8z"></path> <circle cx="12" cy="10" r="3"></circle> </svg> 36.8ยฐC </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M2 12h5l2-6 4 12 2-6h5"></path> </svg> 16 resp/min </div> </div> <div class="visual-status"> <div class="visual-status-bar stable-bar"></div> </div> <div class="update-time">Updated 5 minutes ago</div> </div> <div class="patient-card" data-status="caution"> <div class="tooltip">Blood pressure rising gradually. Medication adjustment may be needed.</div> <div class="patient-header"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='100' height='100'%3E%3Ccircle cx='50' cy='40' r='25' fill='%23efc15f'/%3E%3Cpath d='M50 80 Q 30 55 15 75 Q 15 100 50 100 Q 85 100 85 75 Q 70 55 50 80' fill='%23efc15f'/%3E%3C/svg%3E" alt="Patient" class="patient-photo"> <div> <div class="patient-name">Michael Wilson</div> <div class="patient-status"> <div class="status-bubble caution"></div> Caution </div> </div> </div> <div class="vital-signs"> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z"></path> </svg> 88 BPM </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 2a8 8 0 0 0-8 8c0 5.4 7.4 11.5 7.6 11.7.3.2.7.2 1 0 .1-.1 7.6-6.3 7.6-11.7a8 8 0 0 0-8-8z"></path> <circle cx="12" cy="10" r="3"></circle> </svg> 37.2ยฐC </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M2 12h5l2-6 4 12 2-6h5"></path> </svg> 148/92 </div> </div> <div class="visual-status"> <div class="visual-status-bar caution-bar"></div> </div> <div class="update-time">Updated 8 minutes ago</div> </div> <div class="patient-card" data-status="improving"> <div class="tooltip">Post-operative recovery proceeding well. Pain levels decreasing, mobility improving.</div> <div class="patient-header"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='100' height='100'%3E%3Ccircle cx='50' cy='40' r='25' fill='%238b9fe3'/%3E%3Cpath d='M50 80 Q 30 55 15 75 Q 15 100 50 100 Q 85 100 85 75 Q 70 55 50 80' fill='%238b9fe3'/%3E%3C/svg%3E" alt="Patient" class="patient-photo"> <div> <div class="patient-name">Emma Davis</div> <div class="patient-status"> <div class="status-bubble improving"></div> Improving </div> </div> </div> <div class="vital-signs"> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z"></path> </svg> 78 BPM </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 2a8 8 0 0 0-8 8c0 5.4 7.4 11.5 7.6 11.7.3.2.7.2 1 0 .1-.1 7.6-6.3 7.6-11.7a8 8 0 0 0-8-8z"></path> <circle cx="12" cy="10" r="3"></circle> </svg> 37.0ยฐC </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 8v4l2 2"></path> <circle cx="12" cy="12" r="10"></circle> </svg> 3/10 Pain </div> </div> <div class="visual-status"> <div class="visual-status-bar improving-bar"></div> </div> <div class="update-time">Updated 12 minutes ago</div> </div> <div class="patient-card" data-status="critical"> <div class="notification-dot"></div> <div class="tooltip">Oxygen saturation dropping below acceptable threshold. Blood pressure unstable.</div> <div class="patient-header"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='100' height='100'%3E%3Ccircle cx='50' cy='40' r='25' fill='%23e78486'/%3E%3Cpath d='M50 80 Q 30 55 15 75 Q 15 100 50 100 Q 85 100 85 75 Q 70 55 50 80' fill='%23e78486'/%3E%3C/svg%3E" alt="Patient" class="patient-photo"> <div> <div class="patient-name">James Miller</div> <div class="patient-status"> <div class="status-bubble critical"></div> Critical </div> </div> </div> <div class="vital-signs"> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z"></path> </svg> 132 BPM </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <path d="M8 14s1.5 2 4 2 4-2 4-2"></path> <line x1="9" y1="9" x2="9.01" y2="9"></line> <line x1="15" y1="9" x2="15.01" y2="9"></line> </svg> 92% Oโ </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M2 12h5l2-6 4 12 2-6h5"></path> </svg> 90/50 </div> </div> <div class="visual-status"> <div class="visual-status-bar critical-bar"></div> </div> <div class="update-time">Updated 1 minute ago</div> </div> <div class="patient-card" data-status="stable"> <div class="tooltip">Regular check-up. Maintaining healthy parameters post-treatment.</div> <div class="patient-header"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='100' height='100'%3E%3Ccircle cx='50' cy='40' r='25' fill='%2368b097'/%3E%3Cpath d='M50 80 Q 30 55 15 75 Q 15 100 50 100 Q 85 100 85 75 Q 70 55 50 80' fill='%2368b097'/%3E%3C/svg%3E" alt="Patient" class="patient-photo"> <div> <div class="patient-name">Olivia Thompson</div> <div class="patient-status"> <div class="status-bubble stable"></div> Stable </div> </div> </div> <div class="vital-signs"> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.42 4.58a5.4 5.4 0 0 0-7.65 0l-.77.78-.77-.78a5.4 5.4 0 0 0-7.65 0C1.46 6.7 1.33 10.28 4 13l8 8 8-8c2.67-2.72 2.54-6.3.42-8.42z"></path> </svg> 68 BPM </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 2a8 8 0 0 0-8 8c0 5.4 7.4 11.5 7.6 11.7.3.2.7.2 1 0 .1-.1 7.6-6.3 7.6-11.7a8 8 0 0 0-8-8z"></path> <circle cx="12" cy="10" r="3"></circle> </svg> 36.5ยฐC </div> <div class="vital-item"> <svg class="vital-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M2 12h5l2-6 4 12 2-6h5"></path> </svg> 118/76 </div> </div> <div class="visual-status"> <div class="visual-status-bar stable-bar"></div> </div> <div class="update-time">Updated 7 minutes ago</div> </div> </div> <div class="legend"> <div class="legend-item"> <div class="status-bubble stable"></div> Stable: Normal parameters </div> <div class="legend-item"> <div class="status-bubble critical"></div> Critical: Immediate attention </div> <div class="legend-item"> <div class="status-bubble caution"></div> Caution: Monitor closely </div> <div class="legend-item"> <div class="status-bubble improving"></div> Improving: Positive trend </div> </div> <div class="button-container"> <button class="action-button secondary-button"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line> </svg> Filter View </button> <button class="action-button"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path> </svg> Refresh Data </button> </div> </div> <script> // Simulating real-time data updates const patientCards = document.querySelectorAll('.patient-card'); // Add interactive elements patientCards.forEach(card => { // Add click effect card.addEventListener('click', function() { this.style.transform = 'scale(0.98)'; setTimeout(() => { this.style.transform = ''; }, 150); // Show expanded info (could be implemented to show a modal) const status = this.getAttribute('data-status'); console.log(`Patient ${this.querySelector('.patient-name').textContent} - ${status} status`); }); // Simulated real-time updates if (Math.random() > 0.7) { setInterval(() => { const heartRateElement = card.querySelector('.vital-item:first-child'); if (heartRateElement) { const currentRate = parseInt(heartRateElement.textContent); const newRate = currentRate + Math.floor(Math.random() * 5) - 2; heartRateElement.textContent = `${newRate} BPM`; } const timeElement = card.querySelector('.update-time'); if (timeElement) { timeElement.textContent = 'Updated just now'; setTimeout(() => { timeElement.textContent = 'Updated 1 minute ago'; }, 60000); } }, 30000 + Math.random() * 60000); } }); // Add functionality to buttons document.querySelectorAll('.action-button').forEach(button => { button.addEventListener('click', function(e) { e.preventDefault(); this.style.transform = 'scale(0.95)'; setTimeout(() => { this.style.transform = ''; }, 150); if (this.textContent.includes('Refresh')) { // Simulate refresh patientCards.forEach(card => { card.style.opacity = '0.7'; setTimeout(() => { card.style.opacity = '1'; }, 300); }); } }); }); // Accessibility improvements - ensure keyboard navigation works patientCards.forEach(card => { card.setAttribute('tabindex', '0'); card.setAttribute('role', 'button'); card.setAttribute('aria-label', `Patient ${card.querySelector('.patient-name').textContent} with ${card.getAttribute('data-status')} status`); card.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.click(); } }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Weather Forecast Bubbles</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; } body { width: 100%; height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: #f5f7fa; overflow: hidden; color: #2c3e50; } .container { width: 700px; height: 700px; padding: 30px; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; position: relative; } .search-container { width: 100%; max-width: 400px; margin-bottom: 30px; position: relative; z-index: 10; } .search-bar { width: 100%; padding: 15px 20px; border-radius: 30px; border: none; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); font-size: 16px; outline: none; transition: all 0.3s ease; } .search-bar:focus { box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); } .search-icon { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #7f8c8d; } .bubbles-container { width: 100%; display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; perspective: 1000px; } .weather-bubble { width: 140px; height: 140px; border-radius: 50%; display: flex; flex-direction: column; justify-content: center; align-items: center; color: white; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); position: relative; cursor: pointer; transition: transform 0.5s ease, box-shadow 0.5s ease; overflow: hidden; opacity: 0; transform: scale(0.8); animation: popIn 0.5s ease forwards; z-index: 1; } @keyframes popIn { 0% { opacity: 0; transform: scale(0.8); } 70% { transform: scale(1.05); } 100% { opacity: 1; transform: scale(1); } } .weather-bubble:hover { transform: translateY(-10px) scale(1.05); box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2); z-index: 2; } .bubble-content { z-index: 2; text-align: center; width: 100%; transition: transform 0.3s ease; } .weather-bubble:hover .bubble-content { transform: scale(1.1); } .day { font-size: 16px; font-weight: 600; margin-bottom: 8px; } .temp { font-size: 28px; font-weight: 700; margin-bottom: 5px; } .condition { font-size: 12px; opacity: 0.9; } .weather-icon { font-size: 24px; margin-bottom: 5px; } .bubble-bg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 50%; z-index: 1; } .details-panel { position: absolute; bottom: -300px; width: 500px; height: 250px; background: white; border-radius: 20px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); padding: 25px; transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); opacity: 0; z-index: 5; display: flex; flex-direction: column; } .details-panel.active { bottom: 50px; opacity: 1; } .details-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .details-day { font-size: 24px; font-weight: 700; } .close-btn { background: none; border: none; font-size: 20px; cursor: pointer; color: #7f8c8d; transition: color 0.3s ease; } .close-btn:hover { color: #2c3e50; } .details-content { display: flex; flex-direction: row; justify-content: space-between; flex: 1; } .details-main { display: flex; flex-direction: column; justify-content: center; align-items: center; } .details-temp { font-size: 60px; font-weight: 700; line-height: 1; margin-bottom: 5px; } .details-condition { font-size: 18px; color: #7f8c8d; } .details-metrics { display: flex; flex-direction: column; justify-content: center; gap: 15px; margin-right: 20px; } .metric { display: flex; align-items: center; gap: 10px; } .metric-icon { width: 20px; color: #7f8c8d; } .metric-value { font-weight: 600; } .hourly-forecast { display: flex; justify-content: space-between; margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; width: 100%; } .hour-item { display: flex; flex-direction: column; align-items: center; width: 60px; } .hour-time { font-size: 14px; color: #7f8c8d; margin-bottom: 5px; } .hour-temp { font-weight: 600; } .city-info { text-align: center; margin-bottom: 35px; } .city-name { font-size: 32px; font-weight: 700; margin-bottom: 5px; } .current-date { color: #7f8c8d; font-size: 16px; } .raindrop { position: absolute; width: 3px; height: 20px; background: rgba(255, 255, 255, 0.7); border-radius: 50px; animation: rain 1s linear infinite; opacity: 0; } @keyframes rain { 0% { transform: translateY(0) scale(1); opacity: 0.7; } 100% { transform: translateY(60px) scale(0.5); opacity: 0; } } .snowflake { position: absolute; font-size: 10px; color: white; animation: snow 2s linear infinite; opacity: 0; } @keyframes snow { 0% { transform: translateY(0) rotate(0deg); opacity: 0.7; } 100% { transform: translateY(60px) rotate(360deg); opacity: 0; } } .sunny-ray { position: absolute; width: 100%; height: 100%; top: 0; left: 0; background: radial-gradient(circle at center, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 70%); border-radius: 50%; animation: pulse 2s ease-in-out infinite; z-index: 1; opacity: 0; } @keyframes pulse { 0%, 100% { transform: scale(0.9); opacity: 0.3; } 50% { transform: scale(1.1); opacity: 0.5; } } /* Mobile Responsiveness */ @media (max-width: 700px) { .container { padding: 15px; height: auto; } .bubbles-container { gap: 10px; } .weather-bubble { width: 110px; height: 110px; } .details-panel { width: 90%; height: auto; padding: 15px; } .details-content { flex-direction: column; gap: 20px; } .details-metrics { flex-direction: row; flex-wrap: wrap; justify-content: space-between; margin-right: 0; } .metric { width: 45%; } .hourly-forecast { overflow-x: auto; justify-content: flex-start; gap: 15px; padding-bottom: 10px; } .hour-item { min-width: 60px; } } </style> </head> <body> <div class="container"> <div class="city-info"> <h1 class="city-name">San Francisco</h1> <p class="current-date">Wednesday, August 9</p> </div> <div class="search-container"> <input type="text" class="search-bar" placeholder="Search for a city..."> <div class="search-icon">๐</div> </div> <div class="bubbles-container"> <!-- Weather bubbles will be dynamically inserted here --> </div> <div class="details-panel"> <div class="details-header"> <div class="details-day">Thursday</div> <button class="close-btn">โ</button> </div> <div class="details-content"> <div class="details-main"> <div class="details-temp">72ยฐ</div> <div class="details-condition">Partly Cloudy</div> </div> <div class="details-metrics"> <div class="metric"> <div class="metric-icon">๐ง</div> <div class="metric-value">30% Humidity</div> </div> <div class="metric"> <div class="metric-icon">๐จ</div> <div class="metric-value">12 mph Wind</div> </div> <div class="metric"> <div class="metric-icon">โ๏ธ</div> <div class="metric-value">10% Chance</div> </div> <div class="metric"> <div class="metric-icon">๐ก๏ธ</div> <div class="metric-value">68ยฐ / 75ยฐ</div> </div> </div> </div> <div class="hourly-forecast"> <div class="hour-item"> <div class="hour-time">9 AM</div> <div class="hour-temp">68ยฐ</div> </div> <div class="hour-item"> <div class="hour-time">12 PM</div> <div class="hour-temp">72ยฐ</div> </div> <div class="hour-item"> <div class="hour-time">3 PM</div> <div class="hour-temp">75ยฐ</div> </div> <div class="hour-item"> <div class="hour-time">6 PM</div> <div class="hour-temp">73ยฐ</div> </div> <div class="hour-item"> <div class="hour-time">9 PM</div> <div class="hour-temp">70ยฐ</div> </div> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Weather data for the week const weatherData = [ { day: 'Today', temp: 72, condition: 'Partly Cloudy', icon: 'โ ', gradient: 'linear-gradient(135deg, #83a4d4, #b6fbff)', delay: 0, humidity: 32, wind: 10, precipitation: 8, low: 68, high: 75, hourly: [ { time: '9 AM', temp: 68 }, { time: '12 PM', temp: 72 }, { time: '3 PM', temp: 75 }, { time: '6 PM', temp: 73 }, { time: '9 PM', temp: 70 } ] }, { day: 'Thu', temp: 68, condition: 'Rainy', icon: '๐ง๏ธ', gradient: 'linear-gradient(135deg, #5C6BC0, #7986CB)', delay: 0.1, humidity: 78, wind: 12, precipitation: 80, low: 65, high: 70, hourly: [ { time: '9 AM', temp: 65 }, { time: '12 PM', temp: 67 }, { time: '3 PM', temp: 70 }, { time: '6 PM', temp: 68 }, { time: '9 PM', temp: 66 } ] }, { day: 'Fri', temp: 64, condition: 'Thunderstorms', icon: 'โ๏ธ', gradient: 'linear-gradient(135deg, #4527A0, #7E57C2)', delay: 0.2, humidity: 85, wind: 18, precipitation: 90, low: 62, high: 67, hourly: [ { time: '9 AM', temp: 62 }, { time: '12 PM', temp: 64 }, { time: '3 PM', temp: 67 }, { time: '6 PM', temp: 66 }, { time: '9 PM', temp: 63 } ] }, { day: 'Sat', temp: 61, condition: 'Heavy Rain', icon: '๐ง๏ธ', gradient: 'linear-gradient(135deg, #1A237E, #3949AB)', delay: 0.3, humidity: 90, wind: 22, precipitation: 100, low: 59, high: 63, hourly: [ { time: '9 AM', temp: 59 }, { time: '12 PM', temp: 61 }, { time: '3 PM', temp: 63 }, { time: '6 PM', temp: 62 }, { time: '9 PM', temp: 60 } ] }, { day: 'Sun', temp: 65, condition: 'Cloudy', icon: 'โ๏ธ', gradient: 'linear-gradient(135deg, #78909C, #B0BEC5)', delay: 0.4, humidity: 65, wind: 15, precipitation: 30, low: 61, high: 68, hourly: [ { time: '9 AM', temp: 61 }, { time: '12 PM', temp: 64 }, { time: '3 PM', temp: 68 }, { time: '6 PM', temp: 67 }, { time: '9 PM', temp: 63 } ] }, { day: 'Mon', temp: 70, condition: 'Sunny', icon: 'โ๏ธ', gradient: 'linear-gradient(135deg, #FF8008, #FFC837)', delay: 0.5, humidity: 40, wind: 8, precipitation: 0, low: 65, high: 73, hourly: [ { time: '9 AM', temp: 65 }, { time: '12 PM', temp: 68 }, { time: '3 PM', temp: 73 }, { time: '6 PM', temp: 72 }, { time: '9 PM', temp: 68 } ] }, { day: 'Tue', temp: 72, condition: 'Partly Sunny', icon: '๐ค๏ธ', gradient: 'linear-gradient(135deg, #FFB75E, #ED8F03)', delay: 0.6, humidity: 45, wind: 10, precipitation: 10, low: 66, high: 74, hourly: [ { time: '9 AM', temp: 66 }, { time: '12 PM', temp: 70 }, { time: '3 PM', temp: 74 }, { time: '6 PM', temp: 73 }, { time: '9 PM', temp: 69 } ] } ]; const bubblesContainer = document.querySelector('.bubbles-container'); const detailsPanel = document.querySelector('.details-panel'); const closeBtn = document.querySelector('.close-btn'); // Create weather bubbles weatherData.forEach((day, index) => { const bubble = document.createElement('div'); bubble.className = 'weather-bubble'; bubble.style.animationDelay = `${day.delay}s`; const bubbleBg = document.createElement('div'); bubbleBg.className = 'bubble-bg'; bubbleBg.style.background = day.gradient; const content = document.createElement('div'); content.className = 'bubble-content'; content.innerHTML = ` <div class="day">${day.day}</div> <div class="weather-icon">${day.icon}</div> <div class="temp">${day.temp}ยฐ</div> <div class="condition">${day.condition}</div> `; bubble.appendChild(bubbleBg); bubble.appendChild(content); // Add special weather effects if (day.condition.includes('Rain') || day.condition.includes('Thunderstorm')) { createRaindrops(bubble, 10); } else if (day.condition.includes('Snow')) { createSnowflakes(bubble, 10); } else if (day.condition.includes('Sunny')) { createSunnyEffect(bubble); } // Add click event to show details bubble.addEventListener('click', () => { showDetails(day); }); bubblesContainer.appendChild(bubble); }); // Close details panel closeBtn.addEventListener('click', () => { detailsPanel.classList.remove('active'); }); // Search functionality const searchBar = document.querySelector('.search-bar'); const searchIcon = document.querySelector('.search-icon'); function handleSearch() { const searchTerm = searchBar.value.trim(); if (searchTerm !== '') { document.querySelector('.city-name').textContent = searchTerm; searchBar.value = ''; // Simulate loading new data bubblesContainer.innerHTML = ''; setTimeout(() => { // Recreate bubbles with a slight delay to create a nice effect weatherData.forEach((day, index) => { setTimeout(() => { const bubble = document.createElement('div'); bubble.className = 'weather-bubble'; const bubbleBg = document.createElement('div'); bubbleBg.className = 'bubble-bg'; bubbleBg.style.background = day.gradient; const content = document.createElement('div'); content.className = 'bubble-content'; content.innerHTML = ` <div class="day">${day.day}</div> <div class="weather-icon">${day.icon}</div> <div class="temp">${day.temp}ยฐ</div> <div class="condition">${day.condition}</div> `; bubble.appendChild(bubbleBg); bubble.appendChild(content); if (day.condition.includes('Rain') || day.condition.includes('Thunderstorm')) { createRaindrops(bubble, 10); } else if (day.condition.includes('Snow')) { createSnowflakes(bubble, 10); } else if (day.condition.includes('Sunny')) { createSunnyEffect(bubble); } bubble.addEventListener('click', () => { showDetails(day); }); bubblesContainer.appendChild(bubble); }, index * 100); }); }, 500); } } searchIcon.addEventListener('click', handleSearch); searchBar.addEventListener('keypress', (e) => { if (e.key === 'Enter') { handleSearch(); } }); // Show weather details function showDetails(day) { const detailsDay = document.querySelector('.details-day'); const detailsTemp = document.querySelector('.details-temp'); const detailsCondition = document.querySelector('.details-condition'); const metricsContainer = document.querySelector('.details-metrics'); const hourlyContainer = document.querySelector('.hourly-forecast'); detailsDay.textContent = day.day; detailsTemp.textContent = `${day.temp}ยฐ`; detailsCondition.textContent = day.condition; // Update metrics metricsContainer.innerHTML = ` <div class="metric"> <div class="metric-icon">๐ง</div> <div class="metric-value">${day.humidity}% Humidity</div> </div> <div class="metric"> <div class="metric-icon">๐จ</div> <div class="metric-value">${day.wind} mph Wind</div> </div> <div class="metric"> <div class="metric-icon">โ๏ธ</div> <div class="metric-value">${day.precipitation}% Chance</div> </div> <div class="metric"> <div class="metric-icon">๐ก๏ธ</div> <div class="metric-value">${day.low}ยฐ / ${day.high}ยฐ</div> </div> `; // Update hourly forecast hourlyContainer.innerHTML = ''; day.hourly.forEach(hour => { const hourItem = document.createElement('div'); hourItem.className = 'hour-item'; hourItem.innerHTML = ` <div class="hour-time">${hour.time}</div> <div class="hour-temp">${hour.temp}ยฐ</div> `; hourlyContainer.appendChild(hourItem); }); detailsPanel.classList.add('active'); } // Create raindrops effect function createRaindrops(element, count) { for (let i = 0; i < count; i++) { const raindrop = document.createElement('div'); raindrop.className = 'raindrop'; // Random position within the bubble const left = Math.random() * 100; const top = Math.random() * 40; raindrop.style.left = `${left}%`; raindrop.style.top = `${top}%`; raindrop.style.animationDelay = `${Math.random() * 2}s`; raindrop.style.opacity = Math.random() * 0.7; element.appendChild(raindrop); } } // Create snowflakes effect function createSnowflakes(element, count) { for (let i = 0; i < count; i++) { const snowflake = document.createElement('div'); snowflake.className = 'snowflake'; snowflake.textContent = 'โ'; // Random position within the bubble const left = Math.random() * 100; const top = Math.random() * 40; snowflake.style.left = `${left}%`; snowflake.style.top = `${top}%`; snowflake.style.animationDelay = `${Math.random() * 2}s`; snowflake.style.opacity = Math.random() * 0.7; element.appendChild(snowflake); } } // Create sunny effect function createSunnyEffect(element) { const sunnyRay = document.createElement('div'); sunnyRay.className = 'sunny-ray'; sunnyRay.style.opacity = 0.5; element.appendChild(sunnyRay); } // Update date const today = new Date(); const options = { weekday: 'long', month: 'long', day: 'numeric' }; document.querySelector('.current-date').textContent = today.toLocaleDateString('en-US', options); }); </script> </body> </html>