|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>HypnoMap Live</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
.map-container { |
|
background-image: url('https://maps.googleapis.com/maps/api/staticmap?center=48.8566,2.3522&zoom=13&size=800x600&scale=2'); |
|
background-size: cover; |
|
background-position: center; |
|
} |
|
|
|
.bubble { |
|
position: absolute; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.bubble:hover { |
|
transform: scale(1.05); |
|
} |
|
|
|
.message-banner { |
|
animation: slideIn 0.5s forwards, fadeOut 0.5s 5s forwards; |
|
} |
|
|
|
@keyframes slideIn { |
|
from { transform: translateY(100px); opacity: 0; } |
|
to { transform: translateY(0); opacity: 1; } |
|
} |
|
|
|
@keyframes fadeOut { |
|
from { opacity: 1; } |
|
to { opacity: 0; } |
|
} |
|
|
|
.range-slider::-webkit-slider-thumb { |
|
-webkit-appearance: none; |
|
width: 20px; |
|
height: 20px; |
|
border-radius: 50%; |
|
background: #4f46e5; |
|
cursor: pointer; |
|
} |
|
|
|
.participant-marker { |
|
position: absolute; |
|
width: 24px; |
|
height: 24px; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-weight: bold; |
|
color: white; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-900 text-white min-h-screen"> |
|
|
|
<div id="home-screen" class="flex flex-col items-center justify-center min-h-screen p-4"> |
|
<div class="text-center mb-12"> |
|
<h1 class="text-5xl font-bold mb-4 text-purple-500">HypnoMap Live</h1> |
|
<p class="text-xl text-gray-300">Expérience sonore géolocalisée</p> |
|
</div> |
|
|
|
<div class="w-full max-w-md space-y-6"> |
|
<button id="join-btn" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-4 px-6 rounded-lg text-xl transition flex items-center justify-center"> |
|
<i class="fas fa-user-friends mr-3"></i> Rejoindre une session |
|
</button> |
|
|
|
<button id="create-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-4 px-6 rounded-lg text-xl transition flex items-center justify-center"> |
|
<i class="fas fa-music mr-3"></i> Créer une session (DJ) |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="join-modal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50 hidden"> |
|
<div class="bg-gray-800 rounded-xl p-6 w-full max-w-md"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="text-2xl font-bold">Rejoindre une session</h2> |
|
<button id="close-join-modal" class="text-gray-400 hover:text-white"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
|
|
<div class="mb-6"> |
|
<label class="block text-gray-300 mb-2">Code de session</label> |
|
<input type="text" id="session-code" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-purple-500" placeholder="Entrez le code"> |
|
</div> |
|
|
|
<div class="mb-6"> |
|
<label class="block text-gray-300 mb-2">Votre pseudo</label> |
|
<input type="text" id="participant-name" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-purple-500" placeholder="Optionnel"> |
|
</div> |
|
|
|
<button id="confirm-join" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-6 rounded-lg transition"> |
|
Rejoindre |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="dj-screen" class="hidden min-h-screen flex flex-col"> |
|
<div class="bg-gray-800 p-4 flex justify-between items-center"> |
|
<div> |
|
<h2 class="text-xl font-bold">Mode DJ</h2> |
|
<p class="text-sm text-gray-400">Session: <span id="dj-session-code" class="font-mono">XXXXXX</span></p> |
|
</div> |
|
<div class="flex space-x-2"> |
|
<button id="dj-settings" class="p-2 rounded-full hover:bg-gray-700"> |
|
<i class="fas fa-cog"></i> |
|
</button> |
|
<button id="leave-dj" class="p-2 rounded-full hover:bg-gray-700"> |
|
<i class="fas fa-sign-out-alt"></i> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div class="relative flex-1"> |
|
<div id="dj-map" class="map-container w-full h-full relative overflow-hidden"> |
|
|
|
<div id="dj-bubbles-container"></div> |
|
<div id="dj-participants-container"></div> |
|
</div> |
|
|
|
<div class="absolute bottom-4 left-0 right-0 flex justify-center"> |
|
<button id="create-bubble-btn" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-6 rounded-full shadow-lg flex items-center"> |
|
<i class="fas fa-plus-circle mr-2"></i> Créer une bulle |
|
</button> |
|
</div> |
|
|
|
<div id="dj-controls" class="absolute top-4 right-4 bg-gray-800 bg-opacity-80 rounded-lg p-3 space-y-3"> |
|
<button id="play-all" class="p-2 bg-green-600 hover:bg-green-700 rounded-full"> |
|
<i class="fas fa-play"></i> |
|
</button> |
|
<button id="pause-all" class="p-2 bg-yellow-600 hover:bg-yellow-700 rounded-full"> |
|
<i class="fas fa-pause"></i> |
|
</button> |
|
<button id="global-message" class="p-2 bg-blue-600 hover:bg-blue-700 rounded-full"> |
|
<i class="fas fa-bullhorn"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="participant-screen" class="hidden min-h-screen flex flex-col"> |
|
<div class="bg-gray-800 p-4 flex justify-between items-center"> |
|
<div> |
|
<h2 class="text-xl font-bold">Mode Participant</h2> |
|
<p class="text-sm text-gray-400">Session: <span id="participant-session-code" class="font-mono">XXXXXX</span></p> |
|
</div> |
|
<div class="flex space-x-2"> |
|
<button id="leave-participant" class="p-2 rounded-full hover:bg-gray-700"> |
|
<i class="fas fa-sign-out-alt"></i> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div class="relative flex-1"> |
|
<div id="participant-map" class="map-container w-full h-full relative overflow-hidden"> |
|
|
|
<div id="participant-bubbles-container"></div> |
|
<div id="participant-participants-container"></div> |
|
</div> |
|
|
|
<div id="message-banner" class="message-banner absolute bottom-20 left-0 right-0 mx-auto bg-purple-600 bg-opacity-90 rounded-lg p-4 max-w-md text-center hidden"> |
|
<p id="message-content" class="font-medium"></p> |
|
</div> |
|
|
|
<div class="absolute bottom-4 left-0 right-0 flex justify-center px-4"> |
|
<div class="flex w-full max-w-md bg-gray-800 bg-opacity-80 rounded-full p-2"> |
|
<input type="text" id="participant-message" class="flex-1 bg-transparent px-4 py-2 focus:outline-none" placeholder="Envoyer un message..." maxlength="140"> |
|
<button id="send-message" class="bg-purple-600 hover:bg-purple-700 rounded-full px-4"> |
|
<i class="fas fa-paper-plane"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="bubble-modal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50 hidden"> |
|
<div class="bg-gray-800 rounded-xl p-6 w-full max-w-md"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="text-2xl font-bold">Nouvelle bulle sonore</h2> |
|
<button id="close-bubble-modal" class="text-gray-400 hover:text-white"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
|
|
<div class="mb-4"> |
|
<label class="block text-gray-300 mb-2">Nom de la bulle</label> |
|
<input type="text" id="bubble-name" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-purple-500" placeholder="Ma bulle sonore"> |
|
</div> |
|
|
|
<div class="mb-4"> |
|
<label class="block text-gray-300 mb-2">Fichier audio</label> |
|
<div class="flex items-center"> |
|
<label for="audio-upload" class="bg-gray-700 hover:bg-gray-600 border border-gray-600 rounded-lg px-4 py-3 cursor-pointer flex-1"> |
|
<span id="audio-file-name" class="text-gray-300">Sélectionner un fichier...</span> |
|
</label> |
|
<input type="file" id="audio-upload" class="hidden" accept="audio/*"> |
|
</div> |
|
</div> |
|
|
|
<div class="mb-6"> |
|
<label class="block text-gray-300 mb-2">Rayon d'activation: <span id="radius-value">25</span>m</label> |
|
<input type="range" id="bubble-radius" min="5" max="50" value="25" class="w-full range-slider"> |
|
</div> |
|
|
|
<button id="confirm-bubble" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-6 rounded-lg transition"> |
|
Créer la bulle |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="global-message-modal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50 hidden"> |
|
<div class="bg-gray-800 rounded-xl p-6 w-full max-w-md"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="text-2xl font-bold">Message global</h2> |
|
<button id="close-global-message-modal" class="text-gray-400 hover:text-white"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
|
|
<div class="mb-6"> |
|
<label class="block text-gray-300 mb-2">Message</label> |
|
<textarea id="global-message-text" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-3 h-32 focus:outline-none focus:ring-2 focus:ring-purple-500" placeholder="Écrivez un message pour tous les participants..." maxlength="140"></textarea> |
|
</div> |
|
|
|
<button id="send-global-message" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-6 rounded-lg transition"> |
|
Envoyer |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const appState = { |
|
currentScreen: 'home', |
|
isDJ: false, |
|
sessionCode: '', |
|
bubbles: [], |
|
participants: [], |
|
messages: [], |
|
currentUser: { |
|
id: Math.random().toString(36).substring(2, 10), |
|
name: 'Participant ' + Math.floor(Math.random() * 1000), |
|
position: { x: 50, y: 50 } |
|
} |
|
}; |
|
|
|
|
|
const homeScreen = document.getElementById('home-screen'); |
|
const joinModal = document.getElementById('join-modal'); |
|
const djScreen = document.getElementById('dj-screen'); |
|
const participantScreen = document.getElementById('participant-screen'); |
|
const bubbleModal = document.getElementById('bubble-modal'); |
|
const globalMessageModal = document.getElementById('global-message-modal'); |
|
|
|
|
|
document.getElementById('join-btn').addEventListener('click', () => { |
|
joinModal.classList.remove('hidden'); |
|
}); |
|
|
|
document.getElementById('create-btn').addEventListener('click', () => { |
|
createSession(true); |
|
}); |
|
|
|
document.getElementById('close-join-modal').addEventListener('click', () => { |
|
joinModal.classList.add('hidden'); |
|
}); |
|
|
|
document.getElementById('confirm-join').addEventListener('click', () => { |
|
const code = document.getElementById('session-code').value; |
|
const name = document.getElementById('participant-name').value; |
|
|
|
if (code) { |
|
if (name) appState.currentUser.name = name; |
|
joinSession(code); |
|
joinModal.classList.add('hidden'); |
|
} else { |
|
alert('Veuillez entrer un code de session'); |
|
} |
|
}); |
|
|
|
document.getElementById('leave-dj').addEventListener('click', () => { |
|
showScreen('home'); |
|
}); |
|
|
|
document.getElementById('leave-participant').addEventListener('click', () => { |
|
showScreen('home'); |
|
}); |
|
|
|
document.getElementById('create-bubble-btn').addEventListener('click', () => { |
|
bubbleModal.classList.remove('hidden'); |
|
}); |
|
|
|
document.getElementById('close-bubble-modal').addEventListener('click', () => { |
|
bubbleModal.classList.add('hidden'); |
|
}); |
|
|
|
document.getElementById('confirm-bubble').addEventListener('click', () => { |
|
const name = document.getElementById('bubble-name').value; |
|
const radius = parseInt(document.getElementById('bubble-radius').value); |
|
|
|
if (name) { |
|
createBubble(name, radius); |
|
bubbleModal.classList.add('hidden'); |
|
} else { |
|
alert('Veuillez donner un nom à votre bulle'); |
|
} |
|
}); |
|
|
|
document.getElementById('bubble-radius').addEventListener('input', (e) => { |
|
document.getElementById('radius-value').textContent = e.target.value; |
|
}); |
|
|
|
document.getElementById('audio-upload').addEventListener('change', (e) => { |
|
const file = e.target.files[0]; |
|
if (file) { |
|
document.getElementById('audio-file-name').textContent = file.name; |
|
} |
|
}); |
|
|
|
document.getElementById('global-message').addEventListener('click', () => { |
|
globalMessageModal.classList.remove('hidden'); |
|
}); |
|
|
|
document.getElementById('close-global-message-modal').addEventListener('click', () => { |
|
globalMessageModal.classList.add('hidden'); |
|
}); |
|
|
|
document.getElementById('send-global-message').addEventListener('click', () => { |
|
const message = document.getElementById('global-message-text').value; |
|
if (message) { |
|
sendGlobalMessage(message); |
|
globalMessageModal.classList.add('hidden'); |
|
document.getElementById('global-message-text').value = ''; |
|
} |
|
}); |
|
|
|
document.getElementById('send-message').addEventListener('click', () => { |
|
const message = document.getElementById('participant-message').value; |
|
if (message) { |
|
sendParticipantMessage(message); |
|
document.getElementById('participant-message').value = ''; |
|
} |
|
}); |
|
|
|
|
|
function showScreen(screen) { |
|
homeScreen.classList.add('hidden'); |
|
djScreen.classList.add('hidden'); |
|
participantScreen.classList.add('hidden'); |
|
|
|
appState.currentScreen = screen; |
|
|
|
if (screen === 'home') { |
|
homeScreen.classList.remove('hidden'); |
|
} else if (screen === 'dj') { |
|
djScreen.classList.remove('hidden'); |
|
renderDJMap(); |
|
} else if (screen === 'participant') { |
|
participantScreen.classList.remove('hidden'); |
|
renderParticipantMap(); |
|
} |
|
} |
|
|
|
function createSession(isDJ) { |
|
appState.isDJ = isDJ; |
|
appState.sessionCode = generateSessionCode(); |
|
|
|
if (isDJ) { |
|
document.getElementById('dj-session-code').textContent = appState.sessionCode; |
|
showScreen('dj'); |
|
} else { |
|
document.getElementById('participant-session-code').textContent = appState.sessionCode; |
|
showScreen('participant'); |
|
} |
|
|
|
|
|
setTimeout(() => { |
|
addParticipant('DJ Master', { x: 30, y: 30 }); |
|
addParticipant('Sound Explorer', { x: 70, y: 70 }); |
|
addParticipant('Music Traveler', { x: 60, y: 40 }); |
|
|
|
if (isDJ) { |
|
renderDJMap(); |
|
} else { |
|
renderParticipantMap(); |
|
} |
|
}, 1000); |
|
} |
|
|
|
function joinSession(code) { |
|
appState.isDJ = false; |
|
appState.sessionCode = code; |
|
document.getElementById('participant-session-code').textContent = code; |
|
showScreen('participant'); |
|
|
|
|
|
setTimeout(() => { |
|
addBubble('Ambiance Chill', 20, { x: 40, y: 60 }); |
|
addBubble('Rythme Urbain', 15, { x: 70, y: 30 }); |
|
|
|
renderParticipantMap(); |
|
}, 1000); |
|
} |
|
|
|
function generateSessionCode() { |
|
return Math.random().toString(36).substring(2, 8).toUpperCase(); |
|
} |
|
|
|
function createBubble(name, radius) { |
|
const bubble = { |
|
id: Math.random().toString(36).substring(2, 10), |
|
name, |
|
radius, |
|
position: { x: 50, y: 50 }, |
|
audio: 'sample.mp3' |
|
}; |
|
|
|
appState.bubbles.push(bubble); |
|
renderDJMap(); |
|
} |
|
|
|
function addBubble(name, radius, position) { |
|
const bubble = { |
|
id: Math.random().toString(36).substring(2, 10), |
|
name, |
|
radius, |
|
position, |
|
audio: 'sample.mp3' |
|
}; |
|
|
|
appState.bubbles.push(bubble); |
|
} |
|
|
|
function addParticipant(name, position) { |
|
const participant = { |
|
id: Math.random().toString(36).substring(2, 10), |
|
name, |
|
position |
|
}; |
|
|
|
appState.participants.push(participant); |
|
} |
|
|
|
function sendGlobalMessage(message) { |
|
|
|
console.log('Global message sent:', message); |
|
} |
|
|
|
function sendParticipantMessage(message) { |
|
const messageObj = { |
|
id: Math.random().toString(36).substring(2, 10), |
|
sender: appState.currentUser.name, |
|
content: message, |
|
timestamp: new Date() |
|
}; |
|
|
|
|
|
const banner = document.getElementById('message-banner'); |
|
const content = document.getElementById('message-content'); |
|
|
|
content.textContent = `${messageObj.sender}: ${messageObj.content}`; |
|
banner.classList.remove('hidden'); |
|
|
|
|
|
setTimeout(() => { |
|
banner.classList.add('hidden'); |
|
}, 5500); |
|
} |
|
|
|
function renderDJMap() { |
|
const container = document.getElementById('dj-bubbles-container'); |
|
const participantsContainer = document.getElementById('dj-participants-container'); |
|
|
|
container.innerHTML = ''; |
|
participantsContainer.innerHTML = ''; |
|
|
|
|
|
appState.bubbles.forEach(bubble => { |
|
const bubbleEl = document.createElement('div'); |
|
bubbleEl.className = 'bubble bg-purple-500 bg-opacity-30 border-2 border-purple-400'; |
|
bubbleEl.style.width = `${bubble.radius * 6}px`; |
|
bubbleEl.style.height = `${bubble.radius * 6}px`; |
|
bubbleEl.style.left = `${bubble.position.x}%`; |
|
bubbleEl.style.top = `${bubble.position.y}%`; |
|
bubbleEl.title = bubble.name; |
|
|
|
|
|
bubbleEl.draggable = true; |
|
bubbleEl.addEventListener('dragstart', (e) => { |
|
e.dataTransfer.setData('text/plain', bubble.id); |
|
}); |
|
|
|
container.appendChild(bubbleEl); |
|
}); |
|
|
|
|
|
appState.participants.forEach(participant => { |
|
const participantEl = document.createElement('div'); |
|
participantEl.className = 'participant-marker bg-blue-500'; |
|
participantEl.style.left = `${participant.position.x}%`; |
|
participantEl.style.top = `${participant.position.y}%`; |
|
participantEl.textContent = participant.name.charAt(0); |
|
participantEl.title = participant.name; |
|
|
|
participantsContainer.appendChild(participantEl); |
|
}); |
|
|
|
|
|
const map = document.getElementById('dj-map'); |
|
map.addEventListener('dragover', (e) => { |
|
e.preventDefault(); |
|
}); |
|
|
|
map.addEventListener('drop', (e) => { |
|
e.preventDefault(); |
|
const bubbleId = e.dataTransfer.getData('text/plain'); |
|
const bubble = appState.bubbles.find(b => b.id === bubbleId); |
|
|
|
if (bubble) { |
|
const rect = map.getBoundingClientRect(); |
|
const x = ((e.clientX - rect.left) / rect.width) * 100; |
|
const y = ((e.clientY - rect.top) / rect.height) * 100; |
|
|
|
bubble.position = { x, y }; |
|
renderDJMap(); |
|
} |
|
}); |
|
} |
|
|
|
function renderParticipantMap() { |
|
const container = document.getElementById('participant-bubbles-container'); |
|
const participantsContainer = document.getElementById('participant-participants-container'); |
|
|
|
container.innerHTML = ''; |
|
participantsContainer.innerHTML = ''; |
|
|
|
|
|
appState.bubbles.forEach(bubble => { |
|
const bubbleEl = document.createElement('div'); |
|
bubbleEl.className = 'bubble bg-purple-500 bg-opacity-20 border border-purple-400'; |
|
bubbleEl.style.width = `${bubble.radius * 6}px`; |
|
bubbleEl.style.height = `${bubble.radius * 6}px`; |
|
bubbleEl.style.left = `${bubble.position.x}%`; |
|
bubbleEl.style.top = `${bubble.position.y}%`; |
|
bubbleEl.title = bubble.name; |
|
|
|
container.appendChild(bubbleEl); |
|
}); |
|
|
|
|
|
appState.participants.forEach(participant => { |
|
const participantEl = document.createElement('div'); |
|
participantEl.className = 'participant-marker bg-blue-500'; |
|
participantEl.style.left = `${participant.position.x}%`; |
|
participantEl.style.top = `${participant.position.y}%`; |
|
participantEl.textContent = participant.name.charAt(0); |
|
participantEl.title = participant.name; |
|
|
|
participantsContainer.appendChild(participantEl); |
|
}); |
|
|
|
|
|
const currentUserEl = document.createElement('div'); |
|
currentUserEl.className = 'participant-marker bg-green-500'; |
|
currentUserEl.style.left = `${appState.currentUser.position.x}%`; |
|
currentUserEl.style.top = `${appState.currentUser.position.y}%`; |
|
currentUserEl.textContent = appState.currentUser.name.charAt(0); |
|
currentUserEl.title = appState.currentUser.name; |
|
|
|
participantsContainer.appendChild(currentUserEl); |
|
} |
|
|
|
|
|
showScreen('home'); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=PierreH/test" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |