1033 lines
48 KiB
HTML
1033 lines
48 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', sans-serif;
|
||
background: linear-gradient(135deg, #0a0e27 0%, #1a1a3e 100%);
|
||
color: #fff; min-height: 100vh; padding: 20px;
|
||
}
|
||
.container { max-width: 1400px; margin: 0 auto; }
|
||
h1 { text-align: center; margin-bottom: 30px; font-size: 42px;
|
||
background: linear-gradient(135deg, #00f2fe, #4facfe);
|
||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||
|
||
.status-bar {
|
||
background: rgba(0,0,0,0.4); border-radius: 15px;
|
||
padding: 20px; margin-bottom: 20px;
|
||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px;
|
||
}
|
||
.status-item { text-align: center; }
|
||
.status-label { font-size: 11px; opacity: 0.7; text-transform: uppercase; }
|
||
.status-value { font-size: 24px; font-weight: bold; color: #00f2fe; margin-top: 5px; }
|
||
|
||
.tabs { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
|
||
.tab {
|
||
flex: 1; min-width: 120px; padding: 15px; border: none; border-radius: 10px;
|
||
background: rgba(255,255,255,0.1); color: #fff; cursor: pointer;
|
||
font-size: 14px; font-weight: bold; transition: all 0.3s;
|
||
}
|
||
.tab.active { background: linear-gradient(135deg, #00f2fe, #4facfe); color: #000; }
|
||
.tab:hover:not(.active) { background: rgba(255,255,255,0.2); }
|
||
|
||
.tab-content { display: none; }
|
||
.tab-content.active { display: block; }
|
||
|
||
.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.4); border-radius: 12px;
|
||
padding: 20px; border: 1px solid rgba(255,255,255,0.1);
|
||
}
|
||
.card h2 { margin-bottom: 15px; color: #00f2fe; font-size: 20px; }
|
||
|
||
.crypto-item {
|
||
background: rgba(255,255,255,0.05); padding: 15px;
|
||
border-radius: 8px; margin-bottom: 10px;
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
}
|
||
.crypto-header {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
margin-bottom: 10px; flex-wrap: wrap; gap: 10px;
|
||
}
|
||
.crypto-name { font-weight: bold; font-size: 18px; }
|
||
.crypto-price { color: #00f2fe; font-size: 20px; font-weight: bold; }
|
||
.crypto-change { font-size: 14px; padding: 3px 8px; border-radius: 5px; }
|
||
.crypto-change.up { color: #4caf50; background: rgba(76, 175, 80, 0.2); }
|
||
.crypto-change.down { color: #f44336; background: rgba(244, 67, 54, 0.2); }
|
||
|
||
.btn {
|
||
padding: 10px 20px; border: none; border-radius: 8px;
|
||
font-size: 14px; font-weight: bold; cursor: pointer;
|
||
color: white; transition: all 0.2s; margin: 5px;
|
||
}
|
||
.btn-buy { background: linear-gradient(135deg, #4caf50, #45a049); }
|
||
.btn-sell { background: linear-gradient(135deg, #f44336, #d32f2f); }
|
||
.btn-transfer { background: linear-gradient(135deg, #ff9800, #f57c00); }
|
||
.btn-action { background: linear-gradient(135deg, #9c27b0, #7b1fa2); }
|
||
.btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.3); }
|
||
.btn:active { transform: translateY(0); }
|
||
|
||
.quick-actions { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 10px; }
|
||
.quick-actions button { flex: 1; min-width: 80px; padding: 8px; font-size: 12px; }
|
||
|
||
.progress-bar {
|
||
background: rgba(255,255,255,0.1); border-radius: 10px;
|
||
height: 40px; overflow: hidden; margin: 10px 0;
|
||
}
|
||
.progress-fill {
|
||
height: 100%; background: linear-gradient(90deg, #00f2fe, #4facfe);
|
||
transition: width 0.5s; display: flex; align-items: center;
|
||
justify-content: center; font-weight: bold; font-size: 16px;
|
||
}
|
||
|
||
.shop-item {
|
||
background: rgba(255,255,255,0.05); padding: 15px;
|
||
border-radius: 8px; margin-bottom: 10px;
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
border: 1px solid rgba(255,255,255,0.1); flex-wrap: wrap; gap: 10px;
|
||
}
|
||
.shop-item.owned { border-color: #4caf50; background: rgba(76, 175, 80, 0.1); }
|
||
.item-info { flex: 1; min-width: 200px; }
|
||
.item-name { font-weight: bold; font-size: 16px; margin-bottom: 5px; }
|
||
.item-desc { font-size: 13px; opacity: 0.8; }
|
||
.item-price { color: #ffd700; font-weight: bold; font-size: 18px; margin-right: 15px; }
|
||
|
||
.log {
|
||
background: rgba(0,0,0,0.6); border-radius: 8px;
|
||
padding: 15px; margin-top: 20px; font-family: monospace;
|
||
font-size: 12px; max-height: 250px; overflow-y: auto;
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
}
|
||
.log-entry { margin-bottom: 5px; padding: 3px 0; }
|
||
.log-entry.error { color: #f44336; }
|
||
.log-entry.success { color: #4caf50; }
|
||
.log-entry.info { color: #aaa; }
|
||
.log-entry.warning { color: #ff9800; }
|
||
|
||
.api-status {
|
||
position: fixed; top: 10px; right: 10px;
|
||
padding: 10px 20px; border-radius: 20px;
|
||
font-size: 12px; background: rgba(0, 0, 0, 0.8);
|
||
color: #aaa; z-index: 1000;
|
||
}
|
||
.api-status.connected { color: #4caf50; border: 1px solid #4caf50; }
|
||
.api-status.fallback { color: #ff9800; border: 1px solid #ff9800; }
|
||
|
||
.transfer-section {
|
||
background: rgba(255, 152, 0, 0.1); border: 1px solid rgba(255, 152, 0, 0.3);
|
||
border-radius: 8px; padding: 15px; margin-bottom: 15px;
|
||
}
|
||
.transfer-section h3 { color: #ff9800; margin-bottom: 10px; font-size: 16px; }
|
||
.transfer-input {
|
||
width: 100%; padding: 12px; border: 1px solid rgba(255,255,255,0.2);
|
||
border-radius: 6px; background: rgba(0,0,0,0.4); color: #fff;
|
||
margin-bottom: 10px; font-size: 14px;
|
||
}
|
||
.available-info {
|
||
background: rgba(0,255,0,0.1); padding: 8px; border-radius: 5px;
|
||
margin-bottom: 10px; font-size: 13px; color: #4caf50;
|
||
}
|
||
|
||
.construction-stage {
|
||
background: rgba(255,255,255,0.05); padding: 20px;
|
||
border-radius: 8px; margin-bottom: 15px;
|
||
border: 2px solid rgba(255,255,255,0.1);
|
||
}
|
||
.construction-stage.completed { border-color: #4caf50; background: rgba(76, 175, 80, 0.15); }
|
||
.construction-stage.active { border-color: #00f2fe; background: rgba(0, 242, 254, 0.1); }
|
||
|
||
.save-indicator {
|
||
position: fixed; bottom: 20px; right: 20px;
|
||
padding: 12px 24px; border-radius: 25px;
|
||
font-size: 14px; background: linear-gradient(135deg, #4caf50, #45a049);
|
||
color: white; z-index: 1000; opacity: 0; transition: opacity 0.3s;
|
||
box-shadow: 0 5px 20px rgba(76, 175, 80, 0.4);
|
||
}
|
||
.save-indicator.show { opacity: 1; }
|
||
|
||
.money-display {
|
||
font-size: 28px; font-weight: bold; color: #4caf50;
|
||
margin: 10px 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="api-status" id="apiStatus">⏳ Загрузка...</div>
|
||
<div class="save-indicator" id="saveIndicator">💾 Сохранено!</div>
|
||
|
||
<div class="container">
|
||
<h1>💎 Крипто-Магнат</h1>
|
||
|
||
<div class="status-bar">
|
||
<div class="status-item">
|
||
<div class="status-label">💵 Наличные</div>
|
||
<div class="status-value" id="cash">$10 000</div>
|
||
</div>
|
||
<div class="status-item">
|
||
<div class="status-label">₿ Крипто</div>
|
||
<div class="status-value" id="cryptoValue">$0</div>
|
||
</div>
|
||
<div class="status-item">
|
||
<div class="status-label">🏢 Доход</div>
|
||
<div class="status-value" id="businessIncome">$0/сек</div>
|
||
</div>
|
||
<div class="status-item">
|
||
<div class="status-label">🏠 Дом</div>
|
||
<div class="status-value" id="houseLevel">0%</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tabs">
|
||
<button class="tab active" data-tab="crypto">₿ Биржа</button>
|
||
<button class="tab" data-tab="transfer">💸 Переводы</button>
|
||
<button class="tab" data-tab="house">🏠 Дом</button>
|
||
<button class="tab" data-tab="business">🏢 Бизнесы</button>
|
||
<button class="tab" data-tab="luxury">💎 Роскошь</button>
|
||
</div>
|
||
|
||
<!-- КРИПТО БИРЖА -->
|
||
<div class="tab-content active" id="crypto">
|
||
<div class="grid">
|
||
<div class="card">
|
||
<h2>📊 Криптовалюты</h2>
|
||
<div id="cryptoList"></div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>💼 Мой портфель</h2>
|
||
<div id="portfolio"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ПЕРЕВОДЫ -->
|
||
<div class="tab-content" id="transfer">
|
||
<div class="grid">
|
||
<div class="card">
|
||
<h2>📥 Ввод средств</h2>
|
||
<div class="transfer-section">
|
||
<h3>Из Кликера</h3>
|
||
<div class="available-info">Доступно: $<span id="clickerAvailable">0</span></div>
|
||
<input type="number" class="transfer-input" id="fromClicker" placeholder="Сумма перевода">
|
||
<button class="btn btn-transfer" onclick="transferFrom('clicker')">💸 Перевести в крипто</button>
|
||
</div>
|
||
<div class="transfer-section">
|
||
<h3>Из Инвестора</h3>
|
||
<div class="available-info">Доступно: $<span id="investorAvailable">0</span></div>
|
||
<input type="number" class="transfer-input" id="fromInvestor" placeholder="Сумма перевода">
|
||
<button class="btn btn-transfer" onclick="transferFrom('investor')">💸 Перевести в крипто</button>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>📤 Вывод средств</h2>
|
||
<div class="transfer-section">
|
||
<h3>В Кликер</h3>
|
||
<div class="available-info">Ваш баланс: $<span id="myCash1">0</span></div>
|
||
<input type="number" class="transfer-input" id="toClicker" placeholder="Сумма перевода">
|
||
<button class="btn btn-transfer" onclick="transferTo('clicker')">💸 Отправить</button>
|
||
</div>
|
||
<div class="transfer-section">
|
||
<h3>В Инвестор</h3>
|
||
<div class="available-info">Ваш баланс: $<span id="myCash2">0</span></div>
|
||
<input type="number" class="transfer-input" id="toInvestor" placeholder="Сумма перевода">
|
||
<button class="btn btn-transfer" onclick="transferTo('investor')">💸 Отправить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- СТРОИТЕЛЬСТВО ДОМА -->
|
||
<div class="tab-content" id="house">
|
||
<div class="card">
|
||
<h2>🏗️ Строительство дома</h2>
|
||
<div class="money-display">Ваши деньги: $<span id="cashForHouse">10000</span></div>
|
||
<div id="constructionStages"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- БИЗНЕСЫ -->
|
||
<div class="tab-content" id="business">
|
||
<div class="card">
|
||
<h2>🏢 Мои бизнесы</h2>
|
||
<div class="money-display">Ваши деньги: $<span id="cashForBusiness">10000</span></div>
|
||
<div id="businessList"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- РОСКОШЬ -->
|
||
<div class="tab-content" id="luxury">
|
||
<div class="card">
|
||
<h2>💎 Предметы роскоши</h2>
|
||
<div class="money-display">Ваши деньги: $<span id="cashForLuxury">10000</span></div>
|
||
<div id="luxuryList"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="log" id="log"></div>
|
||
</div>
|
||
|
||
<script>
|
||
// ═══════════════════════════════════════════════════════════
|
||
// КОНФИГУРАЦИЯ
|
||
// ══════════════════════════════════════════════════════════
|
||
const CLICKER_SAVE = 'main_save';
|
||
const CLICKER_TABLE = 'game_save';
|
||
const INVESTOR_SAVE = 'investor_save';
|
||
const INVESTOR_TABLE = 'investor_data';
|
||
const MY_SAVE = 'crypto_save';
|
||
const MY_TABLE = 'crypto_data';
|
||
const LOCAL_STORAGE_KEY = 'cryptoTycoonSave_v2';
|
||
|
||
let myState = {
|
||
cash: 10000,
|
||
portfolio: {},
|
||
businesses: {},
|
||
luxury: {},
|
||
house: { stage: 0, progress: 0 }
|
||
};
|
||
|
||
const cryptos = {
|
||
BTC: { name: 'Bitcoin', price: 50000, change: 0 },
|
||
ETH: { name: 'Ethereum', price: 3000, change: 0 },
|
||
DOGE: { name: 'Dogecoin', price: 0.15, change: 0 },
|
||
SOL: { name: 'Solana', price: 120, change: 0 },
|
||
SHIB: { name: 'Shiba Inu', price: 0.000025, change: 0 }
|
||
};
|
||
|
||
const houseStages = [
|
||
{ name: 'Фундамент', cost: 50000, time: 60 },
|
||
{ name: 'Стены', cost: 100000, time: 120 },
|
||
{ name: 'Крыша', cost: 75000, time: 90 },
|
||
{ name: 'Отделка', cost: 150000, time: 180 },
|
||
{ name: 'Мебель', cost: 200000, time: 240 }
|
||
];
|
||
|
||
const businesses = [
|
||
{ id: 'shop1', name: '🏪 Продуктовый магазин', cost: 25000, income: 50 },
|
||
{ id: 'shop2', name: '👔 Магазин одежды', cost: 75000, income: 150 },
|
||
{ id: 'shop3', name: '🍽️ Ресторан', cost: 200000, income: 400 },
|
||
{ id: 'shop4', name: '🚗 Автосалон', cost: 500000, income: 1000 },
|
||
{ id: 'shop5', name: '🏬 Торговый центр', cost: 2000000, income: 5000 }
|
||
];
|
||
|
||
const luxuryItems = [
|
||
{ id: 'watch1', name: '⌚ Часы Rolex', cost: 50000, desc: 'Статус +10%' },
|
||
{ id: 'car1', name: '🚗 Tesla Model S', cost: 150000, desc: 'Статус +20%' },
|
||
{ id: 'suit1', name: '👔 Костюм Brioni', cost: 75000, desc: 'Статус +15%' },
|
||
{ id: 'yacht1', name: '🛥️ Яхта', cost: 1000000, desc: 'Статус +50%' },
|
||
{ id: 'jet1', name: '✈️ Частный самолёт', cost: 5000000, desc: 'Статус +100%' }
|
||
];
|
||
|
||
let api = null;
|
||
const statusEl = document.getElementById('apiStatus');
|
||
const saveIndicator = document.getElementById('saveIndicator');
|
||
|
||
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 showSaveIndicator() {
|
||
saveIndicator.classList.add('show');
|
||
setTimeout(() => saveIndicator.classList.remove('show'), 2000);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// ИНИЦИАЛИЗАЦИЯ
|
||
// ═══════════════════════════════════════════════════════════
|
||
async function initGame() {
|
||
await loadState();
|
||
updateUI();
|
||
renderCrypto();
|
||
renderHouse();
|
||
renderBusinesses();
|
||
renderLuxury();
|
||
|
||
setInterval(tick, 1000);
|
||
setInterval(updateCryptoPrices, 3000);
|
||
setInterval(saveState, 10000);
|
||
|
||
log('Игра загружена!', 'success');
|
||
}
|
||
|
||
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, 'Данные крипто-магната');
|
||
} catch (e) {
|
||
log('Таблица уже существует');
|
||
}
|
||
|
||
await initGame();
|
||
});
|
||
|
||
setTimeout(() => {
|
||
if (!api) {
|
||
updateStatus('⚠️ localStorage', 'fallback');
|
||
log('Работаем с localStorage', 'warning');
|
||
initGame();
|
||
}
|
||
}, 3000);
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// СОХРАНЕНИЕ / ЗАГРУЗКА
|
||
// ═══════════════════════════════════════════════════════════
|
||
async function loadState() {
|
||
if (api) {
|
||
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('Загружено из API', 'success');
|
||
return;
|
||
}
|
||
} catch (e) {
|
||
log('API ошибка: ' + e.message, 'error');
|
||
}
|
||
}
|
||
|
||
const saved = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||
if (saved) {
|
||
try {
|
||
myState = JSON.parse(saved);
|
||
log('Загружено из localStorage', 'success');
|
||
} catch (e) {
|
||
log('Ошибка загрузки', 'error');
|
||
}
|
||
}
|
||
}
|
||
|
||
async function saveState() {
|
||
if (api) {
|
||
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);
|
||
showSaveIndicator();
|
||
return;
|
||
} catch (e) {
|
||
log('API save error', 'error');
|
||
}
|
||
}
|
||
|
||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(myState));
|
||
showSaveIndicator();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// КРИПТО БИРЖА
|
||
// ═══════════════════════════════════════════════════════════
|
||
function updateCryptoPrices() {
|
||
for (let symbol in cryptos) {
|
||
const change = (Math.random() - 0.5) * 0.1;
|
||
cryptos[symbol].change = change;
|
||
cryptos[symbol].price *= (1 + change);
|
||
if (cryptos[symbol].price < 0.000001) cryptos[symbol].price = 0.000001;
|
||
}
|
||
renderCrypto();
|
||
updateUI();
|
||
}
|
||
|
||
function renderCrypto() {
|
||
const list = document.getElementById('cryptoList');
|
||
list.innerHTML = '';
|
||
|
||
for (let symbol in cryptos) {
|
||
const crypto = cryptos[symbol];
|
||
const changeClass = crypto.change >= 0 ? 'up' : 'down';
|
||
const changeSign = crypto.change >= 0 ? '+' : '';
|
||
const priceDisplay = crypto.price < 1 ? crypto.price.toFixed(6) : crypto.price.toFixed(2);
|
||
const owned = myState.portfolio[symbol] || 0;
|
||
const ownedValue = owned * crypto.price;
|
||
|
||
const div = document.createElement('div');
|
||
div.className = 'crypto-item';
|
||
div.innerHTML = `
|
||
<div class="crypto-header">
|
||
<div>
|
||
<div class="crypto-name">${symbol}</div>
|
||
<div style="font-size: 12px; opacity: 0.7;">${crypto.name}</div>
|
||
</div>
|
||
<div style="text-align: right;">
|
||
<div class="crypto-price">$${priceDisplay}</div>
|
||
<span class="crypto-change ${changeClass}">${changeSign}${(crypto.change * 100).toFixed(2)}%</span>
|
||
</div>
|
||
</div>
|
||
${owned > 0 ? `
|
||
<div style="margin: 10px 0; padding: 10px; background: rgba(0,242,254,0.1); border-radius: 5px;">
|
||
У вас: ${owned.toFixed(6)} ${symbol} ≈ $${ownedValue.toFixed(2)}
|
||
</div>
|
||
` : ''}
|
||
<div class="quick-actions">
|
||
<button class="btn btn-buy" onclick="buyCrypto('${symbol}', 100)">Купить на $100</button>
|
||
<button class="btn btn-buy" onclick="buyCrypto('${symbol}', 1000)">Купить на $1000</button>
|
||
<button class="btn btn-buy" onclick="buyCrypto('${symbol}', 10000)">Купить на $10000</button>
|
||
${owned > 0 ? `<button class="btn btn-sell" onclick="sellCrypto('${symbol}', 1)">Продать всё</button>` : ''}
|
||
</div>
|
||
`;
|
||
list.appendChild(div);
|
||
}
|
||
renderPortfolio();
|
||
}
|
||
|
||
function renderPortfolio() {
|
||
const portfolio = document.getElementById('portfolio');
|
||
let totalValue = 0;
|
||
let hasItems = false;
|
||
|
||
for (let symbol in myState.portfolio) {
|
||
const amount = myState.portfolio[symbol];
|
||
if (amount < 0.000001) continue;
|
||
hasItems = true;
|
||
const value = amount * cryptos[symbol].price;
|
||
totalValue += value;
|
||
|
||
const div = document.createElement('div');
|
||
div.className = 'crypto-item';
|
||
div.innerHTML = `
|
||
<div class="crypto-header">
|
||
<div>
|
||
<div class="crypto-name">${symbol}</div>
|
||
<div style="font-size: 13px;">${amount.toFixed(6)} шт.</div>
|
||
</div>
|
||
<div style="text-align: right;">
|
||
<div class="crypto-price">$${value.toFixed(2)}</div>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-sell" style="width: 100%; margin: 10px 0 0 0;"
|
||
onclick="sellCrypto('${symbol}', 1)">
|
||
Продать за $${value.toFixed(2)}
|
||
</button>
|
||
`;
|
||
portfolio.appendChild(div);
|
||
}
|
||
|
||
if (!hasItems) {
|
||
portfolio.innerHTML = '<div style="text-align:center;opacity:0.5;padding:30px">Портфель пуст<br>Купите криптовалюту!</div>';
|
||
}
|
||
|
||
document.getElementById('cryptoValue').textContent = '$' + Math.floor(totalValue).toLocaleString();
|
||
}
|
||
|
||
function buyCrypto(symbol, amountUsd) {
|
||
if (myState.cash < amountUsd) {
|
||
log(`❌ Недостаточно денег! Есть $${Math.floor(myState.cash)}, нужно $${amountUsd}`, 'error');
|
||
return;
|
||
}
|
||
|
||
const price = cryptos[symbol].price;
|
||
const amount = amountUsd / price;
|
||
|
||
myState.cash -= amountUsd;
|
||
myState.portfolio[symbol] = (myState.portfolio[symbol] || 0) + amount;
|
||
|
||
log(`✅ Куплено ${amount.toFixed(6)} ${symbol} за $${amountUsd}`, 'success');
|
||
saveState();
|
||
renderCrypto();
|
||
updateUI();
|
||
}
|
||
|
||
function sellCrypto(symbol, fraction) {
|
||
const amount = myState.portfolio[symbol];
|
||
if (!amount || amount < 0.000001) {
|
||
log(`❌ У вас нет ${symbol}`, 'error');
|
||
return;
|
||
}
|
||
|
||
const sellAmount = amount * fraction;
|
||
const revenue = sellAmount * cryptos[symbol].price;
|
||
|
||
myState.cash += revenue;
|
||
myState.portfolio[symbol] -= sellAmount;
|
||
|
||
if (myState.portfolio[symbol] < 0.000001) {
|
||
delete myState.portfolio[symbol];
|
||
}
|
||
|
||
log(`✅ Продано ${sellAmount.toFixed(6)} ${symbol} за $${revenue.toFixed(2)}`, 'success');
|
||
saveState();
|
||
renderCrypto();
|
||
updateUI();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// ПЕРЕВОДЫ
|
||
// ═══════════════════════════════════════════════════════════
|
||
async function transferFrom(source) {
|
||
if (!api) {
|
||
log('❌ API недоступен для переводов', 'error');
|
||
return;
|
||
}
|
||
|
||
const input = document.getElementById(source === 'clicker' ? 'fromClicker' : 'fromInvestor');
|
||
const amount = parseFloat(input.value);
|
||
|
||
if (!amount || amount <= 0) {
|
||
log('❌ Введите корректную сумму', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
let sourceTable, sourceSave, sourceKey;
|
||
if (source === 'clicker') {
|
||
sourceTable = CLICKER_TABLE;
|
||
sourceSave = CLICKER_SAVE;
|
||
sourceKey = 'game_state';
|
||
} else {
|
||
sourceTable = INVESTOR_TABLE;
|
||
sourceSave = INVESTOR_SAVE;
|
||
sourceKey = 'state';
|
||
}
|
||
|
||
const result = await api.selectData(sourceTable, { key: sourceKey }, sourceSave, {}, source);
|
||
if (!result.data || result.data.length === 0) {
|
||
log('❌ Источник не найден', 'error');
|
||
return;
|
||
}
|
||
|
||
const state = JSON.parse(result.data[0].value);
|
||
const available = source === 'clicker' ? state.score : state.capital;
|
||
|
||
if (available < amount) {
|
||
log(`❌ Недостаточно в источнике (есть ${Math.floor(available)}, нужно ${amount})`, 'error');
|
||
return;
|
||
}
|
||
|
||
if (source === 'clicker') {
|
||
state.score -= amount;
|
||
} else {
|
||
state.capital -= amount;
|
||
}
|
||
|
||
await api.deleteData(sourceTable, { key: sourceKey }, sourceSave, false, source);
|
||
await api.insertData(sourceTable, {
|
||
key: sourceKey,
|
||
value: JSON.stringify(state),
|
||
updated_at: new Date().toISOString()
|
||
}, sourceSave, source);
|
||
|
||
myState.cash += amount;
|
||
log(`✅ Получено $${amount.toLocaleString()} из ${source}`, 'success');
|
||
input.value = '';
|
||
saveState();
|
||
updateUI();
|
||
refreshAvailable();
|
||
} catch (e) {
|
||
log('❌ Ошибка перевода: ' + e.message, 'error');
|
||
}
|
||
}
|
||
|
||
async function transferTo(target) {
|
||
if (!api) {
|
||
log('❌ API недоступен для переводов', 'error');
|
||
return;
|
||
}
|
||
|
||
const input = document.getElementById(target === 'clicker' ? 'toClicker' : 'toInvestor');
|
||
const amount = parseFloat(input.value);
|
||
|
||
if (!amount || amount <= 0) {
|
||
log('❌ Введите корректную сумму', 'error');
|
||
return;
|
||
}
|
||
|
||
if (myState.cash < amount) {
|
||
log(`❌ Недостаточно средств (есть $${Math.floor(myState.cash)}, нужно $${amount})`, 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
let targetTable, targetSave, targetKey;
|
||
if (target === 'clicker') {
|
||
targetTable = CLICKER_TABLE;
|
||
targetSave = CLICKER_SAVE;
|
||
targetKey = 'game_state';
|
||
} else {
|
||
targetTable = INVESTOR_TABLE;
|
||
targetSave = INVESTOR_SAVE;
|
||
targetKey = 'state';
|
||
}
|
||
|
||
const result = await api.selectData(targetTable, { key: targetKey }, targetSave, {}, target);
|
||
if (!result.data || result.data.length === 0) {
|
||
log('❌ Цель не найдена', 'error');
|
||
return;
|
||
}
|
||
|
||
const state = JSON.parse(result.data[0].value);
|
||
if (target === 'clicker') {
|
||
state.score += amount;
|
||
} else {
|
||
state.capital += amount;
|
||
}
|
||
|
||
await api.deleteData(targetTable, { key: targetKey }, targetSave, false, target);
|
||
await api.insertData(targetTable, {
|
||
key: targetKey,
|
||
value: JSON.stringify(state),
|
||
updated_at: new Date().toISOString()
|
||
}, targetSave, target);
|
||
|
||
myState.cash -= amount;
|
||
log(`✅ Отправлено $${amount.toLocaleString()} в ${target}`, 'success');
|
||
input.value = '';
|
||
saveState();
|
||
updateUI();
|
||
} catch (e) {
|
||
log('❌ Ошибка: ' + e.message, 'error');
|
||
}
|
||
}
|
||
|
||
async function refreshAvailable() {
|
||
if (!api) return;
|
||
try {
|
||
const clickerResult = await api.selectData(CLICKER_TABLE, { key: 'game_state' }, CLICKER_SAVE, {}, 'clicker');
|
||
if (clickerResult.data && clickerResult.data.length > 0) {
|
||
const state = JSON.parse(clickerResult.data[0].value);
|
||
document.getElementById('clickerAvailable').textContent = Math.floor(state.score || 0).toLocaleString();
|
||
}
|
||
|
||
const investorResult = await api.selectData(INVESTOR_TABLE, { key: 'state' }, INVESTOR_SAVE, {}, 'investor');
|
||
if (investorResult.data && investorResult.data.length > 0) {
|
||
const state = JSON.parse(investorResult.data[0].value);
|
||
document.getElementById('investorAvailable').textContent = Math.floor(state.capital || 0).toLocaleString();
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// СТРОИТЕЛЬСТВО ДОМА
|
||
// ═══════════════════════════════════════════════════════════
|
||
function renderHouse() {
|
||
const container = document.getElementById('constructionStages');
|
||
container.innerHTML = '';
|
||
|
||
houseStages.forEach((stage, index) => {
|
||
const isCompleted = myState.house.stage > index;
|
||
const isActive = myState.house.stage === index;
|
||
const progress = isActive ? myState.house.progress : (isCompleted ? 100 : 0);
|
||
const canAfford = myState.cash >= stage.cost;
|
||
|
||
const div = document.createElement('div');
|
||
div.className = `construction-stage ${isCompleted ? 'completed' : ''} ${isActive ? 'active' : ''}`;
|
||
|
||
let html = `
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; flex-wrap: wrap; gap: 10px;">
|
||
<div>
|
||
<div style="font-weight: bold; font-size: 18px;">${index + 1}. ${stage.name}</div>
|
||
<div style="font-size: 13px; opacity: 0.8; margin-top: 5px;">
|
||
💰 Стоимость: $${stage.cost.toLocaleString()} | ⏱️ Время: ${stage.time}с
|
||
</div>
|
||
</div>
|
||
${isActive && !isCompleted ? `
|
||
<button class="btn btn-action" onclick="buildStage()"
|
||
${!canAfford && progress === 0 ? 'disabled' : ''}
|
||
style="${!canAfford && progress === 0 ? 'opacity: 0.5;' : ''}">
|
||
${progress > 0 ? '⚡ Продолжить' : '🏗️ Начать строить'}
|
||
</button>
|
||
` : ''}
|
||
${isCompleted ? '<div style="color: #4caf50; font-weight: bold; font-size: 20px;">✅ Готово</div>' : ''}
|
||
</div>
|
||
`;
|
||
|
||
if (isActive) {
|
||
html += `
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" style="width: ${progress}%">${Math.floor(progress)}%</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
div.innerHTML = html;
|
||
container.appendChild(div);
|
||
});
|
||
|
||
if (myState.house.stage >= houseStages.length) {
|
||
const div = document.createElement('div');
|
||
div.className = 'construction-stage completed';
|
||
div.innerHTML = '<div style="text-align: center; font-size: 20px; color: #4caf50;">🎉 Дом полностью построен!</div>';
|
||
container.appendChild(div);
|
||
}
|
||
}
|
||
|
||
function buildStage() {
|
||
if (myState.house.stage >= houseStages.length) {
|
||
log('🏠 Дом уже построен!', 'info');
|
||
return;
|
||
}
|
||
|
||
const stage = houseStages[myState.house.stage];
|
||
|
||
if (myState.house.progress === 0) {
|
||
if (myState.cash < stage.cost) {
|
||
log(`❌ Недостаточно денег! Есть $${Math.floor(myState.cash)}, нужно $${stage.cost}`, 'error');
|
||
return;
|
||
}
|
||
myState.cash -= stage.cost;
|
||
log(`💵 Списано $${stage.cost.toLocaleString()} за ${stage.name}`, 'info');
|
||
}
|
||
|
||
myState.house.progress += 10;
|
||
|
||
if (myState.house.progress >= 100) {
|
||
myState.house.stage++;
|
||
myState.house.progress = 0;
|
||
log(`✅ ${stage.name} построен!`, 'success');
|
||
} else {
|
||
log(`🏗️ ${stage.name}: ${Math.floor(myState.house.progress)}%`, 'info');
|
||
}
|
||
|
||
saveState();
|
||
renderHouse();
|
||
updateUI();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// БИЗНЕСЫ
|
||
// ═══════════════════════════════════════════════════════════
|
||
function renderBusinesses() {
|
||
const container = document.getElementById('businessList');
|
||
container.innerHTML = '';
|
||
|
||
businesses.forEach(biz => {
|
||
const owned = myState.businesses[biz.id];
|
||
const canAfford = myState.cash >= biz.cost;
|
||
|
||
const div = document.createElement('div');
|
||
div.className = `shop-item ${owned ? 'owned' : ''}`;
|
||
|
||
let html = `
|
||
<div class="item-info">
|
||
<div class="item-name">${biz.name}</div>
|
||
<div class="item-desc">
|
||
💰 Цена: $${biz.cost.toLocaleString()}<br>
|
||
📈 Доход: $${biz.income}/сек
|
||
${owned ? `<br>👥 Работников: ${myState.businesses[biz.id].workers} | ⚡ Эффективность: x${myState.businesses[biz.id].efficiency.toFixed(1)}` : ''}
|
||
</div>
|
||
</div>
|
||
<div class="item-price">$${biz.cost.toLocaleString()}</div>
|
||
`;
|
||
|
||
if (owned) {
|
||
html += `<button class="btn btn-action" onclick="manageBusiness('${biz.id}')">⚙️ Управление</button>`;
|
||
} else {
|
||
html += `<button class="btn btn-buy" onclick="buyBusiness('${biz.id}')" ${!canAfford ? 'disabled' : ''}>🛒 Купить</button>`;
|
||
}
|
||
|
||
div.innerHTML = html;
|
||
container.appendChild(div);
|
||
});
|
||
}
|
||
|
||
function buyBusiness(id) {
|
||
const biz = businesses.find(b => b.id === id);
|
||
|
||
if (myState.cash < biz.cost) {
|
||
log(`❌ Недостаточно денег! Есть $${Math.floor(myState.cash)}, нужно $${biz.cost}`, 'error');
|
||
return;
|
||
}
|
||
|
||
myState.cash -= biz.cost;
|
||
myState.businesses[id] = { workers: 1, efficiency: 1 };
|
||
|
||
log(`✅ Куплен бизнес: ${biz.name}`, 'success');
|
||
saveState();
|
||
renderBusinesses();
|
||
updateUI();
|
||
}
|
||
|
||
function manageBusiness(id) {
|
||
const biz = businesses.find(b => b.id === id);
|
||
const data = myState.businesses[id];
|
||
const hireCost = Math.floor(biz.cost / 10);
|
||
const upgradeCost = Math.floor(biz.cost / 5);
|
||
const currentIncome = biz.income * data.workers * data.efficiency;
|
||
|
||
const action = prompt(
|
||
`📊 ${biz.name}\n\n` +
|
||
`📈 Текущий доход: $${currentIncome.toFixed(0)}/сек\n` +
|
||
`👥 Работников: ${data.workers}\n` +
|
||
`⚡ Эффективность: x${data.efficiency.toFixed(1)}\n\n` +
|
||
`Выберите действие:\n` +
|
||
`1️⃣ Нанять работника ($${hireCost.toLocaleString()})\n` +
|
||
`2️⃣ Уволить работника\n` +
|
||
`3️⃣ Улучшить эффективность x1.5 ($${upgradeCost.toLocaleString()})`
|
||
);
|
||
|
||
if (action === '1') {
|
||
if (myState.cash < hireCost) {
|
||
log(`❌ Недостаточно денег для найма ($${hireCost})`, 'error');
|
||
return;
|
||
}
|
||
myState.cash -= hireCost;
|
||
data.workers++;
|
||
log(`✅ Нанят работник в ${biz.name}`, 'success');
|
||
} else if (action === '2') {
|
||
if (data.workers > 1) {
|
||
data.workers--;
|
||
log(`👋 Работник уволен из ${biz.name}`, 'info');
|
||
} else {
|
||
log('❌ Нельзя уволить последнего работника', 'error');
|
||
}
|
||
} else if (action === '3') {
|
||
if (myState.cash < upgradeCost) {
|
||
log(`❌ Недостаточно денег для улучшения ($${upgradeCost})`, 'error');
|
||
return;
|
||
}
|
||
myState.cash -= upgradeCost;
|
||
data.efficiency *= 1.5;
|
||
log(`✅ Эффективность ${biz.name} улучшена до x${data.efficiency.toFixed(1)}`, 'success');
|
||
}
|
||
|
||
saveState();
|
||
renderBusinesses();
|
||
updateUI();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// РОСКОШЬ
|
||
// ═══════════════════════════════════════════════════════════
|
||
function renderLuxury() {
|
||
const container = document.getElementById('luxuryList');
|
||
container.innerHTML = '';
|
||
|
||
luxuryItems.forEach(item => {
|
||
const owned = myState.luxury[item.id];
|
||
const canAfford = myState.cash >= item.cost;
|
||
|
||
const div = document.createElement('div');
|
||
div.className = `shop-item ${owned ? 'owned' : ''}`;
|
||
|
||
let html = `
|
||
<div class="item-info">
|
||
<div class="item-name">${item.name}</div>
|
||
<div class="item-desc">${item.desc}</div>
|
||
</div>
|
||
<div class="item-price">$${item.cost.toLocaleString()}</div>
|
||
`;
|
||
|
||
if (owned) {
|
||
html += '<button class="btn btn-action" disabled>✅ Куплено</button>';
|
||
} else {
|
||
html += `<button class="btn btn-buy" onclick="buyLuxury('${item.id}')" ${!canAfford ? 'disabled' : ''}>🛒 Купить</button>`;
|
||
}
|
||
|
||
div.innerHTML = html;
|
||
container.appendChild(div);
|
||
});
|
||
}
|
||
|
||
function buyLuxury(id) {
|
||
const item = luxuryItems.find(i => i.id === id);
|
||
|
||
if (myState.luxury[id]) {
|
||
log('❌ У вас уже есть этот предмет', 'error');
|
||
return;
|
||
}
|
||
|
||
if (myState.cash < item.cost) {
|
||
log(`❌ Недостаточно денег! Есть $${Math.floor(myState.cash)}, нужно $${item.cost}`, 'error');
|
||
return;
|
||
}
|
||
|
||
myState.cash -= item.cost;
|
||
myState.luxury[id] = true;
|
||
|
||
log(`✅ Куплено: ${item.name}`, 'success');
|
||
saveState();
|
||
renderLuxury();
|
||
updateUI();
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// ИГРОВОЙ ЦИКЛ
|
||
// ═══════════════════════════════════════════════════════════
|
||
function tick() {
|
||
let totalIncome = 0;
|
||
for (let id in myState.businesses) {
|
||
const biz = businesses.find(b => b.id === id);
|
||
const data = myState.businesses[id];
|
||
const income = biz.income * data.workers * data.efficiency;
|
||
totalIncome += income;
|
||
}
|
||
|
||
if (totalIncome > 0) {
|
||
myState.cash += totalIncome;
|
||
}
|
||
|
||
if (myState.house.stage < houseStages.length && myState.house.progress > 0) {
|
||
const stage = houseStages[myState.house.stage];
|
||
myState.house.progress += (100 / stage.time);
|
||
if (myState.house.progress >= 100) {
|
||
myState.house.stage++;
|
||
myState.house.progress = 0;
|
||
log(`✅ ${stage.name} завершён!`, 'success');
|
||
renderHouse();
|
||
}
|
||
}
|
||
|
||
updateUI();
|
||
}
|
||
|
||
function updateUI() {
|
||
document.getElementById('cash').textContent = '$' + Math.floor(myState.cash).toLocaleString();
|
||
document.getElementById('cashForHouse').textContent = Math.floor(myState.cash).toLocaleString();
|
||
document.getElementById('cashForBusiness').textContent = Math.floor(myState.cash).toLocaleString();
|
||
document.getElementById('cashForLuxury').textContent = Math.floor(myState.cash).toLocaleString();
|
||
document.getElementById('myCash1').textContent = Math.floor(myState.cash).toLocaleString();
|
||
document.getElementById('myCash2').textContent = Math.floor(myState.cash).toLocaleString();
|
||
|
||
let totalIncome = 0;
|
||
for (let id in myState.businesses) {
|
||
const biz = businesses.find(b => b.id === id);
|
||
const data = myState.businesses[id];
|
||
totalIncome += biz.income * data.workers * data.efficiency;
|
||
}
|
||
document.getElementById('businessIncome').textContent = '$' + Math.floor(totalIncome).toLocaleString() + '/сек';
|
||
|
||
const housePercent = Math.floor((myState.house.stage / houseStages.length) * 100);
|
||
document.getElementById('houseLevel').textContent = housePercent + '%';
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// ВКЛАДКИ
|
||
// ═══════════════════════════════════════════════════════════
|
||
document.querySelectorAll('.tab').forEach(tab => {
|
||
tab.onclick = () => {
|
||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||
tab.classList.add('active');
|
||
document.getElementById(tab.dataset.tab).classList.add('active');
|
||
if (tab.dataset.tab === 'transfer') refreshAvailable();
|
||
};
|
||
});
|
||
|
||
// Запуск игры
|
||
updateUI();
|
||
</script>
|
||
</body>
|
||
</html> |