401 lines
18 KiB
HTML
401 lines
18 KiB
HTML
<!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> |