|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Multiplayer Tic-Tac-Toe</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script> |
|
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&display=swap" rel="stylesheet"> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<script> |
|
tailwind.config = { |
|
theme: { |
|
extend: { |
|
colors: { |
|
primary: '#6366f1', |
|
secondary: '#818cf8', |
|
accent: '#a5b4fc', |
|
dark: '#1e293b', |
|
light: '#f1f5f9', |
|
'board-bg': '#ede9fe' |
|
}, |
|
fontFamily: { |
|
sans: ['Nunito', 'sans-serif'] |
|
}, |
|
animation: { |
|
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', |
|
'bounce-slow': 'bounce 2s infinite' |
|
} |
|
} |
|
} |
|
} |
|
</script> |
|
<style> |
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(-10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
.fade-in { |
|
animation: fadeIn 0.5s ease-out forwards; |
|
} |
|
|
|
.grid-cell:hover { |
|
transform: scale(1.05); |
|
transition: transform 0.3s ease; |
|
} |
|
|
|
.winning-cell { |
|
animation: pulse 2s infinite, bounce 1s infinite; |
|
} |
|
|
|
#game-container { |
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15), 0 15px 30px rgba(99, 102, 241, 0.1); |
|
} |
|
|
|
.chip-animation { |
|
position: absolute; |
|
background-color: rgba(255, 255, 255, 0.6); |
|
border-radius: 50%; |
|
animation: expand 1s forwards; |
|
opacity: 0; |
|
} |
|
|
|
@keyframes expand { |
|
0% { transform: scale(0.1); opacity: 1; } |
|
100% { transform: scale(5); opacity: 0; } |
|
} |
|
|
|
.status-banner { |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.confetti { |
|
position: absolute; |
|
width: 10px; |
|
height: 10px; |
|
opacity: 0.7; |
|
animation: fall 3s ease-in-out forwards; |
|
} |
|
|
|
@keyframes fall { |
|
0% { transform: translateY(-10vh) rotate(0deg); opacity: 1; } |
|
100% { transform: translateY(100vh) rotate(360deg); opacity: 0; } |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gradient-to-br from-indigo-50 to-purple-100 min-h-screen flex flex-col items-center justify-center p-4 font-sans"> |
|
<div id="game-container" class="w-full max-w-2xl bg-white rounded-2xl overflow-hidden fade-in"> |
|
|
|
<div class="bg-gradient-to-r from-primary to-secondary text-white py-5 px-6 text-center"> |
|
<h1 class="text-3xl md:text-4xl font-bold flex items-center justify-center gap-2"> |
|
<i class="fas fa-gamepad"></i> |
|
<span>Multiplayer Tic-Tac-Toe</span> |
|
</h1> |
|
<p class="mt-2 opacity-90">Play against friends in real-time!</p> |
|
</div> |
|
|
|
|
|
<div id="lobby" class="p-6"> |
|
<div class="text-center mb-8"> |
|
<i class="fas fa-users text-6xl text-primary mb-4"></i> |
|
<h2 class="text-2xl font-bold text-gray-800">Join or Create a Game</h2> |
|
<p class="text-gray-600 mt-2">Share the Game ID with a friend to play together</p> |
|
</div> |
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> |
|
|
|
<div class="bg-light rounded-xl p-6 border border-accent"> |
|
<div class="flex items-center gap-3 mb-4"> |
|
<div class="bg-primary rounded-lg p-3"> |
|
<i class="fas fa-plus-circle text-white text-xl"></i> |
|
</div> |
|
<h3 class="font-bold text-xl text-gray-800">Create New Game</h3> |
|
</div> |
|
<p class="text-gray-700 mb-4">Create a new game and invite a friend to play with you.</p> |
|
<button id="create-game" class="w-full bg-primary hover:bg-primary/90 text-white py-3 rounded-lg font-semibold transition flex items-center justify-center gap-2"> |
|
<i class="fas fa-chess-board"></i> Create Game |
|
</button> |
|
</div> |
|
|
|
|
|
<div class="bg-light rounded-xl p-6 border border-accent"> |
|
<div class="flex items-center gap-3 mb-4"> |
|
<div class="bg-secondary rounded-lg p-3"> |
|
<i class="fas fa-user-plus text-white text-xl"></i> |
|
</div> |
|
<h3 class="font-bold text-xl text-gray-800">Join Existing Game</h3> |
|
</div> |
|
<div class="space-y-4"> |
|
<div> |
|
<label class="block text-gray-700 font-medium mb-2" for="game-id">Enter Game ID</label> |
|
<input type="text" id="game-id" placeholder="Game ID" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary focus:border-transparent"> |
|
</div> |
|
<button id="join-game" class="w-full bg-secondary hover:bg-secondary/90 text-white py-3 rounded-lg font-semibold transition flex items-center justify-center gap-2"> |
|
<i class="fas fa-sign-in-alt"></i> Join Game |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="game-id-display" class="hidden bg-light rounded-xl p-4 border border-accent"> |
|
<div class="flex flex-col items-center text-center"> |
|
<p class="font-bold text-lg text-gray-800 mb-2">Your Game ID:</p> |
|
<div class="flex items-center gap-2"> |
|
<p id="game-code" class="font-mono text-2xl font-bold bg-white py-2 px-6 rounded-lg border border-primary text-primary"></p> |
|
<button id="copy-id" class="bg-accent text-primary p-2 rounded-lg hover:bg-accent/80 transition"> |
|
<i class="fas fa-copy"></i> |
|
</button> |
|
</div> |
|
<p class="text-gray-600 mt-3 flex items-center gap-2"> |
|
<i class="fas fa-info-circle text-primary"></i> |
|
<span>Share this code with your friend to join the game</span> |
|
</p> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="waiting-room" class="hidden mt-8 bg-board-bg rounded-xl p-8 text-center border border-accent"> |
|
<div class="flex flex-col items-center"> |
|
<div class="relative mb-6"> |
|
<div class="w-24 h-24 rounded-full bg-accent flex items-center justify-center"> |
|
<i class="fas fa-user-clock text-5xl text-primary"></i> |
|
</div> |
|
<div class="absolute -top-2 -right-2 w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center animate-bounce-slow"> |
|
<span id="player-count">1</span> |
|
</div> |
|
</div> |
|
<h3 class="text-xl font-bold text-gray-800 mb-3">Waiting for players...</h3> |
|
<div class="w-full max-w-md bg-white rounded-lg p-4 mx-auto"> |
|
<div class="flex items-center justify-center gap-4 mb-4"> |
|
<div class="bg-primary/10 px-4 py-2 rounded-lg flex items-center gap-2"> |
|
<i class="fas fa-user text-primary"></i> |
|
<span id="player-name" class="font-semibold">You</span> |
|
</div> |
|
<span class="font-bold text-gray-600">vs</span> |
|
<div class="bg-gray-100 px-4 py-2 rounded-lg flex items-center gap-2"> |
|
<i class="fas fa-user-clock text-gray-500"></i> |
|
<span class="text-gray-500">Waiting...</span> |
|
</div> |
|
</div> |
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
<div class="bg-primary h-2.5 rounded-full w-1/3 animate-pulse-slow"></div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="game-section" class="hidden p-6"> |
|
<div id="game-status" class="status-banner bg-gradient-to-r from-purple-600 to-indigo-700 text-white py-3 px-6 rounded-lg flex items-center justify-between mb-6"> |
|
<div class="flex items-center gap-2"> |
|
<i class="fas fa-info-circle"></i> |
|
<span id="status-text">Your turn! (X)</span> |
|
</div> |
|
<div id="players"> |
|
<span id="player-symbol" class="font-bold">X</span> |
|
<span class="mx-1">|</span> |
|
<span id="game-id-label"></span> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="relative bg-board-bg rounded-2xl p-4 overflow-hidden"> |
|
<div id="game-board" class="grid grid-cols-3 gap-4 max-w-lg mx-auto my-4"> |
|
|
|
</div> |
|
|
|
|
|
<div id="game-over" class="hidden absolute inset-0 bg-black/80 flex items-center justify-center rounded-2xl"> |
|
<div class="bg-white p-8 rounded-xl text-center"> |
|
<h3 id="result-text" class="text-2xl font-bold mb-4">X Wins!</h3> |
|
<div class="flex flex-wrap gap-3 justify-center"> |
|
<button id="play-again" class="bg-primary hover:bg-primary/90 text-white py-3 px-6 rounded-lg font-semibold transition"> |
|
<i class="fas fa-redo mr-2"></i> Play Again |
|
</button> |
|
<button id="new-game" class="bg-secondary hover:bg-secondary/90 text-white py-3 px-6 rounded-lg font-semibold transition"> |
|
<i class="fas fa-plus mr-2"></i> New Game |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="flex flex-col sm:flex-row gap-4 mt-8"> |
|
<div class="flex gap-2"> |
|
<button id="copy-link" class="bg-light hover:bg-accent text-primary px-4 py-2 rounded-lg transition flex items-center gap-2"> |
|
<i class="fas fa-link"></i> Copy Invite Link |
|
</button> |
|
<button id="leave-game" class="bg-light hover:bg-rose-500/10 text-rose-500 px-4 py-2 rounded-lg transition flex items-center gap-2"> |
|
<i class="fas fa-sign-out-alt"></i> Leave Game |
|
</button> |
|
</div> |
|
<div class="ml-auto flex gap-2"> |
|
<div id="player-one" class="bg-light px-4 py-2 rounded-lg flex items-center gap-2"> |
|
<i class="fas fa-user text-primary"></i> |
|
<span>X: <span id="player-one-name">Player 1</span></span> |
|
</div> |
|
<div id="player-two" class="bg-light px-4 py-2 rounded-lg flex items-center gap-2"> |
|
<i class="fas fa-user text-secondary"></i> |
|
<span>O: <span id="player-two-name">Player 2</span></span> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<footer class="mt-8 text-center text-gray-600"> |
|
<p>Created with <i class="fas fa-heart text-red-500"></i> & Socket.io</p> |
|
</footer> |
|
|
|
<script> |
|
|
|
const lobbySection = document.getElementById('lobby'); |
|
const gameSection = document.getElementById('game-section'); |
|
const gameBoard = document.getElementById('game-board'); |
|
const waitingRoom = document.getElementById('waiting-room'); |
|
const gameIdDisplay = document.getElementById('game-id-display'); |
|
const gameIdInput = document.getElementById('game-id'); |
|
const gameIdLabel = document.getElementById('game-id-label'); |
|
const gameOver = document.getElementById('game-over'); |
|
const resultText = document.getElementById('result-text'); |
|
const statusText = document.getElementById('status-text'); |
|
const playerSymbol = document.getElementById('player-symbol'); |
|
const playerOneName = document.getElementById('player-one-name'); |
|
const playerTwoName = document.getElementById('player-two-name'); |
|
const playerCount = document.getElementById('player-count'); |
|
|
|
|
|
let gameId = null; |
|
let player = null; |
|
let isCurrentTurn = false; |
|
let gameBoardState = Array(9).fill(null); |
|
|
|
|
|
const socket = io("https://tic-tac-toe-socketio.adaptable.app/", { |
|
transports: ['websocket'] |
|
}); |
|
|
|
|
|
document.getElementById('create-game').addEventListener('click', createGame); |
|
document.getElementById('join-game').addEventListener('click', joinGame); |
|
document.getElementById('copy-id').addEventListener('click', copyGameId); |
|
document.getElementById('play-again').addEventListener('click', restartGame); |
|
document.getElementById('new-game').addEventListener('click', resetGame); |
|
document.getElementById('leave-game').addEventListener('click', leaveGame); |
|
document.getElementById('copy-link').addEventListener('click', copyInviteLink); |
|
|
|
|
|
socket.on('connect', () => { |
|
console.log('Connected to server with socket id:', socket.id); |
|
}); |
|
|
|
socket.on('gameCreated', (id) => { |
|
gameId = id; |
|
document.getElementById('game-code').textContent = id; |
|
gameIdDisplay.classList.remove('hidden'); |
|
waitingRoom.classList.remove('hidden'); |
|
}); |
|
|
|
socket.on('playerJoined', (players) => { |
|
playerCount.textContent = players.length; |
|
if (players.length === 2) { |
|
|
|
setTimeout(() => { |
|
lobbySection.classList.add('hidden'); |
|
gameSection.classList.remove('hidden'); |
|
waitingRoom.classList.add('hidden'); |
|
}, 1000); |
|
} |
|
}); |
|
|
|
socket.on('playerLeft', (players) => { |
|
playerCount.textContent = players.length; |
|
}); |
|
|
|
socket.on('gameJoined', (gameData) => { |
|
gameId = gameData.id; |
|
player = gameData.players.find(p => p.id === socket.id); |
|
|
|
|
|
playerOneName.textContent = gameData.players[0].name; |
|
playerTwoName.textContent = gameData.players[1]?.name || 'Waiting...'; |
|
playerCount.textContent = gameData.players.length; |
|
|
|
|
|
if (player.id === gameData.players[0].id) { |
|
playerSymbol.textContent = 'X'; |
|
playerSymbol.classList.add('text-primary'); |
|
} else if (player.id === gameData.players[1]?.id) { |
|
playerSymbol.textContent = 'O'; |
|
playerSymbol.classList.add('text-secondary'); |
|
} |
|
|
|
|
|
if (gameData.players.length === 2) { |
|
lobbySection.classList.add('hidden'); |
|
gameSection.classList.remove('hidden'); |
|
waitingRoom.classList.add('hidden'); |
|
updateBoard(gameData.board); |
|
updateGameState(gameData); |
|
} else { |
|
|
|
gameIdDisplay.classList.remove('hidden'); |
|
waitingRoom.classList.remove('hidden'); |
|
document.getElementById('game-code').textContent = gameId; |
|
} |
|
|
|
gameIdLabel.textContent = `Game ID: ${gameId}`; |
|
}); |
|
|
|
socket.on('updateBoard', (board) => { |
|
updateBoard(board); |
|
gameBoardState = board; |
|
}); |
|
|
|
socket.on('updateGameState', (gameData) => { |
|
updateGameState(gameData); |
|
}); |
|
|
|
socket.on('gameOver', (result) => { |
|
showGameResult(result); |
|
createConfetti(); |
|
}); |
|
|
|
socket.on('error', (message) => { |
|
showNotification(message); |
|
}); |
|
|
|
|
|
function createGame() { |
|
const playerName = `Player_${Math.floor(Math.random() * 1000)}`; |
|
socket.emit('createGame', playerName); |
|
} |
|
|
|
function joinGame() { |
|
const id = gameIdInput.value.trim(); |
|
if (!id) { |
|
showNotification('Please enter a Game ID!'); |
|
return; |
|
} |
|
|
|
const playerName = `Player_${Math.floor(Math.random() * 1000)}`; |
|
socket.emit('joinGame', { gameId: id, playerName }); |
|
} |
|
|
|
function cellClick(index) { |
|
if (isCurrentTurn && !gameBoardState[index]) { |
|
const chipAnimation = document.createElement('div'); |
|
chipAnimation.className = 'chip-animation'; |
|
const cell = document.getElementById(`cell-${index}`); |
|
cell.appendChild(chipAnimation); |
|
|
|
setTimeout(() => { |
|
socket.emit('makeMove', { |
|
gameId, |
|
index, |
|
symbol: player.symbol |
|
}); |
|
}, 300); |
|
} |
|
} |
|
|
|
function updateBoard(board) { |
|
gameBoardState = [...board]; |
|
gameBoard.innerHTML = ''; |
|
|
|
for (let i = 0; i < 9; i++) { |
|
const cell = document.createElement('div'); |
|
cell.id = `cell-${i}`; |
|
cell.className = 'grid-cell aspect-square bg-white rounded-xl border-2 border-accent shadow-md flex items-center justify-center cursor-pointer hover:shadow-lg transition-all'; |
|
cell.addEventListener('click', () => cellClick(i)); |
|
|
|
if (board[i]) { |
|
const symbol = document.createElement('div'); |
|
symbol.className = 'text-4xl font-bold'; |
|
|
|
if (board[i] === 'X') { |
|
symbol.textContent = 'X'; |
|
symbol.classList.add('text-primary'); |
|
} else { |
|
symbol.textContent = 'O'; |
|
symbol.classList.add('text-secondary'); |
|
} |
|
|
|
cell.appendChild(symbol); |
|
} |
|
|
|
gameBoard.appendChild(cell); |
|
} |
|
} |
|
|
|
function updateGameState(gameData) { |
|
isCurrentTurn = gameData.currentPlayerId === socket.id; |
|
player = gameData.players.find(p => p.id === socket.id); |
|
|
|
|
|
if (gameData.winner) { |
|
if (gameData.winner === 'draw') { |
|
statusText.textContent = "It's a draw!"; |
|
} else if (gameData.winner === player.symbol) { |
|
statusText.textContent = "You won!"; |
|
} else { |
|
statusText.textContent = "You lost!"; |
|
} |
|
} else { |
|
if (isCurrentTurn) { |
|
statusText.textContent = `Your turn! (${player.symbol})`; |
|
document.getElementById('game-status').classList.remove('bg-gradient-to-r', 'from-purple-600', 'to-indigo-700'); |
|
document.getElementById('game-status').classList.add('bg-gradient-to-r', 'from-blue-600', 'to-indigo-700'); |
|
} else { |
|
statusText.textContent = `Waiting for opponent...`; |
|
document.getElementById('game-status').classList.remove('bg-gradient-to-r', 'from-blue-600', 'to-indigo-700'); |
|
document.getElementById('game-status').classList.add('bg-gradient-to-r', 'from-purple-600', 'to-indigo-700'); |
|
} |
|
} |
|
|
|
|
|
if (gameData.winningCombo) { |
|
gameData.winningCombo.forEach(index => { |
|
const cell = document.getElementById(`cell-${index}`); |
|
cell.classList.add('winning-cell'); |
|
}); |
|
} |
|
} |
|
|
|
function showGameResult(result) { |
|
gameOver.classList.remove('hidden'); |
|
|
|
if (result.winner === 'draw') { |
|
resultText.textContent = "Game ended in a draw!"; |
|
resultText.className = "text-2xl font-bold text-gray-800"; |
|
} else { |
|
resultText.textContent = `${result.winner} wins the game!`; |
|
resultText.className = `text-2xl font-bold ${result.winner === 'X' ? 'text-primary' : 'text-secondary'}`; |
|
} |
|
} |
|
|
|
function restartGame() { |
|
socket.emit('restartGame', gameId); |
|
gameOver.classList.add('hidden'); |
|
|
|
|
|
const cells = document.querySelectorAll('.grid-cell'); |
|
cells.forEach(cell => cell.classList.remove('winning-cell')); |
|
} |
|
|
|
function resetGame() { |
|
|
|
lobbySection.classList.remove('hidden'); |
|
gameSection.classList.add('hidden'); |
|
gameOver.classList.add('hidden'); |
|
gameIdDisplay.classList.add('hidden'); |
|
waitingRoom.classList.add('hidden'); |
|
|
|
|
|
gameId = null; |
|
player = null; |
|
isCurrentTurn = false; |
|
gameBoardState = Array(9).fill(null); |
|
|
|
|
|
document.getElementById('game-id').value = ''; |
|
gameBoard.innerHTML = ''; |
|
|
|
|
|
socket.emit('leaveGame', gameId); |
|
} |
|
|
|
function leaveGame() { |
|
socket.emit('leaveGame', gameId); |
|
resetGame(); |
|
} |
|
|
|
function copyGameId() { |
|
navigator.clipboard.writeText(gameId) |
|
.then(() => showNotification('Game ID copied to clipboard!')) |
|
.catch(err => showNotification('Failed to copy: ' + err)); |
|
} |
|
|
|
function copyInviteLink() { |
|
const link = `${window.location.origin}/?game=${gameId}`; |
|
navigator.clipboard.writeText(link) |
|
.then(() => showNotification('Invite link copied to clipboard!')) |
|
.catch(err => showNotification('Failed to copy: ' + err)); |
|
} |
|
|
|
function showNotification(message) { |
|
|
|
const notification = document.createElement('div'); |
|
notification.className = 'fixed bottom-5 right-5 bg-dark text-white px-4 py-3 rounded-lg shadow-lg transform transition-all duration-300'; |
|
notification.innerHTML = ` |
|
<div class="flex items-center gap-2"> |
|
<i class="fas fa-info-circle"></i> |
|
<span>${message}</span> |
|
</div> |
|
`; |
|
|
|
document.body.appendChild(notification); |
|
|
|
|
|
setTimeout(() => { |
|
notification.style.transform = 'translateX(100%)'; |
|
setTimeout(() => { |
|
document.body.removeChild(notification); |
|
}, 300); |
|
}, 3000); |
|
} |
|
|
|
function createConfetti() { |
|
const colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6', '#ec4899']; |
|
const gameBoardRect = gameBoard.getBoundingClientRect(); |
|
|
|
for (let i = 0; i < 150; i++) { |
|
const confetti = document.createElement('div'); |
|
const colorIndex = Math.floor(Math.random() * colors.length); |
|
|
|
confetti.style.backgroundColor = colors[colorIndex]; |
|
confetti.className = 'confetti'; |
|
confetti.style.left = Math.random() * gameBoardRect.width + 'px'; |
|
confetti.style.top = Math.random() * gameBoardRect.height + 'px'; |
|
confetti.style.animationDuration = (Math.random() * 3 + 2) + 's'; |
|
confetti.style.opacity = Math.random(); |
|
confetti.style.width = Math.random() * 10 + 5 + 'px'; |
|
confetti.style.height = confetti.style.width; |
|
|
|
gameBoard.appendChild(confetti); |
|
|
|
setTimeout(() => { |
|
confetti.remove(); |
|
}, 4000); |
|
} |
|
} |
|
|
|
|
|
function initializeBoard() { |
|
for (let i = 0; i < 9; i++) { |
|
const cell = document.createElement('div'); |
|
cell.id = `cell-${i}`; |
|
cell.className = 'grid-cell aspect-square bg-white rounded-xl border-2 border-accent shadow-md flex items-center justify-center cursor-pointer hover:shadow-lg transition-all'; |
|
cell.addEventListener('click', () => cellClick(i)); |
|
gameBoard.appendChild(cell); |
|
} |
|
} |
|
|
|
initializeBoard(); |
|
|
|
|
|
function checkUrlParams() { |
|
const params = new URLSearchParams(window.location.search); |
|
if (params.has('game')) { |
|
const gameId = params.get('game'); |
|
gameIdInput.value = gameId; |
|
|
|
showNotification("Game ID detected! Click Join Game to continue."); |
|
} |
|
} |
|
|
|
checkUrlParams(); |
|
</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=LULDev/tictactoe" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |