997 lines
35 KiB
HTML
997 lines
35 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 {
|
||
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);
|
||
}
|
||
|
||
.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);
|
||
box-shadow: 0 15px 40px rgba(102, 126, 234, 0.6);
|
||
}
|
||
|
||
.click-button:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.shop-section {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border-radius: 20px;
|
||
padding: 30px;
|
||
backdrop-filter: blur(10px);
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.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;
|
||
color: #fff;
|
||
}
|
||
|
||
.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);
|
||
box-shadow: 0 5px 15px rgba(245, 87, 108, 0.4);
|
||
}
|
||
|
||
.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%;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.reset-button:hover {
|
||
transform: scale(1.02);
|
||
box-shadow: 0 5px 20px rgba(250, 112, 154, 0.4);
|
||
}
|
||
|
||
.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;
|
||
}
|
||
}
|
||
|
||
/* Скроллбар */
|
||
.shop-section::-webkit-scrollbar {
|
||
width: 10px;
|
||
}
|
||
|
||
.shop-section::-webkit-scrollbar-track {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.shop-section::-webkit-scrollbar-thumb {
|
||
background: rgba(255, 215, 0, 0.3);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.shop-section::-webkit-scrollbar-thumb:hover {
|
||
background: rgba(255, 215, 0, 0.5);
|
||
}
|
||
|
||
/* Индикатор сохранения */
|
||
.save-indicator {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
color: white;
|
||
padding: 12px 20px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
display: none;
|
||
align-items: center;
|
||
gap: 10px;
|
||
z-index: 1000;
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.save-indicator.show {
|
||
display: flex;
|
||
animation: slideIn 0.3s ease;
|
||
}
|
||
|
||
.save-indicator.success {
|
||
border-color: rgba(76, 175, 80, 0.5);
|
||
}
|
||
|
||
.save-indicator.error {
|
||
border-color: rgba(244, 67, 54, 0.5);
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateX(100px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
}
|
||
|
||
.spinner {
|
||
width: 16px;
|
||
height: 16px;
|
||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||
border-top-color: white;
|
||
border-radius: 50%;
|
||
animation: spin 0.8s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="save-indicator" id="saveIndicator">
|
||
<div class="spinner"></div>
|
||
<span id="saveIndicatorText">Сохранение...</span>
|
||
</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 }
|
||
}
|
||
};
|
||
|
||
// API клиент
|
||
class GameAPI {
|
||
constructor(gameSlug) {
|
||
this.gameSlug = gameSlug;
|
||
this.tableName = 'game_save';
|
||
}
|
||
|
||
getCSRFToken() {
|
||
return document.querySelector('[name=csrfmiddlewaretoken]')?.value ||
|
||
document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1];
|
||
}
|
||
|
||
async requestPermission(operation, description = '') {
|
||
return new Promise((resolve) => {
|
||
const modal = document.createElement('div');
|
||
modal.className = 'permission-modal';
|
||
modal.innerHTML = `
|
||
<div class="permission-modal-content">
|
||
<div class="permission-modal-header">
|
||
<span class="material-icons-round">security</span>
|
||
<h3>Запрос разрешения</h3>
|
||
</div>
|
||
|
||
<div class="permission-modal-body">
|
||
<p>Игра <strong>"${this.gameSlug}"</strong> хочет:</p>
|
||
<div class="permission-operation">
|
||
<span class="material-icons-round">${this.getOperationIcon(operation)}</span>
|
||
<strong>${this.getOperationName(operation)}</strong>
|
||
</div>
|
||
${description ? `<p class="permission-description">${description}</p>` : ''}
|
||
</div>
|
||
|
||
<div class="permission-modal-footer">
|
||
<label class="permission-checkbox">
|
||
<input type="checkbox" id="dontAskAgain">
|
||
<span>Не спрашивать больше для этой операции</span>
|
||
</label>
|
||
|
||
<div class="permission-buttons">
|
||
<button class="btn btn-danger" id="denyAllBtn">
|
||
<span class="material-icons-round">block</span>
|
||
Запретить всё
|
||
</button>
|
||
<button class="btn btn-secondary" id="denyBtn">
|
||
<span class="material-icons-round">close</span>
|
||
Запретить
|
||
</button>
|
||
<button class="btn btn-success" id="allowBtn">
|
||
<span class="material-icons-round">check</span>
|
||
Разрешить
|
||
</button>
|
||
<button class="btn btn-primary" id="allowAllBtn">
|
||
<span class="material-icons-round">done_all</span>
|
||
Разрешить всё
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(modal);
|
||
|
||
const dontAskAgain = modal.querySelector('#dontAskAgain');
|
||
const allowBtn = modal.querySelector('#allowBtn');
|
||
const denyBtn = modal.querySelector('#denyBtn');
|
||
const allowAllBtn = modal.querySelector('#allowAllBtn');
|
||
const denyAllBtn = modal.querySelector('#denyAllBtn');
|
||
|
||
const handleResponse = async (allow, dontAsk, allowAll, denyAll) => {
|
||
await this.savePermission(operation, allow, dontAsk, allowAll, denyAll);
|
||
modal.remove();
|
||
resolve(allow);
|
||
};
|
||
|
||
allowBtn.onclick = () => handleResponse(true, dontAskAgain.checked, false, false);
|
||
denyBtn.onclick = () => handleResponse(false, dontAskAgain.checked, false, false);
|
||
allowAllBtn.onclick = () => handleResponse(true, true, true, false);
|
||
denyAllBtn.onclick = () => handleResponse(false, true, false, true);
|
||
|
||
modal.onclick = (e) => {
|
||
if (e.target === modal) {
|
||
handleResponse(false, false, false, false);
|
||
}
|
||
};
|
||
});
|
||
}
|
||
|
||
async getPermission(operation) {
|
||
try {
|
||
const response = await fetch('/api/user/permissions/', {
|
||
method: 'GET',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': this.getCSRFToken()
|
||
}
|
||
});
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
const permissions = data.permissions || {};
|
||
const gamePerms = permissions[this.gameSlug] || {};
|
||
return gamePerms[this.tableName]?.[operation] ?? null;
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки разрешений:', error);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async savePermission(operation, allow, dontAskAgain, allowAll, denyAll) {
|
||
try {
|
||
await fetch('/api/user/permissions/', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': this.getCSRFToken()
|
||
},
|
||
body: JSON.stringify({
|
||
game_slug: this.gameSlug,
|
||
table_name: this.tableName,
|
||
operation: operation,
|
||
allow: allow,
|
||
dont_ask_again: dontAskAgain,
|
||
allow_all: allowAll,
|
||
deny_all: denyAll
|
||
})
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка сохранения разрешения:', error);
|
||
}
|
||
}
|
||
|
||
async checkPermission(operation, description = '') {
|
||
const cached = await this.getPermission(operation);
|
||
if (cached !== null) {
|
||
return cached;
|
||
}
|
||
return await this.requestPermission(operation, description);
|
||
}
|
||
|
||
async loadGame() {
|
||
const allowed = await this.checkPermission('select', 'Загрузить сохранение игры');
|
||
if (!allowed) {
|
||
throw new Error('Загрузка запрещена');
|
||
}
|
||
|
||
const response = await fetch('/api/game/select/', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': this.getCSRFToken()
|
||
},
|
||
body: JSON.stringify({
|
||
game_slug: this.gameSlug,
|
||
table_name: this.tableName,
|
||
conditions: { 'key': 'game_state' }
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Ошибка загрузки');
|
||
}
|
||
|
||
const data = await response.json();
|
||
return data.data?.[0]?.value || null;
|
||
}
|
||
|
||
async saveGame(state) {
|
||
const allowed = await this.checkPermission('insert', 'Сохранить прогресс игры');
|
||
if (!allowed) {
|
||
throw new Error('Сохранение запрещено');
|
||
}
|
||
|
||
const response = await fetch('/api/game/insert/', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': this.getCSRFToken()
|
||
},
|
||
body: JSON.stringify({
|
||
game_slug: this.gameSlug,
|
||
table_name: this.tableName,
|
||
data: {
|
||
'key': 'game_state',
|
||
'value': JSON.stringify(state),
|
||
'updated_at': new Date().toISOString()
|
||
}
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Ошибка сохранения');
|
||
}
|
||
|
||
return await response.json();
|
||
}
|
||
|
||
async deleteGame() {
|
||
const allowed = await this.checkPermission('delete', 'Удалить сохранение игры');
|
||
if (!allowed) {
|
||
throw new Error('Удаление запрещено');
|
||
}
|
||
|
||
const response = await fetch('/api/game/delete/', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': this.getCSRFToken()
|
||
},
|
||
body: JSON.stringify({
|
||
game_slug: this.gameSlug,
|
||
table_name: this.tableName,
|
||
conditions: { 'key': 'game_state' }
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Ошибка удаления');
|
||
}
|
||
|
||
return await response.json();
|
||
}
|
||
|
||
getOperationIcon(operation) {
|
||
const icons = {
|
||
'select': 'download',
|
||
'insert': 'save',
|
||
'delete': 'delete'
|
||
};
|
||
return icons[operation] || 'help';
|
||
}
|
||
|
||
getOperationName(operation) {
|
||
const names = {
|
||
'select': 'Загрузить данные',
|
||
'insert': 'Сохранить данные',
|
||
'delete': 'Удалить данные'
|
||
};
|
||
return names[operation] || operation;
|
||
}
|
||
}
|
||
|
||
// CSS для модального окна
|
||
const modalStyles = `
|
||
<style>
|
||
.permission-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 10000;
|
||
backdrop-filter: blur(5px);
|
||
}
|
||
|
||
.permission-modal-content {
|
||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||
border-radius: 16px;
|
||
padding: 30px;
|
||
max-width: 500px;
|
||
width: 90%;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
animation: modalSlideIn 0.3s ease;
|
||
}
|
||
|
||
@keyframes modalSlideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-50px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.permission-modal-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.permission-modal-header .material-icons-round {
|
||
font-size: 32px;
|
||
color: #ffd700;
|
||
}
|
||
|
||
.permission-modal-header h3 {
|
||
margin: 0;
|
||
color: #fff;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.permission-modal-body {
|
||
margin-bottom: 25px;
|
||
color: #ccc;
|
||
}
|
||
|
||
.permission-modal-body p {
|
||
margin: 10px 0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.permission-operation {
|
||
background: rgba(255, 215, 0, 0.1);
|
||
border: 1px solid rgba(255, 215, 0, 0.3);
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
margin: 15px 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.permission-operation .material-icons-round {
|
||
color: #ffd700;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.permission-operation strong {
|
||
color: #ffd700;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.permission-description {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
padding: 10px;
|
||
border-radius: 6px;
|
||
font-style: italic;
|
||
}
|
||
|
||
.permission-modal-footer {
|
||
border-top: 2px solid rgba(255, 255, 255, 0.1);
|
||
padding-top: 20px;
|
||
}
|
||
|
||
.permission-checkbox {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
cursor: pointer;
|
||
color: #aaa;
|
||
}
|
||
|
||
.permission-checkbox input[type="checkbox"] {
|
||
width: 18px;
|
||
height: 18px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.permission-buttons {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 10px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 12px 20px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.btn .material-icons-round {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-success {
|
||
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.btn:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.permission-buttons {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|
||
`;
|
||
document.head.insertAdjacentHTML('beforeend', modalStyles);
|
||
|
||
// Инициализация API
|
||
const gameAPI = new GameAPI('clicker-magnat');
|
||
|
||
// Описания улучшений
|
||
const upgradeDescriptions = {
|
||
clickPower: 'Увеличивает доход за клик на 1',
|
||
autoClicker: 'Автоматически добавляет 1 в секунду',
|
||
superClick: 'Увеличивает доход за клик на 10',
|
||
megaAuto: 'Автоматически добавляет 10 в секунду',
|
||
ultraClick: 'Увеличивает доход за клик на 100',
|
||
hyperAuto: 'Автоматически добавляет 100 в секунду'
|
||
};
|
||
|
||
const upgradeNames = {
|
||
clickPower: '💪 Сила клика',
|
||
autoClicker: '🤖 Автокликер',
|
||
superClick: '⚡ Супер клик',
|
||
megaAuto: '🚀 Мега автокликер',
|
||
ultraClick: '🔥 Ультра клик',
|
||
hyperAuto: '💎 Гипер автокликер'
|
||
};
|
||
|
||
// Показ индикатора сохранения
|
||
function showSaveIndicator(text, type = 'loading') {
|
||
const indicator = document.getElementById('saveIndicator');
|
||
const indicatorText = document.getElementById('saveIndicatorText');
|
||
|
||
indicator.className = 'save-indicator show';
|
||
if (type === 'success') {
|
||
indicator.classList.add('success');
|
||
indicator.querySelector('.spinner').style.display = 'none';
|
||
} else if (type === 'error') {
|
||
indicator.classList.add('error');
|
||
indicator.querySelector('.spinner').style.display = 'none';
|
||
} else {
|
||
indicator.querySelector('.spinner').style.display = 'block';
|
||
}
|
||
|
||
indicatorText.textContent = text;
|
||
}
|
||
|
||
function hideSaveIndicator() {
|
||
const indicator = document.getElementById('saveIndicator');
|
||
indicator.classList.remove('show');
|
||
}
|
||
|
||
// Загрузка сохранения через API
|
||
async function loadGame() {
|
||
try {
|
||
showSaveIndicator('Загрузка сохранения...');
|
||
const savedData = await gameAPI.loadGame();
|
||
|
||
if (savedData) {
|
||
const savedState = JSON.parse(savedData);
|
||
gameState = { ...gameState, ...savedState };
|
||
|
||
// Восстанавливаем стоимость улучшений
|
||
for (let key in gameState.upgrades) {
|
||
const upgrade = gameState.upgrades[key];
|
||
upgrade.cost = Math.floor(upgrade.baseCost * Math.pow(1.15, upgrade.level));
|
||
}
|
||
}
|
||
|
||
showSaveIndicator('Сохранение загружено', 'success');
|
||
setTimeout(hideSaveIndicator, 2000);
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки:', error);
|
||
showSaveIndicator('Ошибка загрузки', 'error');
|
||
setTimeout(hideSaveIndicator, 2000);
|
||
|
||
// Fallback на localStorage
|
||
const saved = localStorage.getItem('clickerGame');
|
||
if (saved) {
|
||
const savedState = JSON.parse(saved);
|
||
gameState = { ...gameState, ...savedState };
|
||
for (let key in gameState.upgrades) {
|
||
const upgrade = gameState.upgrades[key];
|
||
upgrade.cost = Math.floor(upgrade.baseCost * Math.pow(1.15, upgrade.level));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Сохранение игры через API
|
||
async function saveGame() {
|
||
try {
|
||
await gameAPI.saveGame(gameState);
|
||
showSaveIndicator('Сохранено', 'success');
|
||
setTimeout(hideSaveIndicator, 1500);
|
||
} catch (error) {
|
||
console.error('Ошибка сохранения:', error);
|
||
showSaveIndicator('Ошибка сохранения', 'error');
|
||
setTimeout(hideSaveIndicator, 2000);
|
||
|
||
// Fallback на localStorage
|
||
localStorage.setItem('clickerGame', JSON.stringify(gameState));
|
||
}
|
||
}
|
||
|
||
// Подсчет дохода за клик
|
||
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;
|
||
}
|
||
|
||
// Обновление UI
|
||
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>
|
||
`;
|
||
|
||
const buyButton = item.querySelector('.buy-button');
|
||
buyButton.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 function resetGame() {
|
||
if (confirm('Вы уверены, что хотите сбросить весь прогресс?')) {
|
||
try {
|
||
showSaveIndicator('Удаление сохранения...');
|
||
await gameAPI.deleteGame();
|
||
showSaveIndicator('Прогресс сброшен', 'success');
|
||
setTimeout(hideSaveIndicator, 1500);
|
||
} catch (error) {
|
||
console.error('Ошибка удаления:', error);
|
||
showSaveIndicator('Ошибка удаления', 'error');
|
||
setTimeout(hideSaveIndicator, 2000);
|
||
}
|
||
|
||
localStorage.removeItem('clickerGame');
|
||
location.reload();
|
||
}
|
||
}
|
||
|
||
// Инициализация
|
||
(async () => {
|
||
await loadGame();
|
||
updateUI();
|
||
|
||
document.getElementById('clickButton').addEventListener('click', handleClick);
|
||
document.getElementById('resetButton').addEventListener('click', resetGame);
|
||
|
||
// Автокликер каждые 100мс
|
||
setInterval(autoClick, 100);
|
||
|
||
// Автосохранение каждые 30 секунд
|
||
setInterval(saveGame, 30000);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html> |