Добавить investor.html
This commit is contained in:
+402
@@ -0,0 +1,402 @@
|
|||||||
|
<!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', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #0f3443 0%, #34e89e 100%);
|
||||||
|
color: #fff; min-height: 100vh; padding: 20px;
|
||||||
|
}
|
||||||
|
.container { max-width: 1000px; margin: 0 auto; }
|
||||||
|
h1 { text-align: center; margin-bottom: 30px; font-size: 36px; text-shadow: 0 2px 10px rgba(0,0,0,0.3); }
|
||||||
|
|
||||||
|
.status-bar {
|
||||||
|
background: rgba(0,0,0,0.3); border-radius: 12px;
|
||||||
|
padding: 20px; margin-bottom: 20px;
|
||||||
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
.status-item { text-align: center; }
|
||||||
|
.status-label { font-size: 12px; opacity: 0.7; text-transform: uppercase; letter-spacing: 1px; }
|
||||||
|
.status-value { font-size: 28px; font-weight: bold; color: #ffd700; margin-top: 5px; }
|
||||||
|
|
||||||
|
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||||
|
@media (max-width: 768px) { .grid { grid-template-columns: 1fr; } }
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: rgba(0,0,0,0.3); border-radius: 12px;
|
||||||
|
padding: 25px; backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.card h2 { margin-bottom: 15px; color: #ffd700; font-size: 20px; }
|
||||||
|
|
||||||
|
.clicker-info {
|
||||||
|
background: rgba(255,215,0,0.1); border: 1px solid rgba(255,215,0,0.3);
|
||||||
|
border-radius: 8px; padding: 15px; margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.clicker-info .score { font-size: 32px; color: #ffd700; font-weight: bold; }
|
||||||
|
.clicker-info .label { font-size: 12px; opacity: 0.7; margin-bottom: 5px; }
|
||||||
|
|
||||||
|
.invest-btn, .action-btn {
|
||||||
|
width: 100%; padding: 12px; border: none; border-radius: 8px;
|
||||||
|
font-size: 14px; font-weight: bold; cursor: pointer;
|
||||||
|
margin-bottom: 10px; color: white; transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.invest-btn { background: linear-gradient(135deg, #f093fb, #f5576c); }
|
||||||
|
.invest-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(245,87,108,0.4); }
|
||||||
|
.invest-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||||
|
.action-btn { background: linear-gradient(135deg, #667eea, #764ba2); }
|
||||||
|
.action-btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(102,126,234,0.4); }
|
||||||
|
|
||||||
|
.history { max-height: 300px; overflow-y: auto; }
|
||||||
|
.history-item {
|
||||||
|
background: rgba(255,255,255,0.05); padding: 10px;
|
||||||
|
border-radius: 6px; margin-bottom: 8px; font-size: 13px;
|
||||||
|
display: flex; justify-content: space-between;
|
||||||
|
border: 1px solid rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
|
.history-item .amount { font-weight: bold; }
|
||||||
|
.history-item .amount.positive { color: #4caf50; }
|
||||||
|
.history-item .amount.negative { color: #f44336; }
|
||||||
|
|
||||||
|
.log {
|
||||||
|
background: rgba(0,0,0,0.4); border-radius: 8px;
|
||||||
|
padding: 10px; margin-top: 15px; font-family: monospace;
|
||||||
|
font-size: 12px; max-height: 150px; overflow-y: auto;
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.log-entry { margin-bottom: 3px; padding: 2px 0; }
|
||||||
|
.log-entry.error { color: #f44336; }
|
||||||
|
.log-entry.success { color: #4caf50; }
|
||||||
|
.log-entry.info { color: #aaa; }
|
||||||
|
|
||||||
|
.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="container">
|
||||||
|
<h1>💰 Инвестор</h1>
|
||||||
|
|
||||||
|
<div class="status-bar">
|
||||||
|
<div class="status-item">
|
||||||
|
<div class="status-label">Ваш капитал</div>
|
||||||
|
<div class="status-value" id="capital">0</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-item">
|
||||||
|
<div class="status-label">Доход/сек</div>
|
||||||
|
<div class="status-value" id="income">0</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-item">
|
||||||
|
<div class="status-label">Инвестировано</div>
|
||||||
|
<div class="status-value" id="invested">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<h2>📊 Состояние кликера</h2>
|
||||||
|
<div class="clicker-info">
|
||||||
|
<div class="label">Счёт игрока в Кликере:</div>
|
||||||
|
<div class="score" id="clickerScore">—</div>
|
||||||
|
</div>
|
||||||
|
<button class="action-btn" id="refreshBtn">🔄 Обновить данные кликера</button>
|
||||||
|
<button class="action-btn" id="boostBtn">🚀 Бонус +1000 к кликеру (стоит 500)</button>
|
||||||
|
<button class="action-btn" id="manageBtn">⚙️ Управление разрешениями</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>💼 Инвестиции</h2>
|
||||||
|
<p style="margin-bottom: 15px; opacity: 0.8; font-size: 14px;">
|
||||||
|
Инвестируйте капитал — получайте 1% дохода в секунду
|
||||||
|
</p>
|
||||||
|
<button class="invest-btn" data-amount="100">Инвестировать 100</button>
|
||||||
|
<button class="invest-btn" data-amount="500">Инвестировать 500</button>
|
||||||
|
<button class="invest-btn" data-amount="1000">Инвестировать 1000</button>
|
||||||
|
<button class="invest-btn" data-amount="5000">Инвестировать 5000</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top: 20px;">
|
||||||
|
<h2>📜 История операций</h2>
|
||||||
|
<div class="history" id="history"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="log" id="log"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// КОНФИГУРАЦИЯ
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
const CLICKER_SAVE = 'main_save'; // Save кликера (чужой!)
|
||||||
|
const CLICKER_TABLE = 'game_save'; // Таблица кликера
|
||||||
|
const MY_SAVE = 'investor_save'; // Наш save
|
||||||
|
const MY_TABLE = 'investor_data'; // Наша таблица данных
|
||||||
|
const HISTORY_TABLE = 'history'; // Наша таблица истории
|
||||||
|
|
||||||
|
let myState = {
|
||||||
|
capital: 1000,
|
||||||
|
invested: 0,
|
||||||
|
income_per_sec: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let clickerScore = null;
|
||||||
|
let api = null;
|
||||||
|
|
||||||
|
const statusEl = document.getElementById('apiStatus');
|
||||||
|
function updateStatus(text, type = '') {
|
||||||
|
statusEl.textContent = text;
|
||||||
|
statusEl.className = 'api-status ' + type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(msg, type = 'info') {
|
||||||
|
const el = document.getElementById('log');
|
||||||
|
const entry = document.createElement('div');
|
||||||
|
entry.className = 'log-entry ' + type;
|
||||||
|
entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
||||||
|
el.insertBefore(entry, el.firstChild);
|
||||||
|
// Ограничиваем количество записей
|
||||||
|
while (el.children.length > 50) {
|
||||||
|
el.removeChild(el.lastChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUI() {
|
||||||
|
document.getElementById('capital').textContent = Math.floor(myState.capital).toLocaleString();
|
||||||
|
document.getElementById('income').textContent = myState.income_per_sec.toLocaleString();
|
||||||
|
document.getElementById('invested').textContent = Math.floor(myState.invested).toLocaleString();
|
||||||
|
document.getElementById('clickerScore').textContent =
|
||||||
|
clickerScore !== null ? Math.floor(clickerScore).toLocaleString() : '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// ИНИЦИАЛИЗАЦИЯ API
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
window.addEventListener('gameApiReady', async () => {
|
||||||
|
api = window.gameApi;
|
||||||
|
updateStatus('✓ API подключён', 'connected');
|
||||||
|
log('API подключён', 'success');
|
||||||
|
|
||||||
|
// Создаём свои таблицы
|
||||||
|
try {
|
||||||
|
await api.createTable(MY_TABLE, {
|
||||||
|
'key': 'TEXT', 'value': 'TEXT'
|
||||||
|
}, MY_SAVE, 'Данные инвестора');
|
||||||
|
log('Таблица данных создана', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
log('Таблица данных уже существует');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.createTable(HISTORY_TABLE, {
|
||||||
|
'type': 'TEXT', 'amount': 'NUMBER', 'timestamp': 'TEXT'
|
||||||
|
}, MY_SAVE, 'История операций');
|
||||||
|
log('Таблица истории создана', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
log('Таблица истории уже существует');
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadState();
|
||||||
|
await loadHistory();
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
// Доход каждую секунду
|
||||||
|
setInterval(tick, 1000);
|
||||||
|
// Автосохранение
|
||||||
|
setInterval(saveState, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fallback если API не подключился
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!api) {
|
||||||
|
updateStatus('✗ API недоступен', 'error');
|
||||||
|
log('API недоступен, работаем без сохранений', 'error');
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// СОХРАНЕНИЕ / ЗАГРУЗКА СОСТОЯНИЯ
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
async function loadState() {
|
||||||
|
if (!api) return;
|
||||||
|
try {
|
||||||
|
const result = await api.selectData(MY_TABLE, { key: 'state' }, MY_SAVE);
|
||||||
|
if (result.data && result.data.length > 0) {
|
||||||
|
myState = JSON.parse(result.data[0].value);
|
||||||
|
log('Состояние загружено', 'success');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('Ошибка загрузки: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveState() {
|
||||||
|
if (!api) return;
|
||||||
|
try {
|
||||||
|
await api.deleteData(MY_TABLE, { key: 'state' }, MY_SAVE);
|
||||||
|
await api.insertData(MY_TABLE, {
|
||||||
|
key: 'state',
|
||||||
|
value: JSON.stringify(myState),
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
}, MY_SAVE);
|
||||||
|
} catch (e) {
|
||||||
|
log('Ошибка сохранения: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadHistory() {
|
||||||
|
if (!api) return;
|
||||||
|
try {
|
||||||
|
const result = await api.selectData(HISTORY_TABLE, {}, MY_SAVE);
|
||||||
|
renderHistory(result.data || []);
|
||||||
|
} catch (e) {
|
||||||
|
log('Ошибка загрузки истории: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHistory(items) {
|
||||||
|
const el = document.getElementById('history');
|
||||||
|
if (items.length === 0) {
|
||||||
|
el.innerHTML = '<div style="text-align:center;opacity:0.5;padding:20px">Нет операций</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.innerHTML = items.slice().reverse().map(item => `
|
||||||
|
<div class="history-item">
|
||||||
|
<span>${item.type}</span>
|
||||||
|
<span class="amount ${item.amount >= 0 ? 'positive' : 'negative'}">
|
||||||
|
${item.amount > 0 ? '+' : ''}${item.amount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addHistory(type, amount) {
|
||||||
|
if (!api) return;
|
||||||
|
try {
|
||||||
|
await api.insertData(HISTORY_TABLE, {
|
||||||
|
type, amount,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}, MY_SAVE);
|
||||||
|
await loadHistory();
|
||||||
|
} catch (e) {
|
||||||
|
log('Ошибка записи истории: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// ВЗАИМОДЕЙСТВИЕ С КЛИКЕРОМ
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
async function refreshClicker() {
|
||||||
|
if (!api) {
|
||||||
|
log('API недоступен', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Читаем save кликера (потребуется разрешение на select в main_save)
|
||||||
|
const result = await api.selectData(CLICKER_TABLE, { key: 'game_state' }, CLICKER_SAVE);
|
||||||
|
if (result.data && result.data.length > 0) {
|
||||||
|
const state = JSON.parse(result.data[0].value);
|
||||||
|
clickerScore = state.score || 0;
|
||||||
|
log(`Счёт кликера: ${clickerScore}`, 'success');
|
||||||
|
} else {
|
||||||
|
clickerScore = 0;
|
||||||
|
log('Кликер ещё не сохранял данные', 'info');
|
||||||
|
}
|
||||||
|
updateUI();
|
||||||
|
} catch (e) {
|
||||||
|
log('Ошибка чтения кликера: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function boostClicker() {
|
||||||
|
if (!api) {
|
||||||
|
log('API недоступен', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (myState.capital < 500) {
|
||||||
|
log('Недостаточно капитала (нужно 500)', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 1. Читаем текущее состояние кликера
|
||||||
|
const result = await api.selectData(CLICKER_TABLE, { key: 'game_state' }, CLICKER_SAVE);
|
||||||
|
if (!result.data || result.data.length === 0) {
|
||||||
|
log('Кликер не найден — сначала запустите его', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const state = JSON.parse(result.data[0].value);
|
||||||
|
const oldScore = state.score || 0;
|
||||||
|
state.score = oldScore + 1000;
|
||||||
|
|
||||||
|
// 2. Обновляем в save кликера (потребуется разрешение на insert в main_save)
|
||||||
|
await api.deleteData(CLICKER_TABLE, { key: 'game_state' }, CLICKER_SAVE);
|
||||||
|
await api.insertData(CLICKER_TABLE, {
|
||||||
|
key: 'game_state',
|
||||||
|
value: JSON.stringify(state),
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
}, CLICKER_SAVE);
|
||||||
|
|
||||||
|
// 3. Списываем капитал
|
||||||
|
myState.capital -= 500;
|
||||||
|
await addHistory('Бонус кликеру (+1000)', -500);
|
||||||
|
log(`✅ Кликеру добавлено +1000 (было ${oldScore}, стало ${state.score})`, 'success');
|
||||||
|
updateUI();
|
||||||
|
} catch (e) {
|
||||||
|
log('Ошибка: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
// ИНВЕСТИЦИИ
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
async function invest(amount) {
|
||||||
|
if (myState.capital < amount) {
|
||||||
|
log('Недостаточно капитала', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
myState.capital -= amount;
|
||||||
|
myState.invested += amount;
|
||||||
|
myState.income_per_sec += Math.floor(amount / 100); // 1% в секунду
|
||||||
|
await addHistory('Инвестиция', -amount);
|
||||||
|
log(`Инвестировано ${amount} (+${Math.floor(amount/100)}/сек)`, 'success');
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tick() {
|
||||||
|
if (myState.income_per_sec > 0) {
|
||||||
|
myState.capital += myState.income_per_sec;
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// ОБРАБОТЧИКИ
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
document.getElementById('refreshBtn').onclick = refreshClicker;
|
||||||
|
document.getElementById('boostBtn').onclick = boostClicker;
|
||||||
|
document.getElementById('manageBtn').onclick = () => {
|
||||||
|
if (api) api.managePermissions();
|
||||||
|
else log('API недоступен', 'error');
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('.invest-btn').forEach(btn => {
|
||||||
|
btn.onclick = () => invest(parseInt(btn.dataset.amount));
|
||||||
|
});
|
||||||
|
|
||||||
|
updateUI();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user