That works with the site Sound Factory NYC
Memories
💸 MONEY 💸
1.5x
2.5x
40
1.5x
1.0x
5
1.0x
0.5
en('https://soundfactorynyc.com/tickets', '_blank'); showToast('🎟️ Opening ticket purchase...'); } function buyTables() { window.open('https://seance.soundfactorynyc.com/tables', '_blank'); showToast('🍾 Opening table reservation...'); } // Chat function function sendBottomMessage() { const input = document.getElementById('chatInputBottom'); if (!input || !input.value.trim()) return; const message = input.value.trim(); showToast('💬 Message sent!'); input.value = ''; } // Toast notification system function showToast(message) { const existing = document.querySelector('.toast'); if (existing) existing.remove(); const toast = document.createElement('div'); toast.className = 'toast'; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => toast.remove(), 3000); } // Welcome message - REMOVED // ======================================== // MULTIPLAYER SYSTEM - INTERACTIVE FEATURES // ======================================== const MultiplayerSystem = { users: new Map(), myUserId: null, localStream: null, cameraActive: false, clubEnergy: 0, myScore: 0, trail: [], collectibles: [], livePins: new Map(), init() { this.myUserId = localStorage.getItem('sf_user_id') || 'user_' + Date.now(); localStorage.setItem('sf_user_id', this.myUserId); // Create some demo users this.createDemoUsers(); // Check for collisions every frame this.startCollisionDetection(); // Initialize new features this.initEnergyMeter(); this.initMemoryTrail(); this.spawnCollectibles(); this.initGoLiveButton(); this.checkCollectiblePickup(); }, // GO LIVE PIN - Drop a live pin at parties for meetups! initGoLiveButton() { const btn = document.createElement('button'); btn.innerHTML = '📍 GO LIVE'; btn.style.cssText = ` position: fixed; top: 70px; left: 50%; transform: translateX(-50%); padding: 12px 30px; background: linear-gradient(135deg, #ff0066, #ff3399); border: none; border-radius: 25px; color: white; font-weight: 800; font-size: 16px; cursor: pointer; z-index: 2000; box-shadow: 0 4px 20px rgba(255,0,102,0.5); transition: all 0.3s; text-transform: uppercase; letter-spacing: 2px; animation: pulse 2s infinite; `; btn.addEventListener('click', () => this.goLive()); btn.addEventListener('mouseenter', function() { this.style.transform = 'translateX(-50%) scale(1.1)'; this.style.boxShadow = '0 6px 30px rgba(255,0,102,0.8)'; }); btn.addEventListener('mouseleave', function() { this.style.transform = 'translateX(-50%) scale(1)'; this.style.boxShadow = '0 4px 20px rgba(255,0,102,0.5)'; }); document.body.appendChild(btn); // Add pulse animation const style = document.createElement('style'); style.textContent = ` @keyframes pulse { 0%, 100% { box-shadow: 0 4px 20px rgba(255,0,102,0.5); } 50% { box-shadow: 0 4px 30px rgba(255,0,102,0.9), 0 0 40px rgba(255,0,102,0.6); } } `; document.head.appendChild(style); }, goLive() { const myChar = document.getElementById('myCharacter'); if (!myChar) return; const x = (parseFloat(myChar.style.left) / window.innerWidth) * 100; const y = (parseFloat(myChar.style.top) / window.innerHeight) * 100; const livePin = document.createElement('div'); livePin.className = 'live-pin'; livePin.style.cssText = ` position: fixed; left: ${x}%; top: ${y}%; z-index: 1000; transform: translate(-50%, -100%); animation: pinDrop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); `; livePin.innerHTML = `
📍
🔴 LIVE
I'M HERE! 🎉
Meet me at this spot!
`; document.querySelector('.blueprint-view').appendChild(livePin); // Store live pin this.livePins.set(this.myUserId, { x, y, timestamp: Date.now(), element: livePin }); showToast('📍 YOU ARE NOW LIVE! People can find you!'); // Remove after 5 minutes setTimeout(() => { livePin.remove(); this.livePins.delete(this.myUserId); showToast('📍 Live pin expired'); }, 5 * 60 * 1000); // Add animations const style = document.createElement('style'); style.textContent = ` @keyframes pinDrop { 0% { transform: translate(-50%, -200%) scale(0); opacity: 0; } 60% { transform: translate(-50%, -90%) scale(1.2); } 100% { transform: translate(-50%, -100%) scale(1); opacity: 1; } } @keyframes livePulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.2); } } @keyframes liveBadge { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } `; document.head.appendChild(style); }, // ENERGY METER - Club vibes increase with activity! initEnergyMeter() { const energyBar = document.createElement('div'); energyBar.id = 'clubEnergyMeter'; energyBar.style.cssText = 'position: fixed; top: 120px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.95); padding: 12px 25px; border-radius: 20px; border: 2px solid rgba(255,0,110,0.4); z-index: 2000; backdrop-filter: blur(20px);'; energyBar.innerHTML = `
CLUB ENERGY
🔥
🎉
WARMING UP
`; document.body.appendChild(energyBar); // Add close button functionality const closeBtn = document.getElementById('closeEnergyBtn'); if (closeBtn) { closeBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); const meter = document.getElementById('clubEnergyMeter'); if (meter) { meter.style.display = 'none'; console.log('✅ Energy meter closed'); } }); closeBtn.addEventListener('mouseover', function() { this.style.background = 'rgba(255,0,0,0.6)'; this.style.transform = 'scale(1.1)'; }); closeBtn.addEventListener('mouseout', function() { this.style.background = 'rgba(255,0,0,0.3)'; this.style.transform = 'scale(1)'; }); } document.body.appendChild(energyBar); // Update energy meter setInterval(() => this.updateEnergy(), 100); }, updateEnergy() { // Energy increases with movement and collisions const myChar = document.getElementById('myCharacter'); if (!myChar) return; const myX = parseFloat(myChar.style.left) || 0; const myY = parseFloat(myChar.style.top) || 0; // Count nearby users let nearbyCount = 0; this.users.forEach(user => { const distance = Math.sqrt(Math.pow(user.x - myX, 2) + Math.pow(user.y - myY, 2)); if (distance < 200) nearbyCount++; }); // Increase energy when moving and near others if (Math.abs(joystickX) > 0.1 || Math.abs(joystickY) > 0.1) { this.clubEnergy += (1 + nearbyCount) * 0.3; } // Natural decay this.clubEnergy = Math.max(0, this.clubEnergy * 0.995); this.clubEnergy = Math.min(100, this.clubEnergy); const fill = document.getElementById('energyFill'); const text = document.getElementById('energyText'); if (fill) fill.style.width = this.clubEnergy + '%'; if (text) { if (this.clubEnergy < 15) text.textContent = 'WARMING UP'; else if (this.clubEnergy < 35) text.textContent = 'GETTING LIT 🔥'; else if (this.clubEnergy < 55) text.textContent = 'ON FIRE!! 🔥🔥'; else if (this.clubEnergy < 75) text.textContent = 'INSANE!!! 🤯'; else text.textContent = 'LEGENDARY!!!! 🚀🚀'; } }, // MEMORY TRAIL - Leave glowing path initMemoryTrail() { setInterval(() => { const myChar = document.getElementById('myCharacter'); if (!myChar) return; const x = parseFloat(myChar.style.left) || 0; const y = parseFloat(myChar.style.top) || 0; // Only add trail if moving if (Math.abs(joystickX) > 0.1 || Math.abs(joystickY) > 0.1) { this.trail.push({ x, y, time: Date.now() }); } // Keep only last 40 points if (this.trail.length > 40) this.trail.shift(); // Remove old points (older than 8 seconds) this.trail = this.trail.filter(p => Date.now() - p.time < 8000); // Draw trail this.drawTrail(); }, 150); }, drawTrail() { // Remove old trail document.querySelectorAll('.trail-point').forEach(el => el.remove()); this.trail.forEach((point, i) => { const age = Date.now() - point.time; const opacity = 1 - (age / 8000); const size = 10 - (age / 8000) * 8; const dot = document.createElement('div'); dot.className = 'trail-point'; dot.style.cssText = ` position: fixed; left: ${point.x}px; top: ${point.y}px; width: ${size}px; height: ${size}px; background: radial-gradient(circle, rgba(255,0,110,${opacity * 0.8}), rgba(138,43,226,${opacity * 0.4}), transparent); border-radius: 50%; pointer-events: none; z-index: 5; box-shadow: 0 0 ${size * 3}px rgba(255,0,110,${opacity * 0.6}); `; document.querySelector('.blueprint-view').appendChild(dot); }); }, // COLLECTIBLES - Power-ups scattered around! spawnCollectibles() { setInterval(() => { if (this.collectibles.length < 6) { const types = [ { emoji: '⭐', name: 'Star', points: 50, color: '#FFD700' }, { emoji: '🌟', name: 'Sparkle', points: 25, color: '#00FFFF' }, { emoji: '⚡', name: 'Lightning', points: 75, color: '#FFFF00' }, { emoji: '💎', name: 'Diamond', points: 100, color: '#00FFFF' }, { emoji: '🎆', name: 'Firework', points: 150, color: '#FF00FF' }, { emoji: '🌈', name: 'Rainbow', points: 200, color: '#FF0066' } ]; const type = types[Math.floor(Math.random() * types.length)]; const x = Math.random() * (window.innerWidth - 150) + 75; const y = Math.random() * (window.innerHeight - 300) + 120; const collectible = { type, x, y, element: this.createCollectible(type, x, y) }; this.collectibles.push(collectible); } }, 8000); // Spawn every 8 seconds }, createCollectible(type, x, y) { const el = document.createElement('div'); el.className = 'collectible'; el.style.cssText = ` position: fixed; left: ${x}px; top: ${y}px; font-size: 30px; z-index: 100; cursor: pointer; animation: float 3s ease-in-out infinite, collectibleGlow 1.5s ease-in-out infinite; filter: drop-shadow(0 0 15px ${type.color}); transition: all 0.3s; `; el.textContent = type.emoji; document.querySelector('.blueprint-view').appendChild(el); // Add float animation const style = document.createElement('style'); style.textContent = ` @keyframes float { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-15px); } } @keyframes collectibleGlow { 0%, 100% { filter: drop-shadow(0 0 10px ${type.color}); } 50% { filter: drop-shadow(0 0 25px ${type.color}); } } `; document.head.appendChild(style); return el; }, checkCollectiblePickup() { setInterval(() => { const myChar = document.getElementById('myCharacter'); if (!myChar) return; const myX = parseFloat(myChar.style.left) || 0; const myY = parseFloat(myChar.style.top) || 0; this.collectibles.forEach((col, index) => { const distance = Math.sqrt(Math.pow(col.x - myX, 2) + Math.pow(col.y - myY, 2)); if (distance < 40) { // Picked up! this.pickupCollectible(col, index); } }); }, 100); }, pickupCollectible(collectible, index) { // Remove from array and DOM this.collectibles.splice(index, 1); // Animate pickup collectible.element.style.animation = 'none'; collectible.element.style.transition = 'all 0.5s'; collectible.element.style.transform = 'scale(2) rotate(360deg)'; collectible.element.style.opacity = '0'; setTimeout(() => collectible.element.remove(), 500); // Add points this.myScore += collectible.type.points; // Boost energy this.clubEnergy = Math.min(100, this.clubEnergy + 15); // Show pickup notification showToast(`${collectible.type.emoji} +${collectible.type.points} points! Score: ${this.myScore}`); // Trigger effect this.triggerCollectibleEffect(collectible.type); }, triggerCollectibleEffect(type) { const myChar = document.getElementById('myCharacter'); if (!myChar) return; // Add temporary glow effect myChar.style.filter = `drop-shadow(0 0 20px ${type.color})`; setTimeout(() => myChar.style.filter = 'none', 2000); // Particle burst for (let i = 0; i < 12; i++) { const particle = document.createElement('div'); particle.textContent = type.emoji; particle.style.cssText = ` position: fixed; left: ${parseFloat(myChar.style.left)}px; top: ${parseFloat(myChar.style.top)}px; font-size: 20px; z-index: 999; pointer-events: none; animation: particleBurst 1s ease-out forwards; `; const angle = (Math.PI * 2 * i) / 12; particle.style.setProperty('--vx', Math.cos(angle) * 100 + 'px'); particle.style.setProperty('--vy', Math.sin(angle) * 100 + 'px'); document.body.appendChild(particle); setTimeout(() => particle.remove(), 1000); } // Add animation const style = document.createElement('style'); style.textContent = ` @keyframes particleBurst { 0% { transform: translate(0, 0) scale(1); opacity: 1; } 100% { transform: translate(var(--vx), var(--vy)) scale(0); opacity: 0; } } `; document.head.appendChild(style); }, init() { this.myUserId = localStorage.getItem('sf_user_id') || 'user_' + Date.now(); localStorage.setItem('sf_user_id', this.myUserId); // Create some demo users this.createDemoUsers(); // Check for collisions every frame this.startCollisionDetection(); }, createDemoUsers() { // REMOVED - No fake demo users // Real users will come from Supabase only }, createUserElement(userId, x, y, colorClass) { const person = document.createElement('div'); person.className = `person other-user ${colorClass}`; person.style.left = x + 'px'; person.style.top = y + 'px'; person.innerHTML = `
`; person.addEventListener('click', () => this.startChat(userId)); document.querySelector('.blueprint-view').appendChild(person); return person; }, animateUsers() { // REMOVED - No fake users to animate // Real Supabase users will be animated by their own presence data }, startCollisionDetection() { setInterval(() => { const myChar = document.getElementById('myCharacter'); if (!myChar) return; const myRect = myChar.getBoundingClientRect(); const myX = parseFloat(myChar.style.left) || 0; const myY = parseFloat(myChar.style.top) || 0; this.users.forEach(user => { const userRect = user.element.getBoundingClientRect(); const distance = Math.sqrt( Math.pow(user.x - myX, 2) + Math.pow(user.y - myY, 2) ); // If within 50px, trigger chat if (distance < 50) { this.onCollision(user); } }); }, 100); }, onCollision(user) { // NO AUTO-POPUP - User must CLICK to open chat // Just show toast notification if (user.chatOpened) return; user.chatOpened = true; showToast(`💬 ${user.name} is nearby! Click them to chat!`); // Reset after 30 seconds setTimeout(() => user.chatOpened = false, 30000); }, showGroupChat(users) { const blueprint = document.querySelector('.blueprint-view'); const popup = document.createElement('div'); popup.className = 'popup-container'; popup.style.left = '50%'; popup.style.top = '50%'; popup.style.transform = 'translate(-50%, -50%)'; const userNames = users.map(u => u.name).join(', '); popup.innerHTML = `
${users.map(u => `
👤
`).join('')}
${userNames}
Connected
${users[0].name}: Hey! Welcome to Sound Factory! 🎵
`; blueprint.appendChild(popup); }, sendChatMessage(userId) { const input = document.getElementById(`chatInput-${userId}`); const messages = document.getElementById(`chatMessages-${userId}`); if (!input || !messages || !input.value.trim()) return; const message = document.createElement('div'); message.className = 'chat-message me'; message.innerHTML = `You: ${input.value}`; messages.appendChild(message); messages.scrollTop = messages.scrollHeight; input.value = ''; // Simulate response setTimeout(() => { const user = this.users.get(userId); const responses = [ 'That\'s awesome! 🎉', 'Love it! 💕', 'Totally agree! 👍', 'This place is amazing! ✨', 'Best night ever! 🔥' ]; const reply = document.createElement('div'); reply.className = 'chat-message'; reply.innerHTML = `${user?.name || 'User'}: ${responses[Math.floor(Math.random() * responses.length)]}`; messages.appendChild(reply); messages.scrollTop = messages.scrollHeight; }, 1000); }, async toggleCamera(button) { const userId = button.closest('.group-chat').querySelector('[id^="videoPreview-"]').id.split('-')[1]; const videoPreview = document.getElementById(`videoPreview-${userId}`); const video = document.getElementById(`localVideo-${userId}`); if (!this.cameraActive) { try { this.localStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: true }); video.srcObject = this.localStream; videoPreview.style.display = 'block'; button.textContent = '🎥 Turn Off Camera'; button.classList.add('active'); this.cameraActive = true; showToast('📹 Camera is on!'); } catch (err) { showToast('❌ Camera access denied'); console.error('Camera error:', err); } } else { if (this.localStream) { this.localStream.getTracks().forEach(track => track.stop()); this.localStream = null; } videoPreview.style.display = 'none'; button.textContent = '📹 Turn On Camera'; button.classList.remove('active'); this.cameraActive = false; showToast('📹 Camera is off'); } }, showPinDetail(pin) { const blueprint = document.querySelector('.blueprint-view'); const popup = document.createElement('div'); popup.className = 'popup-container'; popup.style.left = '50%'; popup.style.top = '50%'; popup.style.transform = 'translate(-50%, -50%)'; const pinData = { image: pin.image || 'https://via.placeholder.com/400x300/ff006e/ffffff?text=Memory', story: pin.story || 'An amazing night at Sound Factory!', user: pin.user || 'Raver #' + Math.floor(Math.random() * 1000), likes: pin.likes || Math.floor(Math.random() * 100), liked: false, comments: pin.comments || [ { user: 'DJ Shadow', text: 'Incredible vibes! 🔥' }, { user: 'Raver Jane', text: 'Best night ever! 💕' } ] }; popup.innerHTML = `
🎵
${pinData.user}
${new Date().toLocaleDateString()}
Memory

${pinData.story}

${pinData.comments.map(c => `
${c.user}: ${c.text}
`).join('')}
`; blueprint.appendChild(popup); }, toggleLike(button) { button.classList.toggle('liked'); const count = document.getElementById('likeCount'); const current = parseInt(count.textContent); count.textContent = button.classList.contains('liked') ? current + 1 : current - 1; showToast(button.classList.contains('liked') ? '❤️ Liked!' : '💔 Unliked'); }, sharePin() { showToast('📤 Pin shared!'); }, addComment() { const input = document.getElementById('commentInput'); const comments = document.getElementById('pinComments'); if (!input || !comments || !input.value.trim()) return; const comment = document.createElement('div'); comment.className = 'pin-comment'; comment.innerHTML = `You: ${input.value}`; comments.appendChild(comment); comments.scrollTop = comments.scrollHeight; input.value = ''; showToast('💬 Comment added!'); } }; // Initialize multiplayer system document.addEventListener('DOMContentLoaded', () => { MultiplayerSystem.init(); }); // ======================================== // CAMERA EFFECTS SYSTEM // ======================================== class CameraEffectsSystem { constructor() { this.stream = null; this.video = null; this.canvas = null; this.ctx = null; this.currentFilter = null; this.isCapturing = false; } async initialize() { try { this.stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } } }); return true; } catch (error) { console.error('Camera access denied:', error); return false; } } openCameraModal(effectName) { const modal = document.createElement('div'); modal.id = 'cameraEffectModal'; modal.style.cssText = ` position: fixed; inset: 0; background: rgba(0,0,0,0.98); z-index: 15000; display: flex; flex-direction: column; align-items: center; justify-content: center; `; modal.innerHTML = `
${effectName}
`; document.body.appendChild(modal); this.video = document.getElementById('cameraPreview'); this.canvas = document.getElementById('effectCanvas'); this.ctx = this.canvas.getContext('2d'); if (this.stream) { this.video.srcObject = this.stream; this.startEffectLoop(); } } startEffectLoop() { const loop = () => { if (!this.video || !this.canvas) return; this.canvas.width = this.video.videoWidth; this.canvas.height = this.video.videoHeight; if (this.currentFilter) { this.applyFilter(this.currentFilter); } if (this.isCapturing) { requestAnimationFrame(loop); } }; this.isCapturing = true; loop(); } applyFilter(filterName) { if (!this.ctx || !this.canvas) return; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); switch(filterName) { case 'glow': this.applyGlow(); break; case 'neon': this.applyNeon(); break; case 'vintage': this.applyVintage(); break; case 'smooth': this.applySmooth(); break; case 'sparkle': this.applySparkle(); break; case 'mirror': this.applyMirror(); break; case 'kaleidoscope': this.applyKaleidoscope(); break; case 'glitch': this.applyGlitch(); break; case 'thermal': this.applyThermal(); break; case 'cyberpunk': this.applyCyberpunk(); break; } } applyGlow() { this.ctx.shadowBlur = 20; this.ctx.shadowColor = '#ff0066'; this.ctx.fillStyle = 'rgba(255, 0, 102, 0.1)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } applyNeon() { this.ctx.strokeStyle = '#00ffff'; this.ctx.lineWidth = 3; this.ctx.shadowBlur = 15; this.ctx.shadowColor = '#00ffff'; this.ctx.strokeRect(10, 10, this.canvas.width - 20, this.canvas.height - 20); } applyVintage() { this.ctx.fillStyle = 'rgba(255, 200, 150, 0.15)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } applySmooth() { this.ctx.filter = 'blur(1px)'; this.ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height); this.ctx.filter = 'none'; } applySparkle() { for (let i = 0; i < 20; i++) { const x = Math.random() * this.canvas.width; const y = Math.random() * this.canvas.height; const size = Math.random() * 3 + 1; this.ctx.fillStyle = '#ffffff'; this.ctx.shadowBlur = 5; this.ctx.shadowColor = '#ffffff'; this.ctx.fillRect(x, y, size, size); } } applyGlitch() { const slices = 10; const sliceHeight = this.canvas.height / slices; for (let i = 0; i < slices; i++) { const offset = (Math.random() - 0.5) * 20; this.ctx.drawImage( this.video, 0, i * sliceHeight, this.canvas.width, sliceHeight, offset, i * sliceHeight, this.canvas.width, sliceHeight ); } } applyThermal() { this.ctx.fillStyle = 'rgba(255, 100, 0, 0.3)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } applyCyberpunk() { this.ctx.strokeStyle = '#ff00ff'; this.ctx.lineWidth = 1; this.ctx.shadowBlur = 10; this.ctx.shadowColor = '#ff00ff'; const gridSize = 30; for (let x = 0; x < this.canvas.width; x += gridSize) { this.ctx.beginPath(); this.ctx.moveTo(x, 0); this.ctx.lineTo(x, this.canvas.height); this.ctx.stroke(); } } async capture() { if (!this.video || !this.canvas) return; const captureCanvas = document.createElement('canvas'); captureCanvas.width = this.video.videoWidth; captureCanvas.height = this.video.videoHeight; const captureCtx = captureCanvas.getContext('2d'); captureCtx.save(); captureCtx.scale(-1, 1); captureCtx.drawImage(this.video, -captureCanvas.width, 0); captureCtx.restore(); const dataUrl = captureCanvas.toDataURL('image/jpeg', 0.9); this.showSaveOptions(dataUrl); } showSaveOptions(dataUrl) { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.98); border: 2px solid #00ff88; border-radius: 20px; padding: 25px; z-index: 16000; text-align: center; max-width: 90vw; `; modal.innerHTML = `
Save Your Shot
`; document.body.appendChild(modal); } downloadImage(dataUrl) { const link = document.createElement('a'); link.download = `soundfactory-${Date.now()}.jpg`; link.href = dataUrl; link.click(); showToast('Photo downloaded!'); this.closeAllModals(); } async switchCamera() { if (!this.stream) return; const tracks = this.stream.getVideoTracks(); const currentFacingMode = tracks[0].getSettings().facingMode; const newFacingMode = currentFacingMode === 'user' ? 'environment' : 'user'; this.stream.getTracks().forEach(track => track.stop()); try { this.stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: newFacingMode } }); if (this.video) this.video.srcObject = this.stream; } catch (error) { console.error('Could not switch camera:', error); } } closeCamera() { this.isCapturing = false; if (this.stream) { this.stream.getTracks().forEach(track => track.stop()); } this.closeAllModals(); } closeAllModals() { ['cameraEffectModal'].forEach(id => { const el = document.getElementById(id); if (el) el.remove(); }); document.querySelectorAll('[style*="z-index: 16000"]').forEach(el => el.remove()); } } // Create global camera effects instance window.cameraEffects = new CameraEffectsSystem(); // ======================================== // PIN OVERLAY SYSTEM // ======================================== const PinSystem = { isActive: false, mode: 'explore', pins: [], userPins: 0, maxPins: 4, init() { // Activate button document.getElementById('activatePinOverlay').addEventListener('click', () => this.activate()); // Close button document.getElementById('closePinOverlay').addEventListener('click', () => this.deactivate()); // Mode buttons document.getElementById('explorePinMode').addEventListener('click', () => this.setMode('explore')); document.getElementById('dropPinMode').addEventListener('click', () => this.setMode('drop')); // Canvas click for dropping pins document.getElementById('pinCanvas').addEventListener('click', (e) => { if (this.mode === 'drop' && this.userPins < this.maxPins) { this.dropPin(e); } }); // Create sample pins this.createSamplePins(); }, activate() { this.isActive = true; const overlay = document.getElementById('pinOverlay'); overlay.style.display = 'block'; overlay.style.pointerEvents = 'auto'; document.getElementById('activatePinOverlay').style.display = 'none'; }, deactivate() { this.isActive = false; document.getElementById('pinOverlay').style.display = 'none'; document.getElementById('activatePinOverlay').style.display = 'flex'; }, setMode(mode) { this.mode = mode; const exploreBtn = document.getElementById('explorePinMode'); const dropBtn = document.getElementById('dropPinMode'); if (mode === 'explore') { exploreBtn.style.background = 'linear-gradient(135deg, #ff006e, #8338ec)'; exploreBtn.style.color = '#fff'; dropBtn.style.background = 'transparent'; dropBtn.style.color = '#666'; document.getElementById('pinCanvas').style.cursor = 'pointer'; } else { dropBtn.style.background = 'linear-gradient(135deg, #ff006e, #8338ec)'; dropBtn.style.color = '#fff'; exploreBtn.style.background = 'transparent'; exploreBtn.style.color = '#666'; document.getElementById('pinCanvas').style.cursor = 'crosshair'; if (this.userPins >= this.maxPins) { showToast('You\'ve used all 4 pins!'); this.setMode('explore'); } } }, dropPin(e) { const rect = e.currentTarget.getBoundingClientRect(); const x = ((e.clientX - rect.left) / rect.width) * 100; const y = ((e.clientY - rect.top) / rect.height) * 100; const pin = document.createElement('div'); pin.className = 'memory-pin'; pin.style.cssText = ` position: absolute; left: ${x}%; top: ${y}%; width: 18px; height: 18px; border-radius: 50%; background: linear-gradient(135deg, #ff006e, #8338ec); border: 2px solid #000; cursor: pointer; box-shadow: 0 0 20px rgba(255, 0, 110, 0.6); z-index: 20; animation: pin-pop 0.3s cubic-bezier(0.4, 0, 0.2, 1); `; // Add pulsing effect const pulse = document.createElement('div'); pulse.style.cssText = ` position: absolute; top: 50%; left: 50%; width: 100%; height: 100%; border-radius: 50%; background: inherit; transform: translate(-50%, -50%); animation: pin-pulse 2s infinite; opacity: 0; `; pin.appendChild(pulse); // Add click listener pin.addEventListener('click', (e) => { e.stopPropagation(); if (this.mode === 'explore') { MultiplayerSystem.showPinDetail({ image: null, story: 'A memory from Sound Factory', user: 'You' }); } }); document.getElementById('pinCanvas').appendChild(pin); this.pins.push({ element: pin, x, y, user: 'me' }); this.userPins++; // Update pin counter const dots = document.querySelectorAll('.pin-dot'); if (dots[this.userPins - 1]) { dots[this.userPins - 1].style.background = 'linear-gradient(135deg, #ff006e, #8338ec)'; dots[this.userPins - 1].style.borderColor = '#ff006e'; dots[this.userPins - 1].style.boxShadow = '0 0 20px rgba(255, 0, 110, 0.6)'; } showToast('Memory dropped! ✨'); this.setMode('explore'); }, showPinPopup(pin, x, y) { // Remove existing popup const existingPopup = document.querySelector('.pin-popup'); if (existingPopup) existingPopup.remove(); const popup = document.createElement('div'); popup.className = 'pin-popup'; popup.style.cssText = ` position: absolute; left: ${x}%; top: ${y + 3}%; background: rgba(10, 10, 10, 0.95); backdrop-filter: blur(30px); border: 1px solid rgba(255, 0, 110, 0.2); border-radius: 20px; padding: 20px; min-width: 280px; max-width: 350px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8); z-index: 1000; color: #fff; animation: pin-pop 0.3s cubic-bezier(0.4, 0, 0.2, 1); `; popup.innerHTML = `
🎵
Raver #${Math.floor(Math.random() * 1000)}
Classic Era
"Best night ever! Junior Vasquez was on fire 🔥"
`; // Close on click outside setTimeout(() => { const closePopup = (e) => { if (!popup.contains(e.target) && !pin.contains(e.target)) { popup.remove(); document.removeEventListener('click', closePopup); } }; document.addEventListener('click', closePopup); }, 100); document.getElementById('pinCanvas').appendChild(popup); }, createSamplePins() { const samples = [ { x: 25, y: 30 }, { x: 50, y: 50 }, { x: 75, y: 35 }, { x: 40, y: 70 } ]; samples.forEach((pos, i) => { setTimeout(() => { const pin = document.createElement('div'); pin.style.cssText = ` position: absolute; left: ${pos.x}%; top: ${pos.y}%; width: 16px; height: 16px; border-radius: 50%; background: linear-gradient(135deg, #00ff88, #00cc66); border: 2px solid #000; cursor: pointer; box-shadow: 0 0 15px rgba(0, 255, 136, 0.4); z-index: 20; opacity: 0; animation: pin-fade-in 0.5s ease-out forwards; `; pin.addEventListener('click', (e) => { e.stopPropagation(); if (this.mode === 'explore') { MultiplayerSystem.showPinDetail({ image: 'https://via.placeholder.com/400x300/00ff88/ffffff?text=Classic+Memory', story: 'Remember when Junior Vasquez dropped that incredible set? The energy was INSANE! 🔥', user: 'Raver #' + Math.floor(Math.random() * 1000), likes: 42 + Math.floor(Math.random() * 100), comments: [ { user: 'Club Legend', text: 'I was there! Best night of my life! 💕' }, { user: 'Dance Queen', text: 'The music never stops! 🎵' } ] }); } }); document.getElementById('pinCanvas').appendChild(pin); }, i * 200); }); } }; // Add pin animations const pinStyles = document.createElement('style'); pinStyles.textContent = ` @keyframes pin-pop { 0% { transform: scale(0); opacity: 0; } 50% { transform: scale(1.2); } 100% { transform: scale(1); opacity: 1; } } @keyframes pin-pulse { 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.8; } 100% { transform: translate(-50%, -50%) scale(3); opacity: 0; } } @keyframes pin-fade-in { to { opacity: 1; } } `; document.head.appendChild(pinStyles); // Initialize pin system PinSystem.init(); // ======================================== // ENHANCED CHAT SYSTEM WITH SUPABASE // ======================================== const ChatSystem = { supabase: null, userId: null, init() { // Initialize Supabase (replace with your credentials) // this.supabase = window.supabase.createClient( // 'YOUR_SUPABASE_URL', // 'YOUR_SUPABASE_KEY' // ); // Generate user ID this.userId = localStorage.getItem('sf_user_id') || 'user_' + Date.now(); localStorage.setItem('sf_user_id', this.userId); // Hook into existing chat input const originalSend = window.sendBottomMessage; window.sendBottomMessage = () => { const input = document.getElementById('chatInputBottom'); if (input && input.value.trim()) { this.sendMessage(input.value.trim()); input.value = ''; } }; }, async sendMessage(text) { showToast('💬 ' + text); // If Supabase is configured, save to database // if (this.supabase) { // await this.supabase // .from('messages') // .insert({ // user_id: this.userId, // text: text, // timestamp: new Date().toISOString() // }); // } } }; // Initialize chat system ChatSystem.init(); // Enhanced Reaction System with Money Controls // Money-specific controls and intensity scaling let reactionSystem; class EnhancedReactionSystem { constructor() { this.canvas = document.getElementById('reaction-canvas'); this.ctx = this.canvas.getContext('2d'); this.reactions = []; this.particles = []; // Money-specific controls (only affect money) this.moneySpeed = 1.5; this.moneyExplosion = 2.5; this.moneyParticles = 40; this.moneySparkle = 1.5; this.resizeCanvas(); window.addEventListener('resize', () => this.resizeCanvas()); this.animate(); } resizeCanvas() { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; } triggerReaction(type, x, y, amount) { if (type === 'money') return this.createMoneyReaction(x, y, amount); if (type === 'heel') return this.createHeelReaction(x, y); if (type === 'pride') return this.createPrideReaction(x, y); if (type === 'jp') return this.createTextReaction(x, y, 'JP', '#ffd700'); if (type === 'sf') return this.createTextReaction(x, y, 'SF', '#00ffff'); } createMoneyReaction(x, y, amount) { const intensityMult = amount / 5; const speed = this.moneySpeed * (1 + intensityMult * 0.5); const explosion = this.moneyExplosion * (1 + intensityMult); const billCount = Math.ceil(amount / 5); for (let i = 0; i < billCount; i++) { this.reactions.push({ x: x + (Math.random() - 0.5) * 30, y: y + (Math.random() - 0.5) * 30, vx: (Math.random() - 0.5) * 10 * explosion, vy: (-25 - Math.random() * 15) * speed, amount, type: 'money', size: 40 + (amount * 2), opacity: 1, rotation: Math.random() * Math.PI * 2, rotationSpeed: (Math.random() - 0.5) * 0.4 * speed, gravity: 0.6 }); } const particleCount = Math.floor(this.moneyParticles * intensityMult); for (let i = 0; i < particleCount; i++) { this.particles.push({ x, y, vx: (Math.random() - 0.5) * 15 * explosion, vy: (Math.random() - 0.5) * 15 * explosion, size: Math.random() * 5 + 2, color: ['#FFD700', '#FFA500', '#FFFF00'][Math.floor(Math.random() * 3)], life: 1, decay: 0.01, sparkle: Math.random() < 0.7 }); } showToast(`💰 ${amount} TIP!`); } createTextReaction(x, y, text, color) { this.reactions.push({ x, y, vx: (Math.random() - 0.5) * 12, vy: -25 - Math.random() * 10, text, type: 'text', size: 40, opacity: 1, rotation: 0, rotationSpeed: (Math.random() - 0.5) * 0.2, gravity: 0.5, color }); for (let i = 0; i < 15; i++) { this.particles.push({ x, y, vx: (Math.random() - 0.5) * 8, vy: (Math.random() - 0.5) * 8, size: Math.random() * 4 + 1, color, life: 1, decay: 0.02 }); } } createHeelReaction(x, y) { this.reactions.push({ x, y, vx: (Math.random() - 0.5) * 15, vy: -30 - Math.random() * 10, type: 'heel', size: 45, opacity: 1, rotation: 0, rotationSpeed: (Math.random() - 0.5) * 0.5, gravity: 0.5 }); for (let i = 0; i < 25; i++) { this.particles.push({ x, y, vx: (Math.random() - 0.5) * 10, vy: (Math.random() - 0.5) * 10, size: Math.random() * 4 + 1, color: '#ff0000', life: 1, decay: 0.02, sparkle: true }); } } createPrideReaction(x, y) { this.reactions.push({ x, y, vx: (Math.random() - 0.5) * 12, vy: -25 - Math.random() * 10, type: 'pride', size: 50, opacity: 1, rotation: 0, rotationSpeed: (Math.random() - 0.5) * 0.2, gravity: 0.5 }); const colors = ['#e40303', '#ff8c00', '#ffed00', '#008026', '#004cff', '#750787']; for (let i = 0; i < 35; i++) { this.particles.push({ x, y, vx: (Math.random() - 0.5) * 8, vy: (Math.random() - 0.5) * 8, size: Math.random() * 5 + 2, color: colors[Math.floor(Math.random() * colors.length)], life: 1, decay: 0.015 }); } } draw() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); for (let i = this.particles.length - 1; i >= 0; i--) { const p = this.particles[i]; if (p.life <= 0) { this.particles.splice(i, 1); continue; } this.ctx.save(); this.ctx.globalAlpha = p.life; if (p.sparkle) { const size = p.size * (1 + Math.sin(Date.now() * 0.01 * this.moneySparkle) * 0.4); this.ctx.fillStyle = p.color; this.drawStar(p.x, p.y, 5, size, size / 2); } else { this.ctx.beginPath(); this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); this.ctx.fillStyle = p.color; this.ctx.fill(); } this.ctx.restore(); p.x += p.vx; p.y += p.vy; p.vy += 0.3; p.life -= p.decay; } for (let i = this.reactions.length - 1; i >= 0; i--) { const r = this.reactions[i]; if (r.opacity <= 0 || r.y > this.canvas.height + 100) { this.reactions.splice(i, 1); continue; } this.ctx.save(); this.ctx.translate(r.x, r.y); this.ctx.rotate(r.rotation || 0); this.ctx.globalAlpha = r.opacity; if (r.type === 'money') { const gradient = this.ctx.createLinearGradient(-r.size / 2, 0, r.size / 2, 0); gradient.addColorStop(0, '#85bb65'); gradient.addColorStop(0.5, '#a5d6a7'); gradient.addColorStop(1, '#85bb65'); this.ctx.fillStyle = gradient; this.ctx.shadowBlur = 15; this.ctx.shadowColor = '#FFD700'; this.ctx.fillRect(-r.size / 2, -r.size / 4, r.size, r.size / 2); this.ctx.shadowBlur = 0; this.ctx.fillStyle = '#2a5434'; this.ctx.font = `bold ${r.size / 2.5}px Arial`; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; this.ctx.fillText(`${r.amount}`, 0, 0); r.rotation += r.rotationSpeed; } else if (r.type === 'heel') { this.ctx.font = `${r.size}px Arial`; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; this.ctx.shadowBlur = 10; this.ctx.shadowColor = '#ff0000'; this.ctx.fillText('👠', 0, 0); r.rotation += r.rotationSpeed; } else if (r.type === 'pride') { this.ctx.font = `${r.size}px Arial`; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; this.ctx.fillText('🏳️‍🌈', 0, 0); r.rotation += r.rotationSpeed; } else if (r.type === 'text') { this.ctx.fillStyle = r.color; this.ctx.font = `bold ${r.size}px Arial`; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; this.ctx.shadowBlur = 15; this.ctx.shadowColor = r.color; this.ctx.fillText(r.text, 0, 0); r.rotation += r.rotationSpeed; } this.ctx.restore(); r.x += r.vx; r.y += r.vy; r.vy += r.gravity; r.vx *= 0.99; if (r.y < r.size) r.vy = Math.abs(r.vy) * 0.5; if (r.x < r.size || r.x > this.canvas.width - r.size) r.vx = -r.vx * 0.8; } } drawStar(x, y, spikes, outer, inner) { let rot = Math.PI / 2 * 3, step = Math.PI / spikes; this.ctx.beginPath(); this.ctx.moveTo(x, y - outer); for (let i = 0; i < spikes; i++) { this.ctx.lineTo(x + Math.cos(rot) * outer, y + Math.sin(rot) * outer); rot += step; this.ctx.lineTo(x + Math.cos(rot) * inner, y + Math.sin(rot) * inner); rot += step; } this.ctx.lineTo(x, y - outer); this.ctx.closePath(); this.ctx.fill(); } animate() { this.draw(); requestAnimationFrame(() => this.animate()); } } // Money controls function toggleMoneyControls() { document.getElementById('moneyControlsPanel').classList.toggle('open'); } // Pin system functions let pinDropMode = false; function toggleDropPinMode() { pinDropMode = true; document.getElementById('dropPinBtn').classList.add('active'); showToast('📍 Drop mode - click anywhere!'); } function toggleExplorePinMode() { pinDropMode = false; document.getElementById('dropPinBtn').classList.remove('active'); showToast('🔍 Explore mode'); } // Initialize document.addEventListener('DOMContentLoaded', () => { reactionSystem = new EnhancedReactionSystem(); // Wire up sliders document.getElementById('moneySpeed').addEventListener('input', (e) => { reactionSystem.moneySpeed = parseFloat(e.target.value); document.getElementById('speedValue').textContent = e.target.value + 'x'; }); document.getElementById('moneyExplosion').addEventListener('input', (e) => { reactionSystem.moneyExplosion = parseFloat(e.target.value); document.getElementById('explosionValue').textContent = e.target.value + 'x'; }); document.getElementById('moneyParticles').addEventListener('input', (e) => { reactionSystem.moneyParticles = parseInt(e.target.value); document.getElementById('particlesValue').textContent = e.target.value; }); document.getElementById('moneySparkle').addEventListener('input', (e) => { reactionSystem.moneySparkle = parseFloat(e.target.value); document.getElementById('sparkleValue').textContent = e.target.value + 'x'; }); // Wire up reaction buttons const buttons = [ { id: 'btn-jp', type: 'jp' }, { id: 'btn-sf', type: 'sf' }, { id: 'btn-pride', type: 'pride' }, { id: 'btn-heel', type: 'heel' }, { id: 'btn-money1', type: 'money', amount: 1 }, { id: 'btn-money5', type: 'money', amount: 5 }, { id: 'btn-money10', type: 'money', amount: 10 }, { id: 'btn-money20', type: 'money', amount: 20 } ]; buttons.forEach(btn => { const el = document.getElementById(btn.id); if (el) { el.addEventListener('click', (e) => { // Shoot from button click position - the cool way! reactionSystem.triggerReaction(btn.type, e.clientX, e.clientY, btn.amount); el.style.transform = 'scale(1.4)'; setTimeout(() => el.style.transform = 'scale(1)', 200); }); } }); });