Обновить clicker.html
This commit is contained in:
+568
-13
@@ -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,24 +787,82 @@
|
||||
hyperAuto: '💎 Гипер автокликер'
|
||||
};
|
||||
|
||||
// Загрузка сохранения
|
||||
function loadGame() {
|
||||
const saved = localStorage.getItem('clickerGame');
|
||||
if (saved) {
|
||||
const savedState = JSON.parse(saved);
|
||||
// Показ индикатора сохранения
|
||||
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() {
|
||||
// Сохранение игры через 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() {
|
||||
@@ -405,28 +948,39 @@
|
||||
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();
|
||||
(async () => {
|
||||
await loadGame();
|
||||
updateUI();
|
||||
|
||||
document.getElementById('clickButton').addEventListener('click', handleClick);
|
||||
@@ -435,8 +989,9 @@
|
||||
// Автокликер каждые 100мс
|
||||
setInterval(autoClick, 100);
|
||||
|
||||
// Автосохранение каждые 10 секунд
|
||||
setInterval(saveGame, 10000);
|
||||
// Автосохранение каждые 30 секунд
|
||||
setInterval(saveGame, 30000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user