test / index.html
PierreH's picture
Add 2 files
79b474d verified
<!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">
<!-- Home 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>
<!-- Join Session Modal -->
<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>
<!-- DJ Mode -->
<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">
<!-- Map will be rendered here -->
<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>
<!-- Participant Mode -->
<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">
<!-- Map will be rendered here -->
<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>
<!-- Create Bubble Modal -->
<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>
<!-- Global Message Modal -->
<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>
// State management
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 }
}
};
// DOM elements
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');
// Event listeners
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 = '';
}
});
// Functions
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');
}
// Simulate other participants joining
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');
// Simulate existing bubbles
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 }, // Center position
audio: 'sample.mp3' // In a real app, this would be the uploaded file
};
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) {
// In a real app, this would be sent to all participants
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()
};
// Show the message banner
const banner = document.getElementById('message-banner');
const content = document.getElementById('message-content');
content.textContent = `${messageObj.sender}: ${messageObj.content}`;
banner.classList.remove('hidden');
// Hide after animation
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 = '';
// Render bubbles
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;
// Make draggable
bubbleEl.draggable = true;
bubbleEl.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', bubble.id);
});
container.appendChild(bubbleEl);
});
// Render participants
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);
});
// Add drop zone for bubbles
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 = '';
// Render bubbles
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);
});
// Render participants
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);
});
// Add current user
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);
}
// Initialize
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>