Files
HTML-Game/clicker.html
T
2026-06-16 14:50:33 +03:00

401 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Кликер-Магнат</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #eee;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.game-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
max-width: 1200px;
width: 100%;
}
.clicker-section, .shop-section {
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 40px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.shop-section { max-height: 80vh; overflow-y: auto; }
.stats { text-align: center; margin-bottom: 30px; }
.score {
font-size: 48px; font-weight: bold; color: #ffd700;
text-shadow: 0 0 20px rgba(255, 215, 0, 0.5); margin-bottom: 10px;
}
.per-click { font-size: 18px; color: #aaa; }
.per-second { font-size: 16px; color: #888; margin-top: 5px; }
.click-button {
width: 200px; height: 200px; border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none; cursor: pointer; font-size: 24px; color: white;
font-weight: bold; box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
transition: all 0.1s ease; display: block; margin: 0 auto;
}
.click-button:hover { transform: scale(1.05); }
.click-button:active { transform: scale(0.95); }
.shop-title {
font-size: 32px; margin-bottom: 20px;
text-align: center; color: #ffd700;
}
.upgrade-item {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px; padding: 20px; margin-bottom: 15px;
transition: all 0.3s ease; border: 2px solid transparent;
}
.upgrade-item:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 215, 0, 0.3);
}
.upgrade-item.cant-afford { opacity: 0.5; }
.upgrade-header {
display: flex; justify-content: space-between;
align-items: center; margin-bottom: 10px;
}
.upgrade-name { font-size: 20px; font-weight: bold; }
.upgrade-level {
font-size: 14px; color: #aaa;
background: rgba(255, 255, 255, 0.1);
padding: 5px 10px; border-radius: 10px;
}
.upgrade-description { font-size: 14px; color: #ccc; margin-bottom: 15px; }
.upgrade-footer { display: flex; justify-content: space-between; align-items: center; }
.upgrade-cost { font-size: 18px; color: #ffd700; font-weight: bold; }
.buy-button {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
border: none; padding: 10px 25px; border-radius: 10px;
color: white; font-weight: bold; cursor: pointer;
transition: all 0.3s ease; font-size: 16px;
}
.buy-button:hover:not(:disabled) { transform: scale(1.05); }
.buy-button:disabled { opacity: 0.5; cursor: not-allowed; }
.reset-button {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
border: none; padding: 15px 30px; border-radius: 15px;
color: white; font-weight: bold; cursor: pointer;
font-size: 16px; margin-top: 20px; width: 100%;
}
.click-animation {
position: absolute; pointer-events: none;
font-size: 24px; font-weight: bold; color: #ffd700;
animation: float-up 1s ease-out forwards;
}
@keyframes float-up {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-100px); }
}
@media (max-width: 768px) {
.game-container { grid-template-columns: 1fr; }
}
/* Статус API */
.api-status {
position: fixed;
top: 10px;
left: 10px;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
background: rgba(0, 0, 0, 0.7);
color: #aaa;
z-index: 1000;
}
.api-status.connected { color: #4caf50; }
.api-status.error { color: #f44336; }
</style>
</head>
<body>
<div class="api-status" id="apiStatus">⏳ API: ожидание...</div>
<div class="game-container">
<div class="clicker-section">
<div class="stats">
<div class="score" id="score">0</div>
<div class="per-click">За клик: <span id="perClick">1</span></div>
<div class="per-second">В секунду: <span id="perSecond">0</span></div>
</div>
<button class="click-button" id="clickButton">КЛИК!</button>
</div>
<div class="shop-section">
<h2 class="shop-title">🛒 Магазин</h2>
<div id="upgradesContainer"></div>
<button class="reset-button" id="resetButton">🔄 Сбросить прогресс</button>
</div>
</div>
<script>
// ═══════════════════════════════════════════════════════════
// СОСТОЯНИЕ ИГРЫ
// ═══════════════════════════════════════════════════════════
let gameState = {
score: 0,
totalClicks: 0,
upgrades: {
clickPower: { level: 0, baseCost: 10, cost: 10, power: 1 },
autoClicker: { level: 0, baseCost: 50, cost: 50, power: 1 },
superClick: { level: 0, baseCost: 500, cost: 500, power: 10 },
megaAuto: { level: 0, baseCost: 2000, cost: 2000, power: 10 },
ultraClick: { level: 0, baseCost: 10000, cost: 10000, power: 100 },
hyperAuto: { level: 0, baseCost: 50000, cost: 50000, power: 100 }
}
};
const upgradeDescriptions = {
clickPower: 'Увеличивает доход за клик на 1',
autoClicker: 'Автоматически добавляет 1 в секунду',
superClick: 'Увеличивает доход за клик на 10',
megaAuto: 'Автоматически добавляет 10 в секунду',
ultraClick: 'Увеличивает доход за клик на 100',
hyperAuto: 'Автоматически добавляет 100 в секунду'
};
const upgradeNames = {
clickPower: '💪 Сила клика',
autoClicker: '🤖 Автокликер',
superClick: '⚡ Супер клик',
megaAuto: '🚀 Мега автокликер',
ultraClick: '🔥 Ультра клик',
hyperAuto: '💎 Гипер автокликер'
};
// ═══════════════════════════════════════════════════════════
// API КЛИЕНТ (через bridge в родителе)
// ═══════════════════════════════════════════════════════════
let api = null;
const TABLE_NAME = 'game_save';
const statusEl = document.getElementById('apiStatus');
function updateStatus(text, type = '') {
statusEl.textContent = text;
statusEl.className = 'api-status ' + type;
}
// Ждём готовности API
async function waitForAPI() {
if (window.gameApi) {
api = window.gameApi;
updateStatus('✓ API подключён', 'connected');
return true;
}
return new Promise((resolve) => {
const timeout = setTimeout(() => {
updateStatus('✗ API недоступен, используем localStorage', 'error');
resolve(false);
}, 5000);
window.addEventListener('gameApiReady', () => {
clearTimeout(timeout);
api = window.gameApi;
updateStatus('✓ API подключён', 'connected');
resolve(true);
});
});
}
// ═══════════════════════════════════════════════════════════
// СОХРАНЕНИЕ / ЗАГРУЗКА
// ═══════════════════════════════════════════════════════════
async function loadGame() {
// Пытаемся загрузить через API
if (api) {
try {
const result = await api.selectData(TABLE_NAME, { key: 'game_state' });
if (result.data && result.data.length > 0) {
const savedState = JSON.parse(result.data[0].value);
gameState = { ...gameState, ...savedState };
for (let key in gameState.upgrades) {
const u = gameState.upgrades[key];
u.cost = Math.floor(u.baseCost * Math.pow(1.15, u.level));
}
console.log('Загружено через API');
return;
}
} catch (e) {
console.warn('API загрузка не удалась:', e.message);
}
}
// Fallback на localStorage
const saved = localStorage.getItem('clickerGame');
if (saved) {
const savedState = JSON.parse(saved);
gameState = { ...gameState, ...savedState };
for (let key in gameState.upgrades) {
const u = gameState.upgrades[key];
u.cost = Math.floor(u.baseCost * Math.pow(1.15, u.level));
}
}
}
async function saveGame() {
// Сохраняем через API
if (api) {
try {
// Сначала удаляем старую запись
await api.deleteData(TABLE_NAME, { key: 'game_state' });
// Затем вставляем новую
await api.insertData(TABLE_NAME, {
key: 'game_state',
value: JSON.stringify(gameState),
updated_at: new Date().toISOString()
});
return;
} catch (e) {
console.warn('API сохранение не удалось:', e.message);
}
}
// Fallback
localStorage.setItem('clickerGame', JSON.stringify(gameState));
}
async function resetGame() {
if (!confirm('Сбросить весь прогресс?')) return;
if (api) {
try {
await api.deleteData(TABLE_NAME, { key: 'game_state' });
} catch (e) {
console.warn('API удаление не удалось:', e.message);
}
}
localStorage.removeItem('clickerGame');
location.reload();
}
// ═══════════════════════════════════════════════════════════
// ИГРОВАЯ ЛОГИКА
// ═══════════════════════════════════════════════════════════
function getClickPower() {
let power = 1;
power += gameState.upgrades.clickPower.level * gameState.upgrades.clickPower.power;
power += gameState.upgrades.superClick.level * gameState.upgrades.superClick.power;
power += gameState.upgrades.ultraClick.level * gameState.upgrades.ultraClick.power;
return power;
}
function getPerSecond() {
let perSecond = 0;
perSecond += gameState.upgrades.autoClicker.level * gameState.upgrades.autoClicker.power;
perSecond += gameState.upgrades.megaAuto.level * gameState.upgrades.megaAuto.power;
perSecond += gameState.upgrades.hyperAuto.level * gameState.upgrades.hyperAuto.power;
return perSecond;
}
function updateUI() {
document.getElementById('score').textContent = Math.floor(gameState.score).toLocaleString();
document.getElementById('perClick').textContent = getClickPower().toLocaleString();
document.getElementById('perSecond').textContent = getPerSecond().toLocaleString();
const container = document.getElementById('upgradesContainer');
container.innerHTML = '';
for (let key in gameState.upgrades) {
const upgrade = gameState.upgrades[key];
const canAfford = gameState.score >= upgrade.cost;
const item = document.createElement('div');
item.className = `upgrade-item ${canAfford ? '' : 'cant-afford'}`;
item.innerHTML = `
<div class="upgrade-header">
<div class="upgrade-name">${upgradeNames[key]}</div>
<div class="upgrade-level">Ур. ${upgrade.level}</div>
</div>
<div class="upgrade-description">${upgradeDescriptions[key]}</div>
<div class="upgrade-footer">
<div class="upgrade-cost">💰 ${upgrade.cost.toLocaleString()}</div>
<button class="buy-button" ${canAfford ? '' : 'disabled'}>Купить</button>
</div>
`;
item.querySelector('.buy-button').addEventListener('click', () => buyUpgrade(key));
container.appendChild(item);
}
}
function buyUpgrade(upgradeKey) {
const upgrade = gameState.upgrades[upgradeKey];
if (gameState.score >= upgrade.cost) {
gameState.score -= upgrade.cost;
upgrade.level++;
upgrade.cost = Math.floor(upgrade.baseCost * Math.pow(1.15, upgrade.level));
updateUI();
saveGame();
}
}
function handleClick(event) {
const power = getClickPower();
gameState.score += power;
gameState.totalClicks++;
const animation = document.createElement('div');
animation.className = 'click-animation';
animation.textContent = `+${power}`;
animation.style.left = `${event.clientX}px`;
animation.style.top = `${event.clientY}px`;
document.body.appendChild(animation);
setTimeout(() => animation.remove(), 1000);
updateUI();
}
function autoClick() {
const perSecond = getPerSecond();
if (perSecond > 0) {
gameState.score += perSecond / 10;
updateUI();
}
}
// ═══════════════════════════════════════════════════════════
// ИНИЦИАЛИЗАЦИЯ
// ═══════════════════════════════════════════════════════════
(async () => {
// Ждём API
await waitForAPI();
// Создаём таблицу при первом запуске
if (api) {
try {
await api.createTable(TABLE_NAME, {
'key': 'TEXT',
'value': 'TEXT',
'updated_at': 'TEXT'
}, 'Таблица сохранений игры');
} catch (e) {
// Таблица уже существует — это нормально
console.log('Таблица уже существует или:', e.message);
}
}
// Загружаем игру
await loadGame();
updateUI();
document.getElementById('clickButton').addEventListener('click', handleClick);
document.getElementById('resetButton').addEventListener('click', resetGame);
setInterval(autoClick, 100);
setInterval(saveGame, 30000);
})();
</script>
</body>
</html>