| <!DOCTYPE html> |
| <html lang="ru"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Пиксельный майнинг</title> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| :root { |
| --bg-color: #1a1a2e; |
| --panel-bg: #16213e; |
| --text-color: #e6e6e6; |
| --accent-color: #4cc9f0; |
| --rare-silver: #c0c0c0; |
| --rare-gold: #ffd700; |
| --rare-diamond: #b9f2ff; |
| --pixel-size: 16px; |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| -webkit-tap-highlight-color: transparent; |
| } |
| |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| background-color: var(--bg-color); |
| color: var(--text-color); |
| min-height: 100vh; |
| overflow-x: hidden; |
| } |
| |
| .container { |
| max-width: 100%; |
| padding: 12px; |
| margin: 0 auto; |
| } |
| |
| header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 8px 12px; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
| margin-bottom: 12px; |
| } |
| |
| .logo { |
| font-weight: bold; |
| font-size: 1.2rem; |
| color: var(--accent-color); |
| } |
| |
| .user-info { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .user-avatar { |
| width: 32px; |
| height: 32px; |
| border-radius: 50%; |
| background-color: var(--panel-bg); |
| } |
| |
| .mining-section { |
| display: flex; |
| flex-direction: column; |
| gap: 16px; |
| margin-bottom: 20px; |
| } |
| |
| .canvas-container { |
| position: relative; |
| margin: 0 auto; |
| border: 2px solid var(--panel-bg); |
| border-radius: 8px; |
| overflow: hidden; |
| background-color: #111; |
| } |
| |
| .pixel-canvas { |
| display: grid; |
| grid-template-columns: repeat(var(--cols), var(--pixel-size)); |
| grid-template-rows: repeat(var(--rows), var(--pixel-size)); |
| } |
| |
| .pixel { |
| background-color: #222; |
| border: 1px solid #111; |
| cursor: pointer; |
| transition: background-color 0.2s; |
| } |
| |
| .pixel:hover { |
| opacity: 0.9; |
| } |
| |
| .pixel-mined { |
| background-color: #444; |
| } |
| |
| .pixel-special { |
| position: relative; |
| } |
| |
| .pixel-silver { |
| background-color: var(--rare-silver); |
| } |
| |
| .pixel-gold { |
| background-color: var(--rare-gold); |
| } |
| |
| .pixel-diamond { |
| background-color: var(--rare-diamond); |
| } |
| |
| .mining-info { |
| background-color: var(--panel-bg); |
| border-radius: 8px; |
| padding: 12px; |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| } |
| |
| .block-info { |
| display: flex; |
| justify-content: space-between; |
| } |
| |
| .progress-container { |
| background-color: #333; |
| border-radius: 999px; |
| height: 8px; |
| margin-top: 4px; |
| overflow: hidden; |
| } |
| |
| .progress-bar { |
| height: 100%; |
| background-color: var(--accent-color); |
| border-radius: 999px; |
| transition: width 0.3s; |
| } |
| |
| .mining-button { |
| background-color: var(--accent-color); |
| color: #111; |
| border: none; |
| border-radius: 8px; |
| padding: 12px; |
| font-weight: bold; |
| font-size: 1rem; |
| cursor: pointer; |
| transition: transform 0.2s, background-color 0.2s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| } |
| |
| .mining-button:active { |
| transform: scale(0.98); |
| background-color: #3aa8d8; |
| } |
| |
| .mining-button i { |
| font-size: 1.2rem; |
| } |
| |
| .stats-section { |
| background-color: var(--panel-bg); |
| border-radius: 8px; |
| padding: 12px; |
| margin-bottom: 16px; |
| } |
| |
| .stats-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 12px; |
| margin-top: 12px; |
| } |
| |
| .stat-item { |
| background-color: rgba(0, 0, 0, 0.2); |
| border-radius: 6px; |
| padding: 8px; |
| text-align: center; |
| } |
| |
| .stat-value { |
| font-size: 1.2rem; |
| font-weight: bold; |
| margin-top: 4px; |
| color: var(--accent-color); |
| } |
| |
| .tab-container { |
| display: flex; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
| margin-bottom: 12px; |
| } |
| |
| .tab { |
| padding: 8px 12px; |
| cursor: pointer; |
| position: relative; |
| } |
| |
| .tab.active { |
| color: var(--accent-color); |
| } |
| |
| .tab.active::after { |
| content: ''; |
| position: absolute; |
| bottom: -1px; |
| left: 0; |
| width: 100%; |
| height: 2px; |
| background-color: var(--accent-color); |
| } |
| |
| .tab-content { |
| display: none; |
| } |
| |
| .tab-content.active { |
| display: block; |
| } |
| |
| .leaderboard { |
| width: 100%; |
| border-collapse: collapse; |
| } |
| |
| .leaderboard th { |
| text-align: left; |
| padding: 8px 12px; |
| background-color: rgba(0, 0, 0, 0.2); |
| } |
| |
| .leaderboard td { |
| padding: 8px 12px; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .leaderboard tr:last-child td { |
| border-bottom: none; |
| } |
| |
| .medal { |
| width: 18px; |
| height: 18px; |
| border-radius: 50%; |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| margin-right: 6px; |
| } |
| |
| .medal-gold { |
| background-color: var(--rare-gold); |
| color: #333; |
| } |
| |
| .medal-silver { |
| background-color: var(--rare-silver); |
| color: #333; |
| } |
| |
| .medal-bronze { |
| background-color: #cd7f32; |
| color: #333; |
| } |
| |
| .history-item { |
| display: flex; |
| justify-content: space-between; |
| padding: 8px 0; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .history-item:last-child { |
| border-bottom: none; |
| } |
| |
| .rare-icon { |
| font-size: 0.8rem; |
| margin-left: 4px; |
| } |
| |
| .pixel-tooltip { |
| position: absolute; |
| top: -30px; |
| left: 50%; |
| transform: translateX(-50%); |
| background-color: rgba(0, 0, 0, 0.8); |
| padding: 4px 8px; |
| border-radius: 4px; |
| font-size: 0.8rem; |
| white-space: nowrap; |
| z-index: 10; |
| opacity: 0; |
| transition: opacity 0.2s; |
| } |
| |
| .pixel:hover .pixel-tooltip { |
| opacity: 1; |
| } |
| |
| .avatar-grid { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 8px; |
| margin-top: 12px; |
| } |
| |
| .avatar-item { |
| aspect-ratio: 1/1; |
| background-color: #333; |
| border-radius: 8px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: transform 0.2s; |
| overflow: hidden; |
| } |
| |
| .avatar-item img { |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| } |
| |
| .avatar-item:active { |
| transform: scale(0.95); |
| } |
| |
| .avatar-item.selected { |
| border: 2px solid var(--accent-color); |
| } |
| |
| @media (min-width: 500px) { |
| :root { |
| --pixel-size: 20px; |
| } |
| |
| .container { |
| max-width: 500px; |
| } |
| |
| .stats-grid { |
| grid-template-columns: repeat(4, 1fr); |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <header> |
| <div class="logo">PixelMiner</div> |
| <div class="user-info"> |
| <span id="user-coins">0 PX</span> |
| <div class="user-avatar" id="user-avatar"></div> |
| </div> |
| </header> |
| |
| <div class="mining-section"> |
| <div class="canvas-container"> |
| <div class="pixel-canvas" id="pixel-canvas"></div> |
| </div> |
| |
| <div class="mining-info"> |
| <div class="block-info"> |
| <div> |
| <div>Блок #<span id="current-block">1</span></div> |
| <div class="progress-container"> |
| <div class="progress-bar" id="block-progress" style="width: 0%"></div> |
| </div> |
| </div> |
| <div style="text-align: right;"> |
| <div>Пикселей: <span id="mined-pixels">0</span>/<span id="total-pixels">0</span></div> |
| <div>Игроков: <span id="online-players">0</span></div> |
| </div> |
| </div> |
| |
| <button class="mining-button" id="mine-button"> |
| <i class="fas fa-hammer"></i> |
| <span>Добыть пиксель</span> |
| </button> |
| </div> |
| </div> |
| |
| <div class="tab-container"> |
| <div class="tab active" data-tab="stats">Статистика</div> |
| <div class="tab" data-tab="leaderboard">Лидеры</div> |
| <div class="tab" data-tab="history">История</div> |
| <div class="tab" data-tab="profile">Профиль</div> |
| </div> |
| |
| <div class="tab-content active" id="stats-content"> |
| <div class="stats-section"> |
| <div class="stats-grid"> |
| <div class="stat-item"> |
| <div>Ваши очки</div> |
| <div class="stat-value" id="user-score">0</div> |
| </div> |
| <div class="stat-item"> |
| <div>Добыто</div> |
| <div class="stat-value" id="user-mined">0</div> |
| </div> |
| <div class="stat-item"> |
| <div>Серебряных</div> |
| <div class="stat-value" id="user-silver">0</div> |
| </div> |
| <div class="stat-item"> |
| <div>Золотых</div> |
| <div class="stat-value" id="user-gold">0</div> |
| </div> |
| <div class="stat-item"> |
| <div>Бриллиантов</div> |
| <div class="stat-value" id="user-diamond">0</div> |
| </div> |
| <div class="stat-item"> |
| <div>Создано блоков</div> |
| <div class="stat-value" id="user-created">0</div> |
| </div> |
| <div class="stat-item"> |
| <div>Место в топе</div> |
| <div class="stat-value" id="user-rank">-</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="tab-content" id="leaderboard-content"> |
| <div class="stats-section"> |
| <table class="leaderboard"> |
| <thead> |
| <tr> |
| <th>#</th> |
| <th>Имя</th> |
| <th>Очки</th> |
| </tr> |
| </thead> |
| <tbody id="leaderboard-body"> |
| |
| </tbody> |
| </table> |
| </div> |
| </div> |
| |
| <div class="tab-content" id="history-content"> |
| <div class="stats-section"> |
| <div id="history-list"> |
| |
| </div> |
| </div> |
| </div> |
| |
| <div class="tab-content" id="profile-content"> |
| <div class="stats-section"> |
| <div> |
| <div style="text-align: center; margin-bottom: 12px;"> |
| <div class="user-avatar" style="width: 80px; height: 80px; margin: 0 auto 8px;" id="profile-avatar"></div> |
| <div id="profile-name">Имя пользователя</div> |
| <div style="font-size: 0.9rem; color: var(--accent-color);" id="profile-pix">0 PX</div> |
| </div> |
| |
| <div style="margin-top: 16px;">Созданные блоки:</div> |
| <div class="avatar-grid" id="avatar-grid"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const config = { |
| cols: 16, |
| rows: 16, |
| blockSizeIncrease: 2, |
| initialBlockSize: 16*16, |
| baseScore: 1, |
| rareChances: { |
| silver: 0.1, |
| gold: 0.03, |
| diamond: 0.01 |
| }, |
| rareScores: { |
| silver: 5, |
| gold: 15, |
| diamond: 50 |
| }, |
| updateInterval: 2000, |
| fakePlayers: 25 |
| }; |
| |
| |
| const state = { |
| currentBlock: 1, |
| canvasSize: config.initialBlockSize, |
| minedPixels: 0, |
| onlinePlayers: 0, |
| playerData: { |
| score: 0, |
| mined: 0, |
| silver: 0, |
| gold: 0, |
| diamond: 0, |
| createdBlocks: 0, |
| pix: 0, |
| avatar: '', |
| name: 'Игрок' |
| }, |
| leaderboard: [], |
| blockHistory: [], |
| createdBlocks: [] |
| }; |
| |
| |
| const elements = { |
| canvas: document.getElementById('pixel-canvas'), |
| currentBlock: document.getElementById('current-block'), |
| minedPixels: document.getElementById('mined-pixels'), |
| totalPixels: document.getElementById('total-pixels'), |
| onlinePlayers: document.getElementById('online-players'), |
| mineButton: document.getElementById('mine-button'), |
| blockProgress: document.getElementById('block-progress'), |
| userScore: document.getElementById('user-score'), |
| userMined: document.getElementById('user-mined'), |
| userSilver: document.getElementById('user-silver'), |
| userGold: document.getElementById('user-gold'), |
| userDiamond: document.getElementById('user-diamond'), |
| userCreated: document.getElementById('user-created'), |
| userRank: document.getElementById('user-rank'), |
| userCoins: document.getElementById('user-coins'), |
| userAvatar: document.getElementById('user-avatar'), |
| profileAvatar: document.getElementById('profile-avatar'), |
| profileName: document.getElementById('profile-name'), |
| profilePix: document.getElementById('profile-pix'), |
| leaderboardBody: document.getElementById('leaderboard-body'), |
| historyList: document.getElementById('history-list'), |
| avatarGrid: document.getElementById('avatar-grid'), |
| tabs: document.querySelectorAll('.tab'), |
| tabContents: document.querySelectorAll('.tab-content') |
| }; |
| |
| |
| let socket = { |
| connect: () => { |
| console.log('Connecting to WebSocket server...'); |
| |
| |
| |
| |
| setInterval(() => { |
| updateOnlinePlayers(); |
| updateBlockProgress(); |
| updateLeaderboard(); |
| }, config.updateInterval); |
| |
| |
| setInterval(() => { |
| simulateOtherPlayers(); |
| }, 1000); |
| }, |
| send: (data) => { |
| console.log('Sending data:', data); |
| |
| } |
| }; |
| |
| |
| function initCanvas() { |
| elements.canvas.style.setProperty('--cols', config.cols); |
| elements.canvas.style.setProperty('--rows', config.rows); |
| elements.totalPixels.textContent = config.cols * config.rows; |
| |
| |
| elements.canvas.innerHTML = ''; |
| |
| |
| for (let i = 0; i < config.cols * config.rows; i++) { |
| const pixel = document.createElement('div'); |
| pixel.className = 'pixel'; |
| pixel.dataset.index = i; |
| |
| |
| const tooltip = document.createElement('div'); |
| tooltip.className = 'pixel-tooltip'; |
| tooltip.textContent = 'Не добыт'; |
| |
| pixel.appendChild(tooltip); |
| elements.canvas.appendChild(pixel); |
| } |
| } |
| |
| |
| function updateUI() { |
| elements.currentBlock.textContent = state.currentBlock; |
| elements.minedPixels.textContent = state.minedPixels; |
| elements.totalPixels.textContent = config.cols * config.rows; |
| elements.onlinePlayers.textContent = state.onlinePlayers; |
| |
| elements.userScore.textContent = state.playerData.score; |
| elements.userMined.textContent = state.playerData.mined; |
| elements.userSilver.textContent = state.playerData.silver; |
| elements.userGold.textContent = state.playerData.gold; |
| elements.userDiamond.textContent = state.playerData.diamond; |
| elements.userCreated.textContent = state.playerData.createdBlocks; |
| elements.userCoins.textContent = state.playerData.pix + ' PX'; |
| elements.profilePix.textContent = state.playerData.pix + ' PX'; |
| |
| |
| const progress = (state.minedPixels / (config.cols * config.rows)) * 100; |
| elements.blockProgress.style.width = progress + '%'; |
| } |
| |
| |
| function minePixel(index = null) { |
| |
| if (index === null) { |
| const unminedPixels = Array.from(document.querySelectorAll('.pixel:not(.pixel-mined)')); |
| if (unminedPixels.length === 0) return false; |
| |
| const randomIndex = Math.floor(Math.random() * unminedPixels.length); |
| index = parseInt(unminedPixels[randomIndex].dataset.index); |
| } |
| |
| const pixel = elements.canvas.children[index]; |
| if (!pixel || pixel.classList.contains('pixel-mined')) return false; |
| |
| |
| const random = Math.random(); |
| let pixelType = 'normal'; |
| let score = config.baseScore; |
| |
| if (random < config.rareChances.diamond) { |
| pixelType = 'diamond'; |
| score = config.rareScores.diamond; |
| state.playerData.diamond++; |
| } else if (random < config.rareChances.gold) { |
| pixelType = 'gold'; |
| score = config.rareScores.gold; |
| state.playerData.gold++; |
| } else if (random < config.rareChances.silver) { |
| pixelType = 'silver'; |
| score = config.rareScores.silver; |
| state.playerData.silver++; |
| } |
| |
| |
| state.minedPixels++; |
| state.playerData.mined++; |
| state.playerData.score += score; |
| state.playerData.pix += score; |
| |
| |
| pixel.classList.add('pixel-mined'); |
| if (pixelType !== 'normal') { |
| pixel.classList.add('pixel-special', `pixel-${pixelType}`); |
| pixel.querySelector('.pixel-tooltip').innerHTML = |
| pixelType === 'silver' ? 'Серебряный ✨' : |
| pixelType === 'gold' ? 'Золотой ⭐' : 'Бриллиантовый 💎'; |
| } else { |
| pixel.querySelector('.pixel-tooltip').textContent = 'Добыт'; |
| } |
| |
| |
| if (state.minedPixels >= config.cols * config.rows) { |
| completeBlock(); |
| } |
| |
| updateUI(); |
| return true; |
| } |
| |
| |
| function completeBlock() { |
| |
| |
| state.playerData.createdBlocks++; |
| |
| |
| const blockData = { |
| id: state.currentBlock, |
| creator: state.playerData.name, |
| score: state.playerData.score, |
| pixels: state.minedPixels, |
| silver: state.playerData.silver, |
| gold: state.playerData.gold, |
| diamond: state.playerData.diamond, |
| completedAt: new Date() |
| }; |
| |
| state.blockHistory.unshift(blockData); |
| state.createdBlocks.push({ |
| id: state.currentBlock, |
| |
| image: generateBlockImage() |
| }); |
| |
| |
| state.currentBlock++; |
| state.minedPixels = 0; |
| |
| |
| config.cols += config.blockSizeIncrease; |
| config.rows += config.blockSizeIncrease; |
| |
| |
| initCanvas(); |
| |
| |
| showBlockCompleteNotification(blockData); |
| } |
| |
| |
| function generateBlockImage() { |
| |
| return `https://picsum.photos/200/200?random=${state.currentBlock}`; |
| } |
| |
| |
| function showBlockCompleteNotification(blockData) { |
| const notification = document.createElement('div'); |
| notification.style.position = 'fixed'; |
| notification.style.bottom = '20px'; |
| notification.style.left = '50%'; |
| notification.style.transform = 'translateX(-50%)'; |
| notification.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; |
| notification.style.color = 'white'; |
| notification.style.padding = '12px 20px'; |
| notification.style.borderRadius = '8px'; |
| notification.style.zIndex = '1000'; |
| notification.style.display = 'flex'; |
| notification.style.alignItems = 'center'; |
| notification.style.gap = '8px'; |
| notification.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.3)'; |
| |
| notification.innerHTML = ` |
| <div style="font-weight: bold; color: var(--accent-color)">Блок #${blockData.id} завершен!</div> |
| <div style="font-size: 0.9rem;">Создатель: ${blockData.creator}</div> |
| `; |
| |
| document.body.appendChild(notification); |
| |
| setTimeout(() => { |
| notification.style.transition = 'opacity 0.5s'; |
| notification.style.opacity = '0'; |
| setTimeout(() => notification.remove(), 500); |
| }, 3000); |
| } |
| |
| |
| function updateOnlinePlayers() { |
| |
| state.onlinePlayers = Math.floor(Math.random() * 50) + config.fakePlayers; |
| updateUI(); |
| } |
| |
| |
| function updateBlockProgress() { |
| |
| if (state.minedPixels < config.cols * config.rows) { |
| state.minedPixels += Math.floor(Math.random() * 5) + 1; |
| |
| |
| if (state.minedPixels > config.cols * config.rows) { |
| state.minedPixels = config.cols * config.rows; |
| } |
| |
| updateUI(); |
| } |
| } |
| |
| |
| function updateLeaderboard() { |
| |
| const fakeData = Array.from({length: 10}, (_, i) => ({ |
| id: i+1, |
| name: ['Игрок1', 'Игрок2', 'Игрок3', 'Игрок4', 'Игрок5', 'Игрок6', 'Игрок7', 'Игрок8'][i] || `Игрок${i+1}`, |
| score: Math.floor(Math.random() * 10000) + 1000, |
| avatar: `https://picsum.photos/200/200?random=${i+100}` |
| })); |
| |
| |
| if (!fakeData.some(p => p.id === 999)) { |
| fakeData.push({ |
| id: 999, |
| name: state.playerData.name, |
| score: state.playerData.score, |
| avatar: state.playerData.avatar |
| }); |
| } |
| |
| |
| fakeData.sort((a, b) => b.score - a.score); |
| state.leaderboard = fakeData.slice(0, 10); |
| |
| |
| elements.leaderboardBody.innerHTML = ''; |
| |
| state.leaderboard.forEach((player, index) => { |
| const isCurrentUser = player.id === 999; |
| const row = document.createElement('tr'); |
| row.style.background = isCurrentUser ? 'rgba(76, 201, 240, 0.2)' : 'transparent'; |
| |
| row.innerHTML = ` |
| <td> |
| ${index < 3 ? |
| `<span class="medal ${index === 0 ? 'medal-gold' : index === 1 ? 'medal-silver' : 'medal-bronze'}"> |
| ${index + 1} |
| </span>` : |
| index + 1 |
| } |
| </td> |
| <td> |
| <div style="display: flex; align-items: center; gap: 8px;"> |
| <img src="${player.avatar}" style="width: 24px; height: 24px; border-radius: 50%;"> |
| ${player.name} |
| </div> |
| </td> |
| <td>${player.score.toLocaleString()}</td> |
| `; |
| |
| elements.leaderboardBody.appendChild(row); |
| |
| |
| if (isCurrentUser) { |
| const rank = index + 1; |
| elements.userRank.textContent = rank; |
| } |
| }); |
| |
| |
| if (!state.leaderboard.some(p => p.id === 999)) { |
| elements.userRank.textContent = '>10'; |
| } |
| } |
| |
| |
| function simulateOtherPlayers() { |
| const unminedPixels = Array.from(document.querySelectorAll('.pixel:not(.pixel-mined)')); |
| if (unminedPixels.length === 0) return; |
| |
| const simultaneousMining = Math.floor(Math.random() * 5); |
| for (let i = 0; i < simultaneousMining; i++) { |
| if (Math.random() < 0.7) { |
| const randomIndex = Math.floor(Math.random() * unminedPixels.length); |
| minePixel(parseInt(unminedPixels[randomIndex].dataset.index)); |
| |
| |
| unminedPixels.splice(randomIndex, 1); |
| if (unminedPixels.length === 0) break; |
| } |
| } |
| } |
| |
| |
| function updateHistory() { |
| elements.historyList.innerHTML = ''; |
| |
| if (state.blockHistory.length === 0) { |
| elements.historyList.innerHTML = '<div style="text-align: center; padding: 12px; color: #666;">История блоков пуста</div>'; |
| return; |
| } |
| |
| state.blockHistory.forEach(block => { |
| const item = document.createElement('div'); |
| item.className = 'history-item'; |
| item.innerHTML = ` |
| <div> |
| <div style="font-weight: bold;">Блок #${block.id}</div> |
| <div style="font-size: 0.8rem; color: #aaa;">${block.completedAt.toLocaleTimeString()}</div> |
| </div> |
| <div style="text-align: right;"> |
| <div>${block.creator}</div> |
| <div style="font-size: 0.8rem;"> |
| <span>${block.score} PX</span> |
| ${block.silver > 0 ? `<span class="rare-icon">✨${block.silver}</span>` : ''} |
| ${block.gold > 0 ? `<span class="rare-icon">⭐${block.gold}</span>` : ''} |
| ${block.diamond > 0 ? `<span class="rare-icon">💎${block.diamond}</span>` : ''} |
| </div> |
| </div> |
| `; |
| |
| elements.historyList.appendChild(item); |
| }); |
| } |
| |
| |
| function updateProfile() { |
| elements.profileName.textContent = state.playerData.name; |
| |
| if (state.playerData.avatar) { |
| elements.userAvatar.style.backgroundImage = `url(${state.playerData.avatar})`; |
| elements.userAvatar.style.backgroundSize = 'cover'; |
| elements.profileAvatar.style.backgroundImage = `url(${state.playerData.avatar})`; |
| elements.profileAvatar.style.backgroundSize = 'cover'; |
| } |
| |
| |
| elements.avatarGrid.innerHTML = ''; |
| |
| if (state.createdBlocks.length === 0) { |
| elements.avatarGrid.innerHTML = '<div style="grid-column: 1 / -1; text-align: center; padding: 12px;">Нет созданных блоков</div>'; |
| return; |
| } |
| |
| state.createdBlocks.forEach(block => { |
| const item = document.createElement('div'); |
| item.className = 'avatar-item'; |
| item.dataset.blockId = block.id; |
| |
| const img = document.createElement('img'); |
| img.src = block.image; |
| img.alt = `Блок ${block.id}`; |
| |
| item.appendChild(img); |
| elements.avatarGrid.appendChild(item); |
| }); |
| } |
| |
| |
| function setupEventListeners() { |
| |
| elements.mineButton.addEventListener('click', () => { |
| minePixel(); |
| }); |
| |
| |
| elements.tabs.forEach(tab => { |
| tab.addEventListener('click', () => { |
| elements.tabs.forEach(t => t.classList.remove('active')); |
| tab.classList.add('active'); |
| |
| const tabName = tab.dataset.tab; |
| elements.tabContents.forEach(content => { |
| content.classList.remove('active'); |
| if (content.id === `${tabName}-content`) { |
| content.classList.add('active'); |
| |
| |
| if (tabName === 'leaderboard') { |
| updateLeaderboard(); |
| } else if (tabName === 'history') { |
| updateHistory(); |
| } else if (tabName === 'profile') { |
| updateProfile(); |
| } |
| } |
| }); |
| }); |
| }); |
| |
| |
| elements.canvas.addEventListener('click', (e) => { |
| if (e.target.classList.contains('pixel')) { |
| const index = parseInt(e.target.dataset.index); |
| if (!e.target.classList.contains('pixel-mined')) { |
| if (Math.random() < 0.5) { |
| minePixel(index); |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| function initGame() { |
| |
| |
| const randomName = `Игрок${Math.floor(Math.random() * 1000)}`; |
| state.playerData.name = randomName; |
| state.playerData.avatar = `https://picsum.photos/200/200?random=${Math.floor(Math.random() * 1000)}`; |
| |
| initCanvas(); |
| updateUI(); |
| setupEventListeners(); |
| socket.connect(); |
| |
| |
| setTimeout(() => { |
| updateLeaderboard(); |
| updateHistory(); |
| updateProfile(); |
| }, 500); |
| } |
| |
| |
| initGame(); |
| }); |
| </script> |
| </body> |
| </html> |