Загрузка данных
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bank Tycoon: Волк с Уолл-Стрит</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;800&display=swap');
:root {
--bg-color: #0f172a;
--panel-bg: #1e293b;
--text-color: #f8fafc;
--text-muted: #94a3b8;
--primary: #3b82f6;
--danger: #ef4444;
--success: #10b981;
--warning: #f59e0b;
}
* { box-sizing: border-box; margin: 0; padding: 0; user-select: none; }
body {
font-family: 'Montserrat', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* ЭКРАН ВЫБОРА */
#setup-screen {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
text-align: center;
background: radial-gradient(circle at center, #1e293b 0%, #0f172a 100%);
}
.bank-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
margin-top: 40px;
}
.bank-card {
background: rgba(30, 41, 59, 0.8);
padding: 30px;
border-radius: 16px;
cursor: pointer;
transition: all 0.2s;
border: 2px solid transparent;
backdrop-filter: blur(10px);
}
.bank-card:hover { transform: translateY(-5px); }
.bank-sber { border-color: #10b981; }
.bank-tbank { border-color: #eab308; }
.bank-alfa { border-color: #ef4444; }
.bank-vtb { border-color: #3b82f6; }
/* ИГРОВОЙ ЭКРАН */
#game-screen {
display: none;
flex-direction: column;
height: 100vh;
}
/* ШАПКА */
header {
background: var(--panel-bg);
padding: 15px 40px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid var(--primary);
z-index: 10;
}
.stats { text-align: right; }
.balance { font-size: 2.2rem; font-weight: 800; color: var(--primary); }
.income { font-size: 1.1rem; color: var(--success); font-weight: 600; }
/* НОВОСТНАЯ ЛЕНТА */
.news-ticker {
background: #020617;
padding: 10px 20px;
display: flex;
align-items: center;
border-bottom: 1px solid #334155;
position: relative;
}
.news-label {
background: var(--danger);
color: white;
padding: 5px 10px;
font-weight: 800;
border-radius: 4px;
margin-right: 15px;
text-transform: uppercase;
font-size: 0.8rem;
z-index: 2;
}
.news-text {
font-size: 1rem;
color: var(--warning);
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.progress-bar-container {
position: absolute;
bottom: 0; left: 0; width: 100%; height: 3px;
background: #334155;
}
.progress-bar {
height: 100%;
background: var(--warning);
width: 0%;
transition: width 0.1s linear;
}
/* ОСНОВНАЯ ЗОНА */
main {
display: flex;
flex: 1;
overflow: hidden;
}
/* ПАНЕЛИ */
.panel {
padding: 20px;
overflow-y: auto;
border-right: 1px solid #334155;
}
/* КЛИКЕР (Лево) */
.clicker-panel {
flex: 0.8;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: var(--bg-color);
}
.click-btn {
background: var(--primary);
color: white;
border: none;
width: 220px;
height: 220px;
border-radius: 50%;
font-size: 1.5rem;
font-weight: 800;
cursor: pointer;
box-shadow: 0 10px 30px rgba(0,0,0,0.5), inset 0 -5px 15px rgba(0,0,0,0.2);
transition: transform 0.1s;
}
.click-btn:active { transform: scale(0.95); }
/* СТАБИЛЬНЫЕ ИНВЕСТИЦИИ (Центр) */
.investments-panel {
flex: 1;
background: #0b1120;
}
.panel-title { font-size: 1.4rem; margin-bottom: 15px; font-weight: 800; }
.inv-card {
background: var(--panel-bg);
border-radius: 10px;
padding: 15px;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
border-left: 4px solid #334155;
}
.inv-card.affordable { border-left-color: var(--primary); }
.inv-info h3 { font-size: 1.1rem; }
.inv-info p { font-size: 0.8rem; color: var(--text-muted); margin-top: 3px; }
.buy-btn {
background: transparent;
color: var(--text-color);
border: 1px solid #334155;
padding: 8px 12px;
border-radius: 6px;
cursor: not-allowed;
font-weight: 600;
}
.inv-card.affordable .buy-btn {
border-color: var(--primary); color: var(--primary); cursor: pointer;
}
.inv-card.affordable .buy-btn:hover { background: var(--primary); color: white; }
/* БИРЖА (Право) */
.stock-panel {
flex: 1.5;
background: #0f172a;
}
.stock-card {
background: var(--panel-bg);
border-radius: 10px;
padding: 15px;
margin-bottom: 12px;
border: 1px solid #334155;
display: grid;
grid-template-columns: 2fr 1fr 1fr;
align-items: center;
gap: 10px;
transition: border-color 0.3s;
}
.stock-card.up { border-left: 4px solid var(--success); }
.stock-card.down { border-left: 4px solid var(--danger); }
.stock-stats { text-align: right; font-size: 0.9rem; }
.stock-price { font-weight: 800; font-size: 1.1rem; }
.stock-div { color: var(--success); }
.stock-actions {
display: flex;
gap: 5px;
justify-content: flex-end;
}
.btn-trade {
padding: 8px 15px;
border-radius: 6px;
border: none;
font-weight: 600;
cursor: pointer;
color: white;
}
.btn-buy { background: var(--success); }
.btn-buy:hover { background: #059669; }
.btn-buy:disabled { background: #334155; color: #94a3b8; cursor: not-allowed; }
.btn-sell { background: var(--danger); }
.btn-sell:hover { background: #dc2626; }
.btn-sell:disabled { background: #334155; color: #94a3b8; cursor: not-allowed; }
.owned-badge {
display: inline-block;
background: #334155;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.8rem;
margin-left: 5px;
}
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
</style>
</head>
<body>
<div id="setup-screen">
<h1>Создайте свой Банк</h1>
<div class="bank-grid">
<div class="bank-card bank-sber" onclick="startGame('Сбер', '#10b981')">
<h2 style="color: #10b981;">Сбер</h2>
</div>
<div class="bank-card bank-tbank" onclick="startGame('Т-Банк', '#eab308')">
<h2 style="color: #eab308;">Т-Банк</h2>
</div>
<div class="bank-card bank-alfa" onclick="startGame('Альфа', '#ef4444')">
<h2 style="color: #ef4444;">Альфа</h2>
</div>
<div class="bank-card bank-vtb" onclick="startGame('ВТБ', '#3b82f6')">
<h2 style="color: #3b82f6;">ВТБ</h2>
</div>
</div>
</div>
<div id="game-screen">
<header>
<div>
<h2 id="ui-bank-name">Банк</h2>
<p style="color: var(--text-muted); font-size: 0.8rem;">Пассивный доход: <span id="ui-total-income">0</span> ₽/сек</p>
</div>
<div class="stats">
<div class="balance"><span id="ui-balance">0</span> ₽</div>
</div>
</header>
<div class="news-ticker">
<div class="news-label">СЛУХИ</div>
<div class="news-text" id="ui-news-text">Ожидание первых сводок с биржи...</div>
<div class="progress-bar-container">
<div class="progress-bar" id="ui-news-progress"></div>
</div>
</div>
<main>
<!-- КЛИКЕР -->
<div class="panel clicker-panel">
<button class="click-btn" onclick="manualClick()">Оформить<br>сделку</button>
<p style="margin-top: 20px; color: var(--text-muted);">Сила клика: <span id="ui-click-power">1</span> ₽</p>
</div>
<!-- БАЗОВЫЕ ИНВЕСТИЦИИ -->
<div class="panel investments-panel">
<div class="panel-title">Надежные активы</div>
<div id="passive-container"></div>
</div>
<!-- БИРЖА -->
<div class="panel stock-panel">
<div class="panel-title">Вклады и Акции (Биржа)</div>
<p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 15px;">
Доходность и цены меняются в зависимости от новостей. Покупайте дешево, продавайте дорого!
</p>
<div id="stocks-container"></div>
</div>
</main>
</div>
<script>
// --- СОСТОЯНИЕ ИГРЫ ---
const game = {
balance: 0,
passiveIncome: 0,
stockIncome: 0,
clickPower: 1,
lastTick: Date.now()
};
// --- БАЗОВЫЕ (СТАБИЛЬНЫЕ) ИНВЕСТИЦИИ ---
const passives = [
{ id: 0, name: "Банкоматы", baseCost: 50, baseInc: 2, count: 0 },
{ id: 1, name: "Кредиты физлицам", baseCost: 300, baseInc: 10, count: 0 },
{ id: 2, name: "Ипотека", baseCost: 2500, baseInc: 60, count: 0 },
{ id: 3, name: "Гособлигации", baseCost: 15000, baseInc: 250, count: 0 },
];
// --- БИРЖЕВЫЕ КОМПАНИИ (ДИНАМИКА) ---
const stocks = [
{ id: 'oil', name: 'ГазНефть Пром', basePrice: 500, currentPrice: 500, baseDiv: 15, currentDiv: 15, owned: 0, status: 'normal',
hintPos: "Аналитики ожидают скачка цен на энергоносители. ГазНефть готовится к взлету!",
hintNeg: "Слухи об экологических санкциях пугают инвесторов нефтегазового сектора..." },
{ id: 'palma', name: 'Сеть доставок "Пальма"', basePrice: 1200, currentPrice: 1200, baseDiv: 40, currentDiv: 40, owned: 0, status: 'normal',
hintPos: "Известный фуд-блогер готовит обзор на доставку 'Пальма'. Ожидается бум заказов роллов!",
hintNeg: "Проблемы с поставками риса и морепродуктов. Ресторанный бизнес под угрозой." },
{ id: 'ural', name: 'Пермский МашЗавод', basePrice: 3500, currentPrice: 3500, baseDiv: 120, currentDiv: 120, owned: 0, status: 'normal',
hintPos: "Инсайдеры: Пермский завод может получить гигантский оборонный госзаказ.",
hintNeg: "Нехватка квалифицированных кадров может остановить конвейеры МашЗавода." },
{ id: 'it', name: 'IT-гигант "Сфера"', basePrice: 8000, currentPrice: 8000, baseDiv: 300, currentDiv: 300, owned: 0, status: 'normal',
hintPos: "В сеть утекли скриншоты новой нейросети от 'Сферы'. Акции готовы пробить потолок!",
hintNeg: "Угроза взлома серверов 'Сферы' заставляет инвесторов в панике сбрасывать активы." },
{ id: 'farm', name: 'ФармСинтез', basePrice: 15000, currentPrice: 15000, baseDiv: 600, currentDiv: 600, owned: 0, status: 'normal',
hintPos: "Слухи: новое лекарство от ФармСинтеза успешно прошло финальные тесты.",
hintNeg: "Минздрав может отозвать лицензию на главный препарат ФармСинтеза." },
{ id: 'build', name: 'СтройУрал Инвест', basePrice: 40000, currentPrice: 40000, baseDiv: 1500, currentDiv: 1500, owned: 0, status: 'normal',
hintPos: "Правительство обсуждает расширение льготной ипотеки. Застройщики в предвкушении.",
hintNeg: "Цены на бетон и арматуру бьют рекорды. Стройки могут заморозить." },
{ id: 'retail', name: 'МегаРитейл', basePrice: 100000, currentPrice: 100000, baseDiv: 4000, currentDiv: 4000, owned: 0, status: 'normal',
hintPos: "Ожидаются рекордные траты населения в праздничные дни. Ритейл озолотится!",
hintNeg: "Снижение покупательской способности сильно ударит по выручке супермаркетов." }
];
// --- СИСТЕМА НОВОСТЕЙ ---
const NEWS_CYCLE_TIME = 30; // 30 секунд
let newsTimer = 0;
let pendingNews = { targetStockIndex: -1, isPositive: true };
function formatNum(num) {
return Math.floor(num).toLocaleString('ru-RU');
}
function startGame(name, color) {
document.documentElement.style.setProperty('--primary', color);
document.getElementById('setup-screen').style.display = 'none';
document.getElementById('game-screen').style.display = 'flex';
document.getElementById('ui-bank-name').innerText = name;
generateNewHint(); // Создаем первый инсайд
renderPassives();
renderStocks();
// Основной игровой цикл
setInterval(gameLoop, 33);
setInterval(updateUIButtons, 200);
}
function manualClick() {
game.balance += game.clickPower;
updateHeader();
}
// --- ЛОГИКА БАЗОВЫХ ИНВЕСТИЦИЙ ---
function getPassiveCost(inv) {
return Math.floor(inv.baseCost * Math.pow(1.15, inv.count));
}
function buyPassive(id) {
const inv = passives[id];
const cost = getPassiveCost(inv);
if (game.balance >= cost) {
game.balance -= cost;
inv.count++;
recalculateIncome();
renderPassives();
}
}
// --- ЛОГИКА БИРЖИ ---
function buyStock(index) {
const s = stocks[index];
if (game.balance >= s.currentPrice) {
game.balance -= s.currentPrice;
s.owned++;
s.currentPrice *= 1.02; // Каждая покупка чуть-чуть повышает цену
recalculateIncome();
renderStocks();
}
}
function sellStock(index) {
const s = stocks[index];
if (s.owned > 0) {
s.owned--;
game.balance += s.currentPrice;
s.currentPrice *= 0.98; // Каждая продажа чуть роняет цену
recalculateIncome();
renderStocks();
}
}
// --- НОВОСТИ И ЭКОНОМИКА ---
function generateNewHint() {
// Выбираем случайную компанию
const targetIdx = Math.floor(Math.random() * stocks.length);
const isPositive = Math.random() > 0.4; // 60% шанс хороших новостей
pendingNews = { targetStockIndex: targetIdx, isPositive: isPositive };
const hintText = isPositive ? stocks[targetIdx].hintPos : stocks[targetIdx].hintNeg;
document.getElementById('ui-news-text').innerText = hintText;
// Визуально подсвечиваем слух
document.getElementById('ui-news-text').style.color = isPositive ? 'var(--success)' : 'var(--danger)';
}
function applyNewsEffects() {
// 1. Постепенно возвращаем все акции к базовым значениям (рынок успокаивается)
stocks.forEach(s => {
s.currentPrice += (s.basePrice - s.currentPrice) * 0.2;
s.currentDiv += (s.baseDiv - s.currentDiv) * 0.2;
s.status = 'normal';
});
// 2. Применяем эффект от сбывшегося инсайда
if (pendingNews.targetStockIndex !== -1) {
const target = stocks[pendingNews.targetStockIndex];
if (pendingNews.isPositive) {
// Взлет на 50% - 120%
const mult = 1.5 + Math.random() * 0.7;
target.currentPrice *= mult;
target.currentDiv *= mult;
target.status = 'up';
} else {
// Обвал на 40% - 70%
const mult = 0.3 + Math.random() * 0.3;
target.currentPrice *= mult;
target.currentDiv *= mult;
target.status = 'down';
}
}
recalculateIncome();
renderStocks();
}
// --- ОБЩИЕ РАСЧЕТЫ ---
function recalculateIncome() {
game.passiveIncome = passives.reduce((sum, inv) => sum + (inv.count * inv.baseInc), 0);
game.stockIncome = stocks.reduce((sum, s) => sum + (s.owned * s.currentDiv), 0);
const total = game.passiveIncome + game.stockIncome;
game.clickPower = 1 + Math.floor(total * 0.02); // Клик = 2% от пассива
}
function gameLoop() {
const now = Date.now();
const dt = (now - game.lastTick) / 1000;
game.lastTick = now;
// Начисление денег
const totalIncome = game.passiveIncome + game.stockIncome;
if (totalIncome > 0) {
game.balance += totalIncome * dt;
}
// Таймер новостей
newsTimer += dt;
const progress = (newsTimer / NEWS_CYCLE_TIME) * 100;
document.getElementById('ui-news-progress').style.width = `${progress}%`;
if (newsTimer >= NEWS_CYCLE_TIME) {
newsTimer = 0;
applyNewsEffects(); // Применяем то, что обещали
generateNewHint(); // Генерируем новый слух
}
updateHeader();
}
// --- ОТРИСОВКА ИНТЕРФЕЙСА ---
function updateHeader() {
document.getElementById('ui-balance').innerText = formatNum(game.balance);
document.getElementById('ui-total-income').innerText = formatNum(game.passiveIncome + game.stockIncome);
document.getElementById('ui-click-power').innerText = formatNum(game.clickPower);
}
function updateUIButtons() {
// Обновляем кнопки пассива
passives.forEach(inv => {
const card = document.getElementById(`inv-${inv.id}`);
const btn = document.getElementById(`btn-inv-${inv.id}`);
if(!card || !btn) re