Spaces:
Running
Running
before the app asked to access microphone and now not please fix - Follow Up Deployment
Browse files- index.html +201 -46
index.html
CHANGED
|
@@ -146,6 +146,9 @@
|
|
| 146 |
<button id="add-player" class="bg-primary-600 hover:bg-primary-700 text-white py-2 px-6 rounded-xl flex items-center">
|
| 147 |
<i class="fas fa-user-plus mr-2"></i> Add Player
|
| 148 |
</button>
|
|
|
|
|
|
|
|
|
|
| 149 |
<button id="buzz-test" class="bg-warning-600 hover:bg-warning-700 text-white py-2 px-6 rounded-xl flex items-center">
|
| 150 |
<i class="fas fa-bolt mr-2"></i> Test Buzz
|
| 151 |
</button>
|
|
@@ -238,6 +241,40 @@
|
|
| 238 |
</div>
|
| 239 |
</div>
|
| 240 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
<script>
|
| 243 |
// Game state variables
|
|
@@ -290,12 +327,27 @@
|
|
| 290 |
|
| 291 |
// Buttons
|
| 292 |
document.getElementById('add-player').addEventListener('click', openAddPlayerModal);
|
|
|
|
| 293 |
document.getElementById('buzz-test').addEventListener('click', simulateBuzz);
|
| 294 |
document.getElementById('new-game').addEventListener('click', startNewGame);
|
| 295 |
document.getElementById('play-again').addEventListener('click', startNewGame);
|
| 296 |
document.getElementById('cancel-player').addEventListener('click', closeAddPlayerModal);
|
| 297 |
document.getElementById('save-player').addEventListener('click', addNewPlayer);
|
|
|
|
|
|
|
| 298 |
document.querySelector('#game-over-modal button').addEventListener('click', () => gameOverModal.classList.add('hidden'));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
// Initialize the game
|
| 301 |
function initGame() {
|
|
@@ -306,54 +358,33 @@
|
|
| 306 |
// Set up audio listening
|
| 307 |
async function setupMicrophone() {
|
| 308 |
try {
|
| 309 |
-
|
| 310 |
-
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
|
|
|
|
|
|
| 319 |
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
gameState.analyser.getByteFrequencyData(dataArray);
|
| 324 |
-
|
| 325 |
-
let total = 0;
|
| 326 |
-
for (let i = 0; i < bufferLength; i++) {
|
| 327 |
-
total += dataArray[i];
|
| 328 |
-
}
|
| 329 |
-
const average = total / bufferLength;
|
| 330 |
-
|
| 331 |
-
// Update buzzer indicator status
|
| 332 |
-
if (average > 60) {
|
| 333 |
-
buzzerStatusEl.classList.remove('bg-gray-500');
|
| 334 |
-
buzzerStatusEl.classList.add('animate-pulse-fast');
|
| 335 |
-
|
| 336 |
-
if (average > 80) {
|
| 337 |
-
// Trigger buzz detection
|
| 338 |
-
handleBuzzDetected();
|
| 339 |
-
// Flash red for buzz
|
| 340 |
-
buzzerStatusEl.classList.remove('bg-green-500', 'bg-yellow-500');
|
| 341 |
-
buzzerStatusEl.classList.add('bg-red-500');
|
| 342 |
-
} else {
|
| 343 |
-
// Sound detected but not buzzing yet
|
| 344 |
-
buzzerStatusEl.classList.add('bg-yellow-500');
|
| 345 |
-
}
|
| 346 |
-
} else {
|
| 347 |
-
buzzerStatusEl.classList.remove('bg-red-500', 'bg-yellow-500', 'animate-pulse-fast');
|
| 348 |
-
buzzerStatusEl.classList.add('bg-green-500');
|
| 349 |
-
}
|
| 350 |
-
|
| 351 |
-
requestAnimationFrame(detectSound);
|
| 352 |
-
};
|
| 353 |
|
| 354 |
-
detectSound();
|
| 355 |
buzzerStatusEl.classList.remove('bg-gray-500');
|
| 356 |
buzzerStatusEl.classList.add('bg-green-500');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
} catch (err) {
|
| 358 |
buzzerStatusEl.classList.remove('bg-gray-500');
|
| 359 |
buzzerStatusEl.classList.add('bg-red-500');
|
|
@@ -715,12 +746,135 @@
|
|
| 715 |
updatePlayerListDisplay();
|
| 716 |
gameOverModal.classList.add('hidden');
|
| 717 |
|
| 718 |
-
// Setup microphone
|
| 719 |
-
|
| 720 |
-
setupMicrophone();
|
| 721 |
-
}
|
| 722 |
}
|
| 723 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
// Simulate a buzz for testing
|
| 725 |
function simulateBuzz() {
|
| 726 |
handleBuzzDetected();
|
|
@@ -741,6 +895,7 @@
|
|
| 741 |
document.addEventListener('DOMContentLoaded', () => {
|
| 742 |
initGame();
|
| 743 |
updateLifeDisplay();
|
|
|
|
| 744 |
});
|
| 745 |
</script>
|
| 746 |
<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=web3district/operation-table" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
|
|
|
| 146 |
<button id="add-player" class="bg-primary-600 hover:bg-primary-700 text-white py-2 px-6 rounded-xl flex items-center">
|
| 147 |
<i class="fas fa-user-plus mr-2"></i> Add Player
|
| 148 |
</button>
|
| 149 |
+
<button id="calibrate" class="bg-secondary-600 hover:bg-secondary-700 text-white py-2 px-6 rounded-xl flex items-center">
|
| 150 |
+
<i class="fas fa-sliders-h mr-2"></i> Calibrate
|
| 151 |
+
</button>
|
| 152 |
<button id="buzz-test" class="bg-warning-600 hover:bg-warning-700 text-white py-2 px-6 rounded-xl flex items-center">
|
| 153 |
<i class="fas fa-bolt mr-2"></i> Test Buzz
|
| 154 |
</button>
|
|
|
|
| 241 |
</div>
|
| 242 |
</div>
|
| 243 |
</div>
|
| 244 |
+
|
| 245 |
+
<!-- Calibration Modal -->
|
| 246 |
+
<div id="calibration-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
|
| 247 |
+
<div class="bg-gray-800 rounded-2xl max-w-md w-full p-8">
|
| 248 |
+
<h2 class="text-2xl font-bold mb-6 text-center">Calibrate Buzzer</h2>
|
| 249 |
+
|
| 250 |
+
<div class="mb-6">
|
| 251 |
+
<p class="mb-4">Press the buzzer from your Operation game 3-5 times to calibrate. The detector will learn the frequency pattern of your specific buzzer.</p>
|
| 252 |
+
<div class="bg-gray-700 rounded-lg p-4 mb-4">
|
| 253 |
+
<div class="flex items-center justify-between mb-2">
|
| 254 |
+
<span>Buzz Detections:</span>
|
| 255 |
+
<span id="calibration-count">0</span>
|
| 256 |
+
</div>
|
| 257 |
+
<div class="h-4 bg-gray-600 rounded-full overflow-hidden">
|
| 258 |
+
<div id="calibration-progress" class="h-full bg-success-600" style="width: 0%"></div>
|
| 259 |
+
</div>
|
| 260 |
+
</div>
|
| 261 |
+
<div class="text-center">
|
| 262 |
+
<div id="calibration-status" class="inline-block px-3 py-1 rounded-full text-sm text-gray-200 bg-gray-700 mb-4">
|
| 263 |
+
Ready to calibrate
|
| 264 |
+
</div>
|
| 265 |
+
<div id="sound-level" class="w-full h-2 bg-gray-700 rounded-full overflow-hidden mb-2">
|
| 266 |
+
<div class="h-full bg-primary-600" style="width: 0%"></div>
|
| 267 |
+
</div>
|
| 268 |
+
<div class="text-xs text-gray-400">Current sound level</div>
|
| 269 |
+
</div>
|
| 270 |
+
</div>
|
| 271 |
+
|
| 272 |
+
<div class="flex gap-3">
|
| 273 |
+
<button id="cancel-calibration" class="flex-1 bg-gray-700 hover:bg-gray-600 py-3 rounded-xl font-medium">Cancel</button>
|
| 274 |
+
<button id="finish-calibration" class="flex-1 bg-primary-600 hover:bg-primary-700 py-3 rounded-xl font-medium">Finish</button>
|
| 275 |
+
</div>
|
| 276 |
+
</div>
|
| 277 |
+
</div>
|
| 278 |
|
| 279 |
<script>
|
| 280 |
// Game state variables
|
|
|
|
| 327 |
|
| 328 |
// Buttons
|
| 329 |
document.getElementById('add-player').addEventListener('click', openAddPlayerModal);
|
| 330 |
+
document.getElementById('calibrate').addEventListener('click', startCalibration);
|
| 331 |
document.getElementById('buzz-test').addEventListener('click', simulateBuzz);
|
| 332 |
document.getElementById('new-game').addEventListener('click', startNewGame);
|
| 333 |
document.getElementById('play-again').addEventListener('click', startNewGame);
|
| 334 |
document.getElementById('cancel-player').addEventListener('click', closeAddPlayerModal);
|
| 335 |
document.getElementById('save-player').addEventListener('click', addNewPlayer);
|
| 336 |
+
document.getElementById('cancel-calibration').addEventListener('click', cancelCalibration);
|
| 337 |
+
document.getElementById('finish-calibration').addEventListener('click', finishCalibration);
|
| 338 |
document.querySelector('#game-over-modal button').addEventListener('click', () => gameOverModal.classList.add('hidden'));
|
| 339 |
+
|
| 340 |
+
// Audio detection settings
|
| 341 |
+
let audioSettings = {
|
| 342 |
+
minFrequency: 1000, // Default minimum frequency
|
| 343 |
+
maxFrequency: 3000, // Default maximum frequency
|
| 344 |
+
threshold: 0.7, // Default volume threshold
|
| 345 |
+
calibrated: false,
|
| 346 |
+
calibrationSamples: []
|
| 347 |
+
};
|
| 348 |
+
|
| 349 |
+
// Microphone stream
|
| 350 |
+
let microphoneStream = null;
|
| 351 |
|
| 352 |
// Initialize the game
|
| 353 |
function initGame() {
|
|
|
|
| 358 |
// Set up audio listening
|
| 359 |
async function setupMicrophone() {
|
| 360 |
try {
|
| 361 |
+
if (!gameState.audioContext) {
|
| 362 |
+
gameState.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
| 363 |
+
gameState.analyser = gameState.audioContext.createAnalyser();
|
| 364 |
+
gameState.analyser.fftSize = 2048;
|
| 365 |
+
}
|
| 366 |
|
| 367 |
+
if (microphoneStream) {
|
| 368 |
+
microphoneStream.getTracks().forEach(track => track.stop());
|
| 369 |
+
}
|
| 370 |
|
| 371 |
+
microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: {
|
| 372 |
+
echoCancellation: false,
|
| 373 |
+
noiseSuppression: false,
|
| 374 |
+
autoGainControl: false
|
| 375 |
+
}});
|
| 376 |
|
| 377 |
+
gameState.microphone = gameState.audioContext.createMediaStreamSource(microphoneStream);
|
| 378 |
+
gameState.microphone.connect(gameState.analyser);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
|
|
|
|
| 380 |
buzzerStatusEl.classList.remove('bg-gray-500');
|
| 381 |
buzzerStatusEl.classList.add('bg-green-500');
|
| 382 |
+
|
| 383 |
+
if (!audioSettings.calibrated) {
|
| 384 |
+
startCalibration();
|
| 385 |
+
} else {
|
| 386 |
+
startAudioDetection();
|
| 387 |
+
}
|
| 388 |
} catch (err) {
|
| 389 |
buzzerStatusEl.classList.remove('bg-gray-500');
|
| 390 |
buzzerStatusEl.classList.add('bg-red-500');
|
|
|
|
| 746 |
updatePlayerListDisplay();
|
| 747 |
gameOverModal.classList.add('hidden');
|
| 748 |
|
| 749 |
+
// Setup microphone
|
| 750 |
+
setupMicrophone();
|
|
|
|
|
|
|
| 751 |
}
|
| 752 |
|
| 753 |
+
// Start calibration mode
|
| 754 |
+
function startCalibration() {
|
| 755 |
+
audioSettings.calibrationSamples = [];
|
| 756 |
+
document.getElementById('calibration-count').textContent = '0';
|
| 757 |
+
document.getElementById('calibration-progress').style.width = '0%';
|
| 758 |
+
document.getElementById('calibration-status').textContent = 'Making initial analysis...';
|
| 759 |
+
document.getElementById('calibration-status').className = 'inline-block px-3 py-1 rounded-full text-sm text-gray-200 bg-gray-700 mb-4';
|
| 760 |
+
|
| 761 |
+
calibrationModal.classList.remove('hidden');
|
| 762 |
+
startAudioDetection(true);
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
// Cancel calibration
|
| 766 |
+
function cancelCalibration() {
|
| 767 |
+
calibrationModal.classList.add('hidden');
|
| 768 |
+
if (gameState.gameActive) {
|
| 769 |
+
startAudioDetection();
|
| 770 |
+
}
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
// Finish calibration and calculate thresholds
|
| 774 |
+
function finishCalibration() {
|
| 775 |
+
if (audioSettings.calibrationSamples.length > 0) {
|
| 776 |
+
// Calculate average frequency and volume thresholds
|
| 777 |
+
const avgFreq = audioSettings.calibrationSamples.reduce((sum, val) => sum + val.frequency, 0) / audioSettings.calibrationSamples.length;
|
| 778 |
+
const avgVol = audioSettings.calibrationSamples.reduce((sum, val) => sum + val.volume, 0) / audioSettings.calibrationSamples.length;
|
| 779 |
+
|
| 780 |
+
// Set detection thresholds with some buffer
|
| 781 |
+
audioSettings.minFrequency = Math.max(0, avgFreq - 200);
|
| 782 |
+
audioSettings.maxFrequency = avgFreq + 200;
|
| 783 |
+
audioSettings.threshold = avgVol * 0.7; // 70% of average volume
|
| 784 |
+
audioSettings.calibrated = true;
|
| 785 |
+
|
| 786 |
+
document.getElementById('calibration-status').textContent = 'Calibration complete!';
|
| 787 |
+
document.getElementById('calibration-status').className = 'inline-block px-3 py-1 rounded-full text-sm text-white bg-success-600 mb-4';
|
| 788 |
+
|
| 789 |
+
setTimeout(() => {
|
| 790 |
+
calibrationModal.classList.add('hidden');
|
| 791 |
+
if (gameState.gameActive) {
|
| 792 |
+
startAudioDetection();
|
| 793 |
+
}
|
| 794 |
+
}, 1000);
|
| 795 |
+
}
|
| 796 |
+
}
|
| 797 |
+
|
| 798 |
+
// Start audio detection
|
| 799 |
+
function startAudioDetection(isCalibrating = false) {
|
| 800 |
+
const bufferLength = gameState.analyser.frequencyBinCount;
|
| 801 |
+
const dataArray = new Uint8Array(bufferLength);
|
| 802 |
+
const frequencyData = new Float32Array(bufferLength);
|
| 803 |
+
|
| 804 |
+
const sampleRate = gameState.audioContext.sampleRate;
|
| 805 |
+
const minFreq = isCalibrating ? 0 : audioSettings.minFrequency;
|
| 806 |
+
const maxFreq = isCalibrating ? sampleRate/2 : audioSettings.maxFrequency;
|
| 807 |
+
|
| 808 |
+
const detectSound = () => {
|
| 809 |
+
if (!gameState.gameActive && !isCalibrating) return;
|
| 810 |
+
|
| 811 |
+
gameState.analyser.getByteFrequencyData(dataArray);
|
| 812 |
+
gameState.analyser.getFloatFrequencyData(frequencyData);
|
| 813 |
+
|
| 814 |
+
// Get frequency with peak volume
|
| 815 |
+
let maxVolume = -Infinity;
|
| 816 |
+
let peakFrequency = 0;
|
| 817 |
+
let totalVolume = 0;
|
| 818 |
+
|
| 819 |
+
for (let i = 0; i < bufferLength; i++) {
|
| 820 |
+
const freq = i * sampleRate / bufferLength;
|
| 821 |
+
if (freq >= minFreq && freq <= maxFreq) {
|
| 822 |
+
if (dataArray[i] > maxVolume) {
|
| 823 |
+
maxVolume = dataArray[i];
|
| 824 |
+
peakFrequency = freq;
|
| 825 |
+
}
|
| 826 |
+
totalVolume += dataArray[i];
|
| 827 |
+
}
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
const avgVolume = totalVolume / bufferLength;
|
| 831 |
+
|
| 832 |
+
// Update sound level indicator for calibration
|
| 833 |
+
if (isCalibrating) {
|
| 834 |
+
const soundLevelBar = document.querySelector('#sound-level div');
|
| 835 |
+
const normalizedVolume = Math.min(100, Math.max(0, (avgVolume / 255) * 100));
|
| 836 |
+
soundLevelBar.style.width = `${normalizedVolume}%`;
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
// Detect buzz - during calibration or normal play
|
| 840 |
+
if (avgVolume > (isCalibrating ? 30 : audioSettings.threshold * 255)) {
|
| 841 |
+
if (isCalibrating) {
|
| 842 |
+
// During calibration, record the frequency signature
|
| 843 |
+
audioSettings.calibrationSamples.push({
|
| 844 |
+
frequency: peakFrequency,
|
| 845 |
+
volume: avgVolume / 255
|
| 846 |
+
});
|
| 847 |
+
|
| 848 |
+
const count = audioSettings.calibrationSamples.length;
|
| 849 |
+
document.getElementById('calibration-count').textContent = count;
|
| 850 |
+
document.getElementById('calibration-progress').style.width = `${Math.min(100, count * 20)}%`;
|
| 851 |
+
|
| 852 |
+
if (count >= 3) {
|
| 853 |
+
document.getElementById('calibration-status').textContent = 'Good samples collected!';
|
| 854 |
+
document.getElementById('calibration-status').className = 'inline-block px-3 py-1 rounded-full text-sm text-white bg-success-600 mb-4';
|
| 855 |
+
}
|
| 856 |
+
} else {
|
| 857 |
+
// During normal gameplay, trigger buzz effects
|
| 858 |
+
handleBuzzDetected();
|
| 859 |
+
buzzerStatusEl.classList.remove('bg-green-500', 'bg-yellow-500');
|
| 860 |
+
buzzerStatusEl.classList.add('bg-red-500', 'animate-buzz');
|
| 861 |
+
setTimeout(() => {
|
| 862 |
+
buzzerStatusEl.classList.remove('animate-buzz');
|
| 863 |
+
}, 500);
|
| 864 |
+
}
|
| 865 |
+
} else {
|
| 866 |
+
buzzerStatusEl.classList.remove('bg-red-500', 'animate-buzz');
|
| 867 |
+
if (gameState.gameActive) {
|
| 868 |
+
buzzerStatusEl.classList.add('bg-green-500');
|
| 869 |
+
}
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
requestAnimationFrame(detectSound);
|
| 873 |
+
};
|
| 874 |
+
|
| 875 |
+
detectSound();
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
// Simulate a buzz for testing
|
| 879 |
function simulateBuzz() {
|
| 880 |
handleBuzzDetected();
|
|
|
|
| 895 |
document.addEventListener('DOMContentLoaded', () => {
|
| 896 |
initGame();
|
| 897 |
updateLifeDisplay();
|
| 898 |
+
setupMicrophone(); // Request mic access on load
|
| 899 |
});
|
| 900 |
</script>
|
| 901 |
<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=web3district/operation-table" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|