Created
January 2, 2025 15:23
-
-
Save aldodelgado/17c453cd3b9650ddd451b951d4455530 to your computer and use it in GitHub Desktop.
Bubbles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| document.addEventListener('DOMContentLoaded', () => { | |
| const loadingElement = document.getElementById('loading'); | |
| const containerElement = document.getElementById('container'); | |
| const bubbles = []; | |
| const initializeBackground = () => { | |
| let backgroundElement = document.querySelector('.background'); | |
| if (!backgroundElement) { | |
| backgroundElement = document.createElement('div'); | |
| backgroundElement.className = 'background'; | |
| document.body.appendChild(backgroundElement); | |
| } | |
| }; | |
| const fetchAndDisplayBubbles = async () => { | |
| try { | |
| const response = await fetch('https://adelgado.ngrok.io/api/tweet-data', { | |
| method: 'GET', | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| const data = await response.json(); | |
| if (!Array.isArray(data)) { | |
| throw new Error('Invalid data format'); | |
| } | |
| console.log('Received data:', data); | |
| const interactionBubbles = containerElement.querySelector('.interaction-bubbles'); | |
| data.forEach((user) => { | |
| // Check if bubble for user already exists | |
| const existingBubble = bubbles.find(b => b.userId === user.periscope_user_id); | |
| if (existingBubble) return; | |
| const bubble = document.createElement('div'); | |
| bubble.className = 'bubble'; | |
| const img = document.createElement('img'); | |
| img.src = user.avatar_url || 'https://via.placeholder.com/50'; | |
| img.alt = `${user.display_name}'s profile picture`; | |
| // Add a small indicator for the role | |
| const roleIndicator = document.createElement('div'); | |
| roleIndicator.className = 'role-indicator'; | |
| // Determine role and assign emoji | |
| let roleEmoji = ''; | |
| if (user.role === 'admin') { | |
| roleEmoji = 'π'; // Admin emoji | |
| } else if (user.role === 'speaker') { | |
| roleEmoji = 'π€'; // Speaker emoji | |
| } else if (user.role === 'listener') { | |
| roleEmoji = 'π'; // Listener emoji | |
| } | |
| roleIndicator.textContent = roleEmoji; // Assign the emoji | |
| bubble.appendChild(img); | |
| bubble.appendChild(roleIndicator); | |
| interactionBubbles.appendChild(bubble); | |
| let x = Math.random() * (window.innerWidth - 80); | |
| let y = Math.random() * (window.innerHeight - 80); | |
| bubble.style.left = `${x}px`; | |
| bubble.style.top = `${y}px`; | |
| bubbles.push({ | |
| userId: user.periscope_user_id, // Track user ID to prevent duplicates | |
| element: bubble, | |
| dx: (Math.random() - 0.5) * 4, | |
| dy: (Math.random() - 0.5) * 4 | |
| }); | |
| }); | |
| loadingElement.style.display = 'none'; | |
| containerElement.style.display = 'flex'; | |
| } catch (error) { | |
| console.error('Error fetching bubble data:', error); | |
| loadingElement.textContent = 'Failed to load data. Please try again later.'; | |
| } | |
| }; | |
| const animateBubbles = () => { | |
| bubbles.forEach((bubble, index) => { | |
| const rect = bubble.element.getBoundingClientRect(); | |
| // Update position | |
| let newX = rect.left + bubble.dx; | |
| let newY = rect.top + bubble.dy; | |
| // Bounce off screen edges | |
| if (newX <= 0 || newX + rect.width >= window.innerWidth) { | |
| bubble.dx *= -1; // Reverse horizontal direction | |
| newX = Math.max(0, Math.min(newX, window.innerWidth - rect.width)); | |
| } | |
| if (newY <= 0 || newY + rect.height >= window.innerHeight) { | |
| bubble.dy *= -1; // Reverse vertical direction | |
| newY = Math.max(0, Math.min(newY, window.innerHeight - rect.height)); | |
| } | |
| // Check collisions with other bubbles | |
| bubbles.forEach((otherBubble, otherIndex) => { | |
| if (index === otherIndex) return; // Skip self-comparison | |
| const otherRect = otherBubble.element.getBoundingClientRect(); | |
| const dx = rect.left - otherRect.left; | |
| const dy = rect.top - otherRect.top; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance < rect.width) { | |
| // Bubbles are colliding, resolve collision | |
| const nx = dx / distance; // Normal vector (x) | |
| const ny = dy / distance; // Normal vector (y) | |
| const p = 2 * (bubble.dx * nx + bubble.dy * ny - otherBubble.dx * nx - otherBubble.dy * ny) / 2; // Momentum exchange factor | |
| // Update velocities for both bubbles | |
| bubble.dx -= p * nx; | |
| bubble.dy -= p * ny; | |
| otherBubble.dx += p * nx; | |
| otherBubble.dy += p * ny; | |
| // Add collision effect (optional) | |
| bubble.element.classList.add('colliding'); | |
| otherBubble.element.classList.add('colliding'); | |
| setTimeout(() => { | |
| bubble.element.classList.remove('colliding'); | |
| otherBubble.element.classList.remove('colliding'); | |
| }, 200); | |
| // Adjust positions to prevent overlap | |
| const overlap = rect.width - distance; | |
| const correctionX = (overlap / 2) * nx; | |
| const correctionY = (overlap / 2) * ny; | |
| newX += correctionX; | |
| newY += correctionY; | |
| const otherX = otherRect.left - correctionX; | |
| const otherY = otherRect.top - correctionY; | |
| otherBubble.element.style.left = `${otherX}px`; | |
| otherBubble.element.style.top = `${otherY}px`; | |
| } | |
| }); | |
| // Update bubble position | |
| bubble.element.style.left = `${newX}px`; | |
| bubble.element.style.top = `${newY}px`; | |
| }); | |
| requestAnimationFrame(animateBubbles); | |
| }; | |
| initializeBackground(); | |
| fetchAndDisplayBubbles(); | |
| animateBubbles(); | |
| setInterval(fetchAndDisplayBubbles, 60000); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment