|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Responsive Tetris Game</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<style> |
|
|
|
|
|
.cell { |
|
|
box-sizing: border-box; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
.cell-filled { |
|
|
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5); |
|
|
} |
|
|
|
|
|
@keyframes flash { |
|
|
0% { opacity: 1; } |
|
|
50% { opacity: 0.5; } |
|
|
100% { opacity: 1; } |
|
|
} |
|
|
|
|
|
.flash-animation { |
|
|
animation: flash 0.3s 2; |
|
|
} |
|
|
|
|
|
|
|
|
.cell-I { background-color: #00FFFF; } |
|
|
.cell-O { background-color: #FFFF00; } |
|
|
.cell-T { background-color: #AA00FF; } |
|
|
.cell-S { background-color: #00FF00; } |
|
|
.cell-Z { background-color: #FF0000; } |
|
|
.cell-J { background-color: #0000FF; } |
|
|
.cell-L { background-color: #FFAA00; } |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> |
|
|
<div class="text-center mb-4"> |
|
|
<h1 class="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-600 mb-2">Tetris</h1> |
|
|
<div class="flex justify-center gap-8"> |
|
|
<div class="text-xl"> |
|
|
<span class="text-gray-300">Score:</span> |
|
|
<span id="score" class="font-bold ml-2">0</span> |
|
|
</div> |
|
|
<div class="text-xl"> |
|
|
<span class="text-gray-300">Level:</span> |
|
|
<span id="level" class="font-bold ml-2">1</span> |
|
|
</div> |
|
|
<div class="text-xl"> |
|
|
<span class="text-gray-300">Lines:</span> |
|
|
<span id="lines" class="font-bold ml-2">0</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex flex-col md:flex-row items-center gap-8"> |
|
|
<div class="relative"> |
|
|
<div id="game-board" class="grid grid-cols-10 grid-rows-20 gap-0 bg-gray-800 border-2 border-gray-700 rounded-md overflow-hidden"></div> |
|
|
<div id="game-over" class="absolute inset-0 bg-black bg-opacity-80 flex flex-col items-center justify-center hidden"> |
|
|
<h2 class="text-3xl font-bold text-red-500 mb-4">Game Over!</h2> |
|
|
<button id="restart-btn" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 rounded-md font-bold transition">Play Again</button> |
|
|
</div> |
|
|
<div id="pause-screen" class="absolute inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden"> |
|
|
<h2 class="text-3xl font-bold text-yellow-400">Paused</h2> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex flex-col gap-6"> |
|
|
<div class="bg-gray-800 p-4 rounded-md border border-gray-700"> |
|
|
<h3 class="text-xl font-semibold mb-2 text-center">Next Piece</h3> |
|
|
<div id="next-piece" class="grid grid-cols-4 grid-rows-4 gap-0 w-24 h-24 mx-auto"></div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-gray-800 p-4 rounded-md border border-gray-700"> |
|
|
<h3 class="text-xl font-semibold mb-2 text-center">Controls</h3> |
|
|
<div class="grid grid-cols-3 gap-2 text-center"> |
|
|
<div class="bg-gray-700 p-2 rounded">← Left</div> |
|
|
<div class="bg-gray-700 p-2 rounded">→ Right</div> |
|
|
<div class="bg-gray-700 p-2 rounded">↑ Rotate</div> |
|
|
<div class="bg-gray-700 p-2 rounded">↓ Soft Drop</div> |
|
|
<div class="bg-gray-700 p-2 rounded">Space Hard Drop</div> |
|
|
<div class="bg-gray-700 p-2 rounded">P Pause</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<button id="pause-btn" class="px-4 py-2 bg-yellow-600 hover:bg-yellow-700 rounded-md font-bold transition">Pause</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-8 text-gray-400 text-sm"> |
|
|
<p>Use keyboard or touch controls to play. Clear lines to score points!</p> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
|
|
const COLS = 10; |
|
|
const ROWS = 20; |
|
|
const BLOCK_SIZE = 30; |
|
|
const NEXT_PIECE_SIZE = 4; |
|
|
|
|
|
|
|
|
let board = Array(ROWS).fill().map(() => Array(COLS).fill(0)); |
|
|
let currentPiece = null; |
|
|
let nextPiece = null; |
|
|
let score = 0; |
|
|
let level = 1; |
|
|
let lines = 0; |
|
|
let gameOver = false; |
|
|
let isPaused = false; |
|
|
let dropInterval = null; |
|
|
let dropSpeed = 1000; |
|
|
|
|
|
|
|
|
const gameBoard = document.getElementById('game-board'); |
|
|
const nextPieceDisplay = document.getElementById('next-piece'); |
|
|
const scoreDisplay = document.getElementById('score'); |
|
|
const levelDisplay = document.getElementById('level'); |
|
|
const linesDisplay = document.getElementById('lines'); |
|
|
const gameOverScreen = document.getElementById('game-over'); |
|
|
const pauseScreen = document.getElementById('pause-screen'); |
|
|
const restartBtn = document.getElementById('restart-btn'); |
|
|
const pauseBtn = document.getElementById('pause-btn'); |
|
|
|
|
|
|
|
|
const SHAPES = { |
|
|
I: [ |
|
|
[0, 0, 0, 0], |
|
|
[1, 1, 1, 1], |
|
|
[0, 0, 0, 0], |
|
|
[0, 0, 0, 0] |
|
|
], |
|
|
O: [ |
|
|
[1, 1], |
|
|
[1, 1] |
|
|
], |
|
|
T: [ |
|
|
[0, 1, 0], |
|
|
[1, 1, 1], |
|
|
[0, 0, 0] |
|
|
], |
|
|
S: [ |
|
|
[0, 1, 1], |
|
|
[1, 1, 0], |
|
|
[0, 0, 0] |
|
|
], |
|
|
Z: [ |
|
|
[1, 1, 0], |
|
|
[0, 1, 1], |
|
|
[0, 0, 0] |
|
|
], |
|
|
J: [ |
|
|
[1, 0, 0], |
|
|
[1, 1, 1], |
|
|
[0, 0, 0] |
|
|
], |
|
|
L: [ |
|
|
[0, 0, 1], |
|
|
[1, 1, 1], |
|
|
[0, 0, 0] |
|
|
] |
|
|
}; |
|
|
|
|
|
const COLORS = { |
|
|
I: 'cell-I', |
|
|
O: 'cell-O', |
|
|
T: 'cell-T', |
|
|
S: 'cell-S', |
|
|
Z: 'cell-Z', |
|
|
J: 'cell-J', |
|
|
L: 'cell-L' |
|
|
}; |
|
|
|
|
|
|
|
|
function initBoard() { |
|
|
gameBoard.innerHTML = ''; |
|
|
gameBoard.style.width = `${COLS * BLOCK_SIZE}px`; |
|
|
gameBoard.style.height = `${ROWS * BLOCK_SIZE}px`; |
|
|
|
|
|
for (let row = 0; row < ROWS; row++) { |
|
|
for (let col = 0; col < COLS; col++) { |
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'cell w-full h-full'; |
|
|
cell.dataset.row = row; |
|
|
cell.dataset.col = col; |
|
|
gameBoard.appendChild(cell); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function initNextPieceDisplay() { |
|
|
nextPieceDisplay.innerHTML = ''; |
|
|
nextPieceDisplay.style.width = `${NEXT_PIECE_SIZE * BLOCK_SIZE}px`; |
|
|
nextPieceDisplay.style.height = `${NEXT_PIECE_SIZE * BLOCK_SIZE}px`; |
|
|
|
|
|
for (let row = 0; row < NEXT_PIECE_SIZE; row++) { |
|
|
for (let col = 0; col < NEXT_PIECE_SIZE; col++) { |
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'cell w-full h-full'; |
|
|
cell.dataset.row = row; |
|
|
cell.dataset.col = col; |
|
|
nextPieceDisplay.appendChild(cell); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function createPiece() { |
|
|
const types = Object.keys(SHAPES); |
|
|
const type = types[Math.floor(Math.random() * types.length)]; |
|
|
const shape = SHAPES[type]; |
|
|
|
|
|
|
|
|
const startCol = type === 'O' ? Math.floor(COLS / 2) - 1 : Math.floor(COLS / 2) - 2; |
|
|
|
|
|
return { |
|
|
shape, |
|
|
type, |
|
|
color: COLORS[type], |
|
|
x: startCol, |
|
|
y: 0 |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
function drawPiece() { |
|
|
if (!currentPiece) return; |
|
|
|
|
|
for (let row = 0; row < currentPiece.shape.length; row++) { |
|
|
for (let col = 0; col < currentPiece.shape[row].length; col++) { |
|
|
if (currentPiece.shape[row][col]) { |
|
|
const boardRow = currentPiece.y + row; |
|
|
const boardCol = currentPiece.x + col; |
|
|
|
|
|
if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) { |
|
|
const cellIndex = boardRow * COLS + boardCol; |
|
|
const cell = gameBoard.children[cellIndex]; |
|
|
cell.className = `cell cell-filled ${currentPiece.color}`; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function drawNextPiece() { |
|
|
if (!nextPiece) return; |
|
|
|
|
|
|
|
|
for (let i = 0; i < nextPieceDisplay.children.length; i++) { |
|
|
nextPieceDisplay.children[i].className = 'cell w-full h-full'; |
|
|
} |
|
|
|
|
|
|
|
|
const offsetX = Math.floor((NEXT_PIECE_SIZE - nextPiece.shape[0].length) / 2); |
|
|
const offsetY = Math.floor((NEXT_PIECE_SIZE - nextPiece.shape.length) / 2); |
|
|
|
|
|
for (let row = 0; row < nextPiece.shape.length; row++) { |
|
|
for (let col = 0; col < nextPiece.shape[row].length; col++) { |
|
|
if (nextPiece.shape[row][col]) { |
|
|
const displayRow = row + offsetY; |
|
|
const displayCol = col + offsetX; |
|
|
const cellIndex = displayRow * NEXT_PIECE_SIZE + displayCol; |
|
|
const cell = nextPieceDisplay.children[cellIndex]; |
|
|
cell.className = `cell cell-filled ${nextPiece.color}`; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function clearPiece() { |
|
|
if (!currentPiece) return; |
|
|
|
|
|
for (let row = 0; row < currentPiece.shape.length; row++) { |
|
|
for (let col = 0; col < currentPiece.shape[row].length; col++) { |
|
|
if (currentPiece.shape[row][col]) { |
|
|
const boardRow = currentPiece.y + row; |
|
|
const boardCol = currentPiece.x + col; |
|
|
|
|
|
if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) { |
|
|
const cellIndex = boardRow * COLS + boardCol; |
|
|
const cell = gameBoard.children[cellIndex]; |
|
|
cell.className = 'cell w-full h-full'; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function isValidMove(offsetX, offsetY, rotatedShape = null) { |
|
|
const shape = rotatedShape || currentPiece.shape; |
|
|
|
|
|
for (let row = 0; row < shape.length; row++) { |
|
|
for (let col = 0; col < shape[row].length; col++) { |
|
|
if (shape[row][col]) { |
|
|
const newX = currentPiece.x + col + offsetX; |
|
|
const newY = currentPiece.y + row + offsetY; |
|
|
|
|
|
|
|
|
if (newX < 0 || newX >= COLS || newY >= ROWS) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
if (newY >= 0 && board[newY][newX] && offsetY >= 0) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
function rotatePiece() { |
|
|
if (!currentPiece) return; |
|
|
|
|
|
|
|
|
const rotated = []; |
|
|
for (let col = 0; col < currentPiece.shape[0].length; col++) { |
|
|
const newRow = []; |
|
|
for (let row = currentPiece.shape.length - 1; row >= 0; row--) { |
|
|
newRow.push(currentPiece.shape[row][col]); |
|
|
} |
|
|
rotated.push(newRow); |
|
|
} |
|
|
|
|
|
|
|
|
if (isValidMove(0, 0, rotated)) { |
|
|
clearPiece(); |
|
|
currentPiece.shape = rotated; |
|
|
drawPiece(); |
|
|
} else { |
|
|
|
|
|
const kicks = [-1, 1, -2, 2]; |
|
|
for (const kick of kicks) { |
|
|
if (isValidMove(kick, 0, rotated)) { |
|
|
clearPiece(); |
|
|
currentPiece.shape = rotated; |
|
|
currentPiece.x += kick; |
|
|
drawPiece(); |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function moveLeft() { |
|
|
if (!currentPiece || gameOver || isPaused) return; |
|
|
|
|
|
if (isValidMove(-1, 0)) { |
|
|
clearPiece(); |
|
|
currentPiece.x--; |
|
|
drawPiece(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function moveRight() { |
|
|
if (!currentPiece || gameOver || isPaused) return; |
|
|
|
|
|
if (isValidMove(1, 0)) { |
|
|
clearPiece(); |
|
|
currentPiece.x++; |
|
|
drawPiece(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function moveDown() { |
|
|
if (!currentPiece || gameOver || isPaused) return; |
|
|
|
|
|
if (isValidMove(0, 1)) { |
|
|
clearPiece(); |
|
|
currentPiece.y++; |
|
|
drawPiece(); |
|
|
return true; |
|
|
} else { |
|
|
lockPiece(); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function hardDrop() { |
|
|
if (!currentPiece || gameOver || isPaused) return; |
|
|
|
|
|
while (moveDown()) { |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function lockPiece() { |
|
|
if (!currentPiece) return; |
|
|
|
|
|
|
|
|
for (let row = 0; row < currentPiece.shape.length; row++) { |
|
|
for (let col = 0; col < currentPiece.shape[row].length; col++) { |
|
|
if (currentPiece.shape[row][col]) { |
|
|
const boardRow = currentPiece.y + row; |
|
|
const boardCol = currentPiece.x + col; |
|
|
|
|
|
if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) { |
|
|
board[boardRow][boardCol] = currentPiece.color; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
checkLines(); |
|
|
|
|
|
|
|
|
currentPiece = nextPiece; |
|
|
nextPiece = createPiece(); |
|
|
drawNextPiece(); |
|
|
|
|
|
|
|
|
if (!isValidMove(0, 0)) { |
|
|
gameOver = true; |
|
|
clearInterval(dropInterval); |
|
|
gameOverScreen.classList.remove('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function checkLines() { |
|
|
let linesCleared = 0; |
|
|
|
|
|
for (let row = ROWS - 1; row >= 0; row--) { |
|
|
if (board[row].every(cell => cell !== 0)) { |
|
|
|
|
|
linesCleared++; |
|
|
|
|
|
|
|
|
for (let col = 0; col < COLS; col++) { |
|
|
const cellIndex = row * COLS + col; |
|
|
const cell = gameBoard.children[cellIndex]; |
|
|
cell.classList.add('flash-animation'); |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
for (let r = row; r > 0; r--) { |
|
|
board[r] = [...board[r - 1]]; |
|
|
} |
|
|
board[0] = Array(COLS).fill(0); |
|
|
|
|
|
|
|
|
redrawBoard(); |
|
|
}, 300); |
|
|
} |
|
|
} |
|
|
|
|
|
if (linesCleared > 0) { |
|
|
|
|
|
const points = [0, 40, 100, 300, 1200]; |
|
|
score += points[linesCleared] * level; |
|
|
lines += linesCleared; |
|
|
|
|
|
|
|
|
level = Math.floor(lines / 10) + 1; |
|
|
|
|
|
|
|
|
dropSpeed = Math.max(100, 1000 - (level - 1) * 100); |
|
|
|
|
|
|
|
|
updateDisplays(); |
|
|
|
|
|
|
|
|
clearInterval(dropInterval); |
|
|
dropInterval = setInterval(moveDown, dropSpeed); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function redrawBoard() { |
|
|
for (let row = 0; row < ROWS; row++) { |
|
|
for (let col = 0; col < COLS; col++) { |
|
|
const cellIndex = row * COLS + col; |
|
|
const cell = gameBoard.children[cellIndex]; |
|
|
|
|
|
|
|
|
cell.classList.remove('flash-animation'); |
|
|
|
|
|
if (board[row][col]) { |
|
|
cell.className = `cell cell-filled ${board[row][col]}`; |
|
|
} else { |
|
|
cell.className = 'cell w-full h-full'; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function updateDisplays() { |
|
|
scoreDisplay.textContent = score; |
|
|
levelDisplay.textContent = level; |
|
|
linesDisplay.textContent = lines; |
|
|
} |
|
|
|
|
|
|
|
|
function startGame() { |
|
|
|
|
|
board = Array(ROWS).fill().map(() => Array(COLS).fill(0)); |
|
|
score = 0; |
|
|
level = 1; |
|
|
lines = 0; |
|
|
gameOver = false; |
|
|
dropSpeed = 1000; |
|
|
|
|
|
|
|
|
currentPiece = createPiece(); |
|
|
nextPiece = createPiece(); |
|
|
|
|
|
|
|
|
updateDisplays(); |
|
|
|
|
|
|
|
|
redrawBoard(); |
|
|
drawPiece(); |
|
|
drawNextPiece(); |
|
|
|
|
|
|
|
|
gameOverScreen.classList.add('hidden'); |
|
|
|
|
|
|
|
|
clearInterval(dropInterval); |
|
|
dropInterval = setInterval(moveDown, dropSpeed); |
|
|
} |
|
|
|
|
|
|
|
|
function togglePause() { |
|
|
if (gameOver) return; |
|
|
|
|
|
isPaused = !isPaused; |
|
|
|
|
|
if (isPaused) { |
|
|
clearInterval(dropInterval); |
|
|
pauseScreen.classList.remove('hidden'); |
|
|
} else { |
|
|
dropInterval = setInterval(moveDown, dropSpeed); |
|
|
pauseScreen.classList.add('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
|
if (gameOver && e.key === 'Enter') { |
|
|
startGame(); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (isPaused && e.key !== 'p') return; |
|
|
|
|
|
switch (e.key) { |
|
|
case 'ArrowLeft': |
|
|
moveLeft(); |
|
|
break; |
|
|
case 'ArrowRight': |
|
|
moveRight(); |
|
|
break; |
|
|
case 'ArrowDown': |
|
|
moveDown(); |
|
|
break; |
|
|
case 'ArrowUp': |
|
|
rotatePiece(); |
|
|
break; |
|
|
case ' ': |
|
|
hardDrop(); |
|
|
break; |
|
|
case 'p': |
|
|
case 'P': |
|
|
togglePause(); |
|
|
break; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
let touchStartX = 0; |
|
|
let touchStartY = 0; |
|
|
|
|
|
gameBoard.addEventListener('touchstart', (e) => { |
|
|
if (gameOver || isPaused) return; |
|
|
|
|
|
touchStartX = e.touches[0].clientX; |
|
|
touchStartY = e.touches[0].clientY; |
|
|
e.preventDefault(); |
|
|
}, { passive: false }); |
|
|
|
|
|
gameBoard.addEventListener('touchmove', (e) => { |
|
|
if (gameOver || isPaused) return; |
|
|
|
|
|
const touchX = e.touches[0].clientX; |
|
|
const touchY = e.touches[0].clientY; |
|
|
const diffX = touchX - touchStartX; |
|
|
const diffY = touchY - touchStartY; |
|
|
|
|
|
|
|
|
if (Math.abs(diffX) > Math.abs(diffY)) { |
|
|
if (diffX > 30) { |
|
|
moveRight(); |
|
|
touchStartX = touchX; |
|
|
} else if (diffX < -30) { |
|
|
moveLeft(); |
|
|
touchStartX = touchX; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (diffY > 30) { |
|
|
moveDown(); |
|
|
touchStartY = touchY; |
|
|
} |
|
|
|
|
|
e.preventDefault(); |
|
|
}, { passive: false }); |
|
|
|
|
|
gameBoard.addEventListener('touchend', (e) => { |
|
|
if (gameOver || isPaused) return; |
|
|
|
|
|
const touchEndX = e.changedTouches[0].clientX; |
|
|
const touchEndY = e.changedTouches[0].clientY; |
|
|
const diffX = touchEndX - touchStartX; |
|
|
const diffY = touchEndY - touchStartY; |
|
|
|
|
|
|
|
|
if (Math.abs(diffX) < 10 && Math.abs(diffY) < 10) { |
|
|
rotatePiece(); |
|
|
} |
|
|
|
|
|
|
|
|
if (diffY < -50) { |
|
|
hardDrop(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
restartBtn.addEventListener('click', startGame); |
|
|
pauseBtn.addEventListener('click', togglePause); |
|
|
|
|
|
|
|
|
initBoard(); |
|
|
initNextPieceDisplay(); |
|
|
startGame(); |
|
|
}); |
|
|
</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=bozhong/tetris" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |