That works with the site
Sound Factory NYC
Memories
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[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()}
${pinData.story}
`;
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);
});
}
});
});