Обновить clicker.html

This commit is contained in:
2026-06-16 14:33:07 +03:00
parent 2e019d41d4
commit 16f31150c9
+579 -24
View File
@@ -246,9 +246,69 @@
.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">
@@ -283,6 +343,431 @@
}
};
// 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',
@@ -302,23 +787,81 @@
hyperAuto: '💎 Гипер автокликер'
};
// Загрузка сохранения
function loadGame() {
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));
// Показ индикатора сохранения
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));
}
}
}
}
// Сохранение игры
function saveGame() {
localStorage.setItem('clickerGame', JSON.stringify(gameState));
// Сохранение игры через 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));
}
}
// Подсчет дохода за клик
@@ -405,38 +948,50 @@
setTimeout(() => animation.remove(), 1000);
updateUI();
saveGame();
}
// Автокликер (каждую секунду)
function autoClick() {
const perSecond = getPerSecond();
if (perSecond > 0) {
gameState.score += perSecond / 10; // Обновляем 10 раз в секунду для плавности
gameState.score += perSecond / 10;
updateUI();
}
}
// Сброс игры
function resetGame() {
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();
}
}
// Инициализация
loadGame();
updateUI();
(async () => {
await loadGame();
updateUI();
document.getElementById('clickButton').addEventListener('click', handleClick);
document.getElementById('resetButton').addEventListener('click', resetGame);
document.getElementById('clickButton').addEventListener('click', handleClick);
document.getElementById('resetButton').addEventListener('click', resetGame);
// Автокликер каждые 100мс
setInterval(autoClick, 100);
// Автокликер каждые 100мс
setInterval(autoClick, 100);
// Автосохранение каждые 10 секунд
setInterval(saveGame, 10000);
// Автосохранение каждые 30 секунд
setInterval(saveGame, 30000);
})();
</script>
</body>
</html>