Revert "UI redesign: CS2 gambling aesthetic, glassmorphism, responsive layout, enhanced sounds, admin panel overhaul"

This reverts commit 4b0fc594b4.
This commit is contained in:
root
2026-07-05 14:42:58 +00:00
parent 4b0fc594b4
commit a34c9f07a7
21 changed files with 9157 additions and 2704 deletions
+425 -107
View File
@@ -3,76 +3,132 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Crash CS2 Simulator</title>
<title>Crash - CS2 Simulator</title>
<link rel="stylesheet" href="/static/css/style.css">
<style>
#crashCanvas { width:100%; height:350px; display:block; border-radius:8px; }
.crash-multiplier { font-size:4rem; font-weight:800; text-shadow:0 0 40px rgba(34,197,94,0.5); transition:all 0.1s; }
.crash-multiplier.crashed { color:#ef4444; text-shadow:0 0 40px rgba(239,68,68,0.5); animation:shake 0.5s ease; }
.crash-multiplier.waiting { font-size:2rem; color:var(--text-dim); text-shadow:none; }
.crash-multiplier.betting { font-size:3rem; color:var(--primary); text-shadow:0 0 30px rgba(59,130,246,0.3); }
.quick-bet-btn { padding:0.4rem; background:var(--bg-dark); border:1px solid var(--border); border-radius:var(--radius-sm); color:var(--text); cursor:pointer; font-size:0.78rem; transition:all 0.2s; }
.quick-bet-btn:hover { border-color:var(--primary); }
.cashout-slider { width:100%; height:6px; background:var(--bg-dark); border-radius:3px; overflow:hidden; }
.cashout-slider-fill { height:100%; background:linear-gradient(90deg,var(--success),#f59e0b,#ef4444); border-radius:3px; transition:width 0.1s; }
.crash-layout { max-width: 960px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem; }
.crash-canvas-container {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1rem;
position: relative;
overflow: hidden;
}
#crashCanvas { width: 100%; height: 350px; display: block; border-radius: 8px; }
.crash-overlay {
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center;
pointer-events: none;
}
.crash-multiplier { font-size: 4rem; font-weight: 800; text-shadow: 0 0 40px rgba(34,197,94,0.5); transition: all 0.1s; }
.crash-multiplier.crashed { color: #ef4444; text-shadow: 0 0 40px rgba(239,68,68,0.5); animation: crashShake 0.5s ease; }
.crash-multiplier.waiting { font-size: 2rem; color: var(--text-secondary); text-shadow: none; }
.crash-multiplier.betting { font-size: 3rem; color: var(--primary-color); text-shadow: 0 0 30px rgba(59,130,246,0.3); }
@keyframes crashShake {
0%,100%{transform:translateX(0)}10%{transform:translateX(-10px)rotate(-2deg)}30%{transform:translateX(10px)rotate(2deg)}50%{transform:translateX(-8px)rotate(-1deg)}70%{transform:translateX(8px)rotate(1deg)}90%{transform:translateX(-4px)}
}
.crash-status-text { font-size: 1rem; color: var(--text-secondary); margin-top: 0.5rem; }
.crash-controls { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.crash-bet-panel { background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 12px; padding: 1.5rem; }
.crash-players-panel { background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 12px; padding: 1.5rem; max-height: 300px; overflow-y: auto; }
.crash-bet-input { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.crash-bet-input input { flex: 1; padding: 0.75rem; background: var(--bg-dark); border: 1px solid var(--border-color); border-radius: 8px; color: var(--text-color); font-size: 1.1rem; }
.quick-bet-btns { display: grid; grid-template-columns: repeat(4,1fr); gap: 0.5rem; margin-bottom: 1rem; }
.quick-bet-btn { padding: 0.5rem; background: var(--bg-dark); border: 1px solid var(--border-color); border-radius: 6px; color: var(--text-color); cursor: pointer; font-size: 0.85rem; transition: all 0.2s; }
.quick-bet-btn:hover { border-color: var(--primary-color); }
.crash-btn { width: 100%; padding: 0.75rem; border: none; border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.2s; }
.crash-btn.bet { background: var(--primary-color); color: white; }
.crash-btn.bet:hover { filter: brightness(1.1); }
.crash-btn.bet.placed { background: #6366f1; }
.crash-btn.cashout { background: var(--success-color); color: white; }
.crash-btn.cashout:hover { filter: brightness(1.1); }
.crash-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.crash-btn.hidden { display: none; }
.player-row { display: flex; justify-content: space-between; align-items: center; padding: 0.4rem 0; border-bottom: 1px solid var(--border-color); font-size: 0.85rem; }
.player-row:last-child { border-bottom: none; }
.player-row .payout { color: var(--success-color); font-weight: 600; }
.player-row .bet-amount { color: var(--text-secondary); }
.crash-history { display: flex; gap: 0.4rem; flex-wrap: wrap; padding: 0.5rem 0; }
.crash-history-item { padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.crash-history-item.win { background: rgba(34,197,94,0.15); color: var(--success-color); }
.crash-history-item.loss { background: rgba(239,68,68,0.15); color: #ef4444; }
.cashout-progress { margin-top: 0.75rem; display: none; }
.cashout-progress.active { display: block; }
.cashout-slider { width: 100%; height: 6px; background: var(--bg-dark); border-radius: 3px; position: relative; overflow: hidden; }
.cashout-slider-fill { height: 100%; background: linear-gradient(90deg, var(--success-color), #f59e0b, #ef4444); border-radius: 3px; transition: width 0.1s; }
.cashout-label { display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--text-secondary); margin-top: 0.25rem; }
.crash-my-bet { margin-top: 0.5rem; padding: 0.5rem; background: var(--bg-dark); border-radius: 6px; text-align: center; font-size: 0.85rem; }
.crash-my-bet .bet-highlight { color: var(--primary-color); font-weight: 600; }
.crash-my-bet .win-highlight { color: var(--success-color); font-weight: 600; }
.crash-my-bet .lose-highlight { color: #ef4444; font-weight: 600; }
</style>
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">CS2 <span>Simulator</span></a>
<button class="mobile-toggle" id="mobileToggle"></button>
<div class="nav-links" id="navLinks">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">Апгрейд</a>
<a href="/crash" class="nav-link active">Crash</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/crash" class="nav-link active">💥 Crash</a>
<a href="/inventory" class="nav-link">Инвентарь</a>
<a href="/achievements" class="nav-link">Достижения</a>
<a href="/activity" class="nav-link">Лента</a>
<a href="/achievements" class="nav-link">🏆 Достижения</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<a href="/profile" class="nav-link">Профиль</a>
<span class="user-balance">{{ "%.0f"|format(user.balance) }} ₽</span>
<button onclick="toggleSafeMode()" class="btn btn-ghost" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-ghost">Выйти</button>
<button onclick="toggleSound()" class="btn btn-outline" id="soundToggle">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
</div>
</div>
</nav>
<main class="section">
<div class="container" style="max-width:960px;">
<div class="card" style="position:relative;overflow:hidden;">
<main class="container" style="padding: 2rem 1rem;">
<div class="crash-layout">
<div class="crash-canvas-container">
<canvas id="crashCanvas"></canvas>
<div style="position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;">
<div class="crash-overlay">
<div class="crash-multiplier waiting" id="crashMultiplier">Ожидание...</div>
<div class="text-dim text-sm" style="margin-top:0.5rem;" id="crashStatus">Следующий раунд скоро начнётся</div>
<div class="crash-status-text" id="crashStatus">Следующий раунд скоро начнётся</div>
</div>
</div>
<div class="flex gap-2" style="flex-wrap:wrap;padding:0.5rem 0;" id="crashHistory"></div>
<div class="crash-history" id="crashHistory"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.75rem;">
<div class="card">
<h3 class="display-font" style="font-size:0.85rem;margin-bottom:0.75rem;">💰 Сделать ставку</h3>
<div class="flex gap-2" style="margin-bottom:0.75rem;">
<input type="number" id="betAmount" value="100" min="1" step="1" class="form-input" style="flex:1;">
<div class="crash-controls">
<div class="crash-bet-panel">
<h3>💰 Сделать ставку</h3>
<div class="crash-bet-input">
<input type="number" id="betAmount" value="100" min="1" step="1">
</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0.35rem;margin-bottom:0.75rem;">
<div class="quick-bet-btns">
<button class="quick-bet-btn" onclick="setBet(50)">50</button>
<button class="quick-bet-btn" onclick="setBet(100)">100</button>
<button class="quick-bet-btn" onclick="setBet(500)">500</button>
<button class="quick-bet-btn" onclick="setBet(1000)">1K</button>
</div>
<button class="btn btn-primary btn-block" id="betBtn" onclick="placeBet()">💰 Сделать ставку</button>
<button class="btn btn-success btn-block" id="cashoutBtn" onclick="cashOut()" style="display:none;">💰 Забрать</button>
<div id="myBetDisplay" style="display:none;margin-top:0.5rem;padding:0.4rem;background:var(--bg-dark);border-radius:var(--radius-sm);text-align:center;font-size:0.85rem;"></div>
<div class="cashout-progress" id="cashoutProgress" style="display:none;margin-top:0.5rem;">
<div class="cashout-slider"><div class="cashout-slider-fill" id="cashoutSliderFill" style="width:0%"></div></div>
<div class="flex justify-between text-xs text-dim" style="margin-top:0.2rem;"><span>1.00x</span><span id="cashoutMultLabel">1.00x</span></div>
<button class="crash-btn bet" id="betBtn" onclick="placeBet()">Сделать ставку</button>
<button class="crash-btn cashout hidden" id="cashoutBtn" onclick="cashOut()">💰 Забрать</button>
<div id="myBetDisplay" class="crash-my-bet" style="display:none;"></div>
<div class="cashout-progress" id="cashoutProgress">
<div class="cashout-slider">
<div class="cashout-slider-fill" id="cashoutSliderFill" style="width:0%"></div>
</div>
<div class="cashout-label">
<span>1.00x</span>
<span id="cashoutMultLabel">1.00x</span>
</div>
</div>
</div>
<div class="card">
<h3 class="display-font" style="font-size:0.85rem;margin-bottom:0.75rem;">👥 Ставки</h3>
<div id="playersList"><div class="text-dim text-sm text-center" style="padding:1rem;">Ожидание ставок...</div></div>
<div class="crash-players-panel">
<h3>👥 Ставки</h3>
<div id="playersList">
<div style="color: var(--text-secondary); text-align: center; padding: 1rem;">
Ожидание ставок...
</div>
</div>
</div>
</div>
</div>
@@ -82,118 +138,380 @@
<script src="/static/js/websocket.js"></script>
<script src="/static/js/safemode.js"></script>
<script>
const canvas = document.getElementById('crashCanvas'), ctx = canvas.getContext('2d');
const multiplierEl = document.getElementById('crashMultiplier'), statusEl = document.getElementById('crashStatus');
const betBtn = document.getElementById('betBtn'), cashoutBtn = document.getElementById('cashoutBtn');
const playersList = document.getElementById('playersList'), historyEl = document.getElementById('crashHistory');
const betInput = document.getElementById('betAmount'), myBetDisplay = document.getElementById('myBetDisplay');
const cashoutProgress = document.getElementById('cashoutProgress'), cashoutSliderFill = document.getElementById('cashoutSliderFill'), cashoutMultLabel = document.getElementById('cashoutMultLabel');
let gameState = null, myActiveBet = null, myCashoutMult = null, lastPhase = null;
const canvas = document.getElementById('crashCanvas');
const ctx = canvas.getContext('2d');
const multiplierEl = document.getElementById('crashMultiplier');
const statusEl = document.getElementById('crashStatus');
const betBtn = document.getElementById('betBtn');
const cashoutBtn = document.getElementById('cashoutBtn');
const playersList = document.getElementById('playersList');
const historyEl = document.getElementById('crashHistory');
const betInput = document.getElementById('betAmount');
const myBetDisplay = document.getElementById('myBetDisplay');
const cashoutProgress = document.getElementById('cashoutProgress');
const cashoutSliderFill = document.getElementById('cashoutSliderFill');
const cashoutMultLabel = document.getElementById('cashoutMultLabel');
function resizeCanvas() { const r = canvas.parentElement.getBoundingClientRect(); canvas.width = canvas.clientWidth || r.width-32; canvas.height = canvas.clientHeight || 350; }
resizeCanvas(); window.addEventListener('resize', resizeCanvas);
function setBet(a) { betInput.value = a; SoundManager.click(); }
function expMult(t) { return Math.exp(t*0.08); }
function timeForMult(m) { return Math.log(m)/0.08; }
let gameState = null;
let myActiveBet = null; // текущая ставка в этом раунде
let myCashoutMult = null; // на каком множителе вывел (null = не вывел)
let lastPhase = null;
function resizeCanvas() {
const rect = canvas.parentElement.getBoundingClientRect();
canvas.width = canvas.clientWidth || rect.width - 32;
canvas.height = canvas.clientHeight || 350;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
function setBet(amount) { betInput.value = amount; SoundManager.click(); }
function expMult(t) { return Math.exp(t * 0.08); }
function timeForMult(m) { return Math.log(m) / 0.08; }
let graphPoints = [];
const GRAPH_WINDOW = 15;
const GRAPH_WINDOW = 15; // секунд в окне
function drawGraph() {
const w = canvas.width, h = canvas.height, pad = 40, drawW = w-pad*2, drawH = h-pad*2;
ctx.clearRect(0,0,w,h);
if (!gameState || gameState.phase === 'waiting') { ctx.fillStyle = 'rgba(255,255,255,0.08)'; ctx.font = '20px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('💥 Crash', w/2, h/2); return; }
const phase = gameState.phase, curMult = gameState.current_multiplier||1, crashPoint = gameState.crash_point||100, isCrashed = phase === 'crashed', isRunning = phase === 'running';
if (isRunning && curMult > 1) { const now = performance.now()/1000; graphPoints.push({t:now, m:curMult}); const cutoff = now - GRAPH_WINDOW; graphPoints = graphPoints.filter(p => p.t >= cutoff); if (graphPoints.length > 500) graphPoints = graphPoints.slice(-500); }
else if (isCrashed) graphPoints = [];
let maxY = 2; if (graphPoints.length > 0) maxY = Math.max(...graphPoints.map(p=>p.m)); maxY = Math.max(maxY, curMult+1, crashPoint, 3); maxY = Math.min(maxY, 200); maxY = Math.ceil(maxY);
ctx.strokeStyle = 'rgba(255,255,255,0.04)'; ctx.lineWidth = 1; ctx.font = '10px sans-serif'; ctx.textAlign = 'right';
for (let i = 1; i <= 5; i++) { const y = h-pad-(i/5)*drawH; ctx.beginPath(); ctx.moveTo(pad,y); ctx.lineTo(w-pad,y); ctx.stroke(); const multLabel = 1+(maxY-1)*(i/5); ctx.fillStyle = 'rgba(255,255,255,0.12)'; ctx.fillText(`x${multLabel.toFixed(1)}`, pad-6, y+3); }
if (graphPoints.length < 2) { if (isCrashed) { ctx.fillStyle = 'rgba(239,68,68,0.5)'; ctx.font = '36px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`💥 x${crashPoint.toFixed(2)}`, w/2, h/2); } return; }
const color = isCrashed ? '#ef4444' : '#22c55e'; const now = performance.now()/1000, windowStart = now - GRAPH_WINDOW;
ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = 3; ctx.shadowColor = color; ctx.shadowBlur = 10;
const w = canvas.width, h = canvas.height, pad = 40;
const drawW = w - pad * 2, drawH = h - pad * 2;
ctx.clearRect(0, 0, w, h);
if (!gameState || gameState.phase === 'waiting') {
ctx.fillStyle = 'rgba(255,255,255,0.08)';
ctx.font = '20px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('💥 Crash', w/2, h/2);
return;
}
const phase = gameState.phase;
const curMult = gameState.current_multiplier || 1;
const crashPoint = gameState.crash_point || 100;
const isCrashed = phase === 'crashed';
const isRunning = phase === 'running';
// Добавляем текущую точку в историю
if (isRunning && curMult > 1) {
const now = performance.now() / 1000;
graphPoints.push({ t: now, m: curMult });
// Отсекаем точки за пределами окна
const cutoff = now - GRAPH_WINDOW;
graphPoints = graphPoints.filter(p => p.t >= cutoff);
if (graphPoints.length > 500) graphPoints = graphPoints.slice(-500);
} else if (isCrashed) {
graphPoints = [];
}
// Определяем диапазон мультипликаторов для оси Y
let maxY = 2;
if (graphPoints.length > 0) {
maxY = Math.max(...graphPoints.map(p => p.m));
}
maxY = Math.max(maxY, curMult + 1, crashPoint, 3);
maxY = Math.min(maxY, 200); // не растягивать до небес
maxY = Math.ceil(maxY);
// Сетка
ctx.strokeStyle = 'rgba(255,255,255,0.04)';
ctx.lineWidth = 1;
const gridLines = 5;
ctx.font = '10px sans-serif';
ctx.textAlign = 'right';
for (let i = 1; i <= gridLines; i++) {
const y = h - pad - (i / gridLines) * drawH;
ctx.beginPath(); ctx.moveTo(pad, y); ctx.lineTo(w - pad, y); ctx.stroke();
const multLabel = 1 + (maxY - 1) * (i / gridLines);
ctx.fillStyle = 'rgba(255,255,255,0.12)';
ctx.fillText(`x${multLabel.toFixed(1)}`, pad - 6, y + 3);
}
// Рисуем кривую
if (graphPoints.length < 2) {
if (isCrashed) {
ctx.fillStyle = 'rgba(239,68,68,0.5)';
ctx.font = '36px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(`💥 x${crashPoint.toFixed(2)}`, w/2, h/2);
}
return;
}
const color = isCrashed ? '#ef4444' : '#22c55e';
const now = performance.now() / 1000;
const windowStart = now - GRAPH_WINDOW;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 3;
ctx.shadowColor = color;
ctx.shadowBlur = 10;
let first = true;
for (const p of graphPoints) { const x = pad+((p.t-windowStart)/GRAPH_WINDOW)*drawW, y = h-pad-((p.m-1)/(maxY-1))*drawH; if (first) { ctx.moveTo(x,y); first=false; } else ctx.lineTo(x,y); }
ctx.stroke(); ctx.shadowBlur = 0;
if (isRunning && curMult > 1) { const lastPt = graphPoints[graphPoints.length-1]; const x = pad+((lastPt.t-windowStart)/GRAPH_WINDOW)*drawW, y = h-pad-((lastPt.m-1)/(maxY-1))*drawH; ctx.beginPath(); ctx.arc(x,y,5,0,Math.PI*2); ctx.fillStyle = '#22c55e'; ctx.fill(); }
if (phase === 'betting') { ctx.fillStyle = 'rgba(255,255,255,0.06)'; ctx.font = '40px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`${gameState.timer}`, w/2, h/2+10); }
for (const p of graphPoints) {
const x = pad + ((p.t - windowStart) / GRAPH_WINDOW) * drawW;
const y = h - pad - ((p.m - 1) / (maxY - 1)) * drawH;
if (first) { ctx.moveTo(x, y); first = false; }
else ctx.lineTo(x, y);
}
ctx.stroke();
ctx.shadowBlur = 0;
// Текущая точка (справа, на последнем значении)
if (isRunning && curMult > 1) {
const lastPt = graphPoints[graphPoints.length - 1];
const x = pad + ((lastPt.t - windowStart) / GRAPH_WINDOW) * drawW;
const y = h - pad - ((lastPt.m - 1) / (maxY - 1)) * drawH;
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fillStyle = '#22c55e';
ctx.fill();
ctx.shadowColor = '#22c55e';
ctx.shadowBlur = 20;
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(34,197,94,0.4)';
ctx.lineWidth = 2;
ctx.stroke();
ctx.shadowBlur = 0;
}
// Беттинг фаза — таймер
if (phase === 'betting') {
ctx.fillStyle = 'rgba(255,255,255,0.06)';
ctx.font = '40px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(`${gameState.timer}`, w/2, h/2 + 10);
}
}
function updateUI() {
if (!gameState) return;
const phase = gameState.phase;
if (phase === 'waiting') { multiplierEl.className = 'crash-multiplier waiting'; multiplierEl.textContent = 'Ожидание...'; statusEl.textContent = 'Следующий раунд скоро начнётся'; }
else if (phase === 'betting') { multiplierEl.className = 'crash-multiplier betting'; multiplierEl.textContent = `${gameState.timer}`; statusEl.textContent = 'Делайте ставки!'; }
else if (phase === 'running') { const mult = gameState.current_multiplier; multiplierEl.className = 'crash-multiplier'; multiplierEl.textContent = `x${mult.toFixed(2)}`; statusEl.textContent = '🚀 Взлетаем!'; if (myActiveBet && myCashoutMult === null) { cashoutSliderFill.style.width = `${Math.min(100,((mult-1)/15)*100)}%`; cashoutMultLabel.textContent = `x${mult.toFixed(2)}`; } }
else if (phase === 'crashed') { multiplierEl.className = 'crash-multiplier crashed'; multiplierEl.textContent = `💥 x${gameState.crash_point.toFixed(2)}`; statusEl.textContent = 'Краш!'; }
const canBet = phase === 'betting', canCashout = phase === 'running' && myActiveBet !== null && myCashoutMult === null;
if (canBet) { betBtn.style.display = ''; betBtn.disabled = false; betBtn.textContent = myActiveBet ? `Ставка: ${myActiveBet}` : '💰 Сделать ставку'; betBtn.className = 'btn btn-primary btn-block'; if (myActiveBet) betBtn.className = 'btn btn-outline btn-block'; }
else betBtn.style.display = canCashout ? 'none' : '';
cashoutBtn.style.display = canCashout ? '' : 'none';
if (canCashout) cashoutBtn.textContent = `💰 Забрать (x${gameState.current_multiplier.toFixed(2)})`;
cashoutProgress.style.display = (phase === 'running' && myActiveBet !== null && myCashoutMult === null) ? 'block' : 'none';
// Обновляем множитель и статус
if (phase === 'waiting') {
multiplierEl.className = 'crash-multiplier waiting';
multiplierEl.textContent = 'Ожидание...';
statusEl.textContent = 'Следующий раунд скоро начнётся';
} else if (phase === 'betting') {
multiplierEl.className = 'crash-multiplier betting';
multiplierEl.textContent = `${gameState.timer}`;
statusEl.textContent = 'Делайте ставки!';
} else if (phase === 'running') {
const mult = gameState.current_multiplier;
multiplierEl.className = 'crash-multiplier';
multiplierEl.textContent = `x${mult.toFixed(2)}`;
statusEl.textContent = '🚀 Взлетаем!';
if (myActiveBet && myCashoutMult === null) {
const prog = Math.min(100, ((mult - 1) / 15) * 100);
cashoutSliderFill.style.width = `${prog}%`;
cashoutMultLabel.textContent = `x${mult.toFixed(2)}`;
}
} else if (phase === 'crashed') {
multiplierEl.className = 'crash-multiplier crashed';
multiplierEl.textContent = `💥 x${gameState.crash_point.toFixed(2)}`;
statusEl.textContent = 'Краш!';
}
// Кнопки
const canBet = phase === 'betting';
const canCashout = phase === 'running' && myActiveBet !== null && myCashoutMult === null;
betBtn.classList.toggle('hidden', !canBet && !(phase === 'betting' && myActiveBet));
if (canBet) {
betBtn.disabled = false;
betBtn.textContent = myActiveBet ? `Ставка: ${myActiveBet}` : '💰 Сделать ставку';
betBtn.className = 'crash-btn bet' + (myActiveBet ? ' placed' : '');
}
cashoutBtn.classList.toggle('hidden', !canCashout);
if (canCashout) {
cashoutBtn.disabled = false;
cashoutBtn.textContent = `💰 Забрать (x${gameState.current_multiplier.toFixed(2)})`;
}
cashoutProgress.classList.toggle('active', phase === 'running' && myActiveBet !== null && myCashoutMult === null);
// Статус моей ставки
if (myActiveBet !== null) {
myBetDisplay.style.display = 'block';
if (myCashoutMult !== null) myBetDisplay.innerHTML = `✅ Вывел(а) <span style="color:var(--success);font-weight:600;">${(myActiveBet*myCashoutMult).toFixed(0)} ₽</span> на x${myCashoutMult.toFixed(2)}`;
else if (phase === 'crashed') myBetDisplay.innerHTML = `❌ Проиграно <span style="color:#ef4444;font-weight:600;">${myActiveBet.toFixed(0)} ₽</span>`;
else if (phase === 'running') myBetDisplay.innerHTML = `Ставка: <span style="color:var(--primary);font-weight:600;">${myActiveBet.toFixed(0)} ₽</span> → ${(myActiveBet*gameState.current_multiplier).toFixed(0)}`;
else myBetDisplay.innerHTML = `Ставка: <span style="color:var(--primary);font-weight:600;">${myActiveBet.toFixed(0)} ₽</span>`;
} else myBetDisplay.style.display = 'none';
if (myCashoutMult !== null) {
const payout = myActiveBet * myCashoutMult;
myBetDisplay.style.display = 'block';
myBetDisplay.innerHTML = `
✅ Вывел(а) <span class="win-highlight">${payout.toFixed(0)} ₽</span>
на x${myCashoutMult.toFixed(2)}
`;
} else if (phase === 'crashed') {
myBetDisplay.style.display = 'block';
myBetDisplay.innerHTML = `
❌ Проиграно <span class="lose-highlight">${myActiveBet.toFixed(0)} ₽</span>
`;
} else if (phase === 'running') {
const potential = myActiveBet * gameState.current_multiplier;
myBetDisplay.style.display = 'block';
myBetDisplay.innerHTML = `
Ставка: <span class="bet-highlight">${myActiveBet.toFixed(0)} ₽</span>
${potential.toFixed(0)}
`;
} else {
myBetDisplay.style.display = 'block';
myBetDisplay.innerHTML = `
Ставка: <span class="bet-highlight">${myActiveBet.toFixed(0)} ₽</span>
`;
}
} else {
myBetDisplay.style.display = 'none';
}
// Звуки на смену фазы
if (lastPhase !== phase) {
if (phase === 'running') SoundManager.crashBet();
if (phase === 'crashed') { SoundManager.crashCrashed(); myActiveBet = null; myCashoutMult = null; }
if (phase === 'betting' && (lastPhase === 'crashed' || lastPhase === 'waiting')) { myActiveBet = null; myCashoutMult = null; }
if (phase === 'crashed') SoundManager.crashCrashed();
lastPhase = phase;
}
updatePlayers(); updateHistory(); drawGraph();
updatePlayers();
updateHistory();
drawGraph();
}
function updatePlayers() {
if (!gameState) return;
const bc = gameState.bet_count||0;
if (bc === 0) { playersList.innerHTML = '<div class="text-dim text-sm text-center" style="padding:1rem;">Пока нет ставок</div>'; return; }
const cashed = gameState.cashed_out||{};
let html = `<div class="flex justify-between text-xs" style="font-weight:600;padding:0.3rem 0;border-bottom:1px solid var(--border);"><span>Ставок: ${bc}</span><span>Сумма: ${(gameState.total_bets||0).toFixed(0)} ₽</span><span>Вывели: ${Object.keys(cashed).length}</span></div>`;
const bc = gameState.bet_count || 0;
if (bc === 0) {
playersList.innerHTML = '<div style="color:var(--text-secondary);text-align:center;padding:1rem;">Пока нет ставок</div>';
return;
}
const cashed = gameState.cashed_out || {};
const cashCount = Object.keys(cashed).length;
let html = `<div class="player-row" style="font-weight:600;border-bottom:2px solid var(--border-color);">
<span>Ставок: ${bc}</span>
<span>Сумма: ${(gameState.total_bets || 0).toFixed(0)} ₽</span>
<span>Вывели: ${cashCount}</span>
</div>`;
if (gameState.phase === 'running' || gameState.phase === 'crashed') {
const mult = gameState.current_multiplier || gameState.crash_point;
for (const [uid, m] of Object.entries(cashed)) {
const isMe = myActiveBet !== null && parseInt(uid) === 0;
html += `<div class="flex justify-between text-xs" style="padding:0.25rem 0;${isMe?'color:var(--success)':''}"><span>${isMe?'Вы':'Игрок #'+uid.slice(-4)}</span><span style="color:var(--success);font-weight:600;">✅ x${m.toFixed(2)}</span></div>`;
html += `<div class="player-row" style="${isMe ? 'color:var(--success-color)' : ''}">
<span>${isMe ? 'Вы' : 'Игрок #' + uid.slice(-4)}</span>
<span class="payout">✅ x${m.toFixed(2)}</span>
</div>`;
}
}
playersList.innerHTML = html;
}
function updateHistory() {
if (!gameState || !gameState.history) return;
historyEl.innerHTML = gameState.history.slice().reverse().map(m => `<span style="padding:0.15rem 0.45rem;border-radius:4px;font-size:0.7rem;font-weight:600;${m>2?'background:rgba(34,197,94,0.15);color:var(--success)':'background:rgba(239,68,68,0.15);color:#ef4444'}">x${m.toFixed(2)}</span>`).join('');
historyEl.innerHTML = gameState.history.slice().reverse().map(m => {
const isWin = m > 2;
return `<span class="crash-history-item ${isWin ? 'win' : 'loss'}">x${m.toFixed(2)}</span>`;
}).join('');
}
WS.on('crash_state', (data) => { if (!data.game) return; gameState = data.game; updateUI(); });
// WebSocket events
WS.on('crash_state', (data) => {
if (!data.game) return;
gameState = data.game;
// При краше сбрасываем мою ставку т.к. раунд закончился
if (gameState.phase === 'crashed' && lastPhase === 'running') {
// Если не вывел и краш — ставка проиграна, но стейт остается
}
if (gameState.phase === 'waiting' || gameState.phase === 'betting') {
// Новый раунд — если у меня была ставка, её уже нет (проиграна/выведена)
if (lastPhase === 'crashed' || lastPhase === 'running') {
if (myCashoutMult === null && myActiveBet !== null && gameState.phase === 'betting') {
// Ставка проиграна в прошлом раунде
}
}
if (gameState.phase === 'betting') {
// Если начался новый раунд betting и мы не обнулились —
// это новый раунд. Старая ставка аннулируется.
if (lastPhase === 'crashed' || lastPhase === 'waiting') {
myActiveBet = null;
myCashoutMult = null;
}
}
}
updateUI();
});
async function placeBet() {
const amount = parseInt(betInput.value);
if (!amount || amount <= 0) { SoundManager.error(); return; }
const fd = new FormData(); fd.append('amount', amount);
if (!amount || amount <= 0) {
SoundManager.error();
return;
}
const formData = new FormData();
formData.append('amount', amount);
try {
const res = await fetch('/web/api/crash/bet', { method:'POST', body:fd });
const res = await fetch('/web/api/crash/bet', { method: 'POST', body: formData });
const data = await res.json();
if (data.success) { myActiveBet = data.bet; myCashoutMult = null; SoundManager.crashBet(); document.querySelectorAll('.user-balance').forEach(el => el.textContent = `${Math.floor(data.new_balance)}`); updateUI(); }
else { SoundManager.error(); alert(data.error); }
} catch(e) { SoundManager.error(); alert('Ошибка соединения'); }
if (data.success) {
myActiveBet = data.bet;
myCashoutMult = null;
SoundManager.crashBet();
document.querySelectorAll('.user-balance').forEach(el => {
el.textContent = `${Math.floor(data.new_balance)}`;
});
updateUI();
} else {
SoundManager.error();
alert(data.error);
}
} catch (e) {
SoundManager.error();
alert('Ошибка соединения');
}
}
async function cashOut() {
if (!myActiveBet || myCashoutMult !== null) return;
try {
const res = await fetch('/web/api/crash/cashout', { method:'POST' });
const res = await fetch('/web/api/crash/cashout', { method: 'POST' });
const data = await res.json();
if (data.success) { myCashoutMult = data.multiplier; SoundManager.crashCashout(myCashoutMult); document.querySelectorAll('.user-balance').forEach(el => el.textContent = `${Math.floor(data.new_balance)}`); updateUI(); }
else { SoundManager.error(); alert(data.error); }
} catch(e) { SoundManager.error(); alert('Ошибка соединения'); }
if (data.success) {
myCashoutMult = data.multiplier;
SoundManager.crashCashout();
document.querySelectorAll('.user-balance').forEach(el => {
el.textContent = `${Math.floor(data.new_balance)}`;
});
updateUI();
} else {
SoundManager.error();
alert(data.error);
}
} catch (e) {
SoundManager.error();
alert('Ошибка соединения');
}
}
function toggleSound() { const e = SoundManager.toggle(); document.getElementById('soundToggle').textContent = e ? '🔊' : '🔇'; }
document.addEventListener('DOMContentLoaded', () => { const btn = document.getElementById('soundToggle'); if(btn) btn.textContent = localStorage.getItem('sound_enabled') !== 'false' ? '🔊' : '🔇'; });
async function logout() {
await fetch('/web/api/auth/logout', { method: 'POST' });
window.location.href = '/';
}
function toggleSound() {
const enabled = SoundManager.toggle();
document.getElementById('soundToggle').textContent = enabled ? '🔊' : '🔇';
}
document.addEventListener('DOMContentLoaded', () => {
const btn = document.getElementById('soundToggle');
if (btn) btn.textContent = localStorage.getItem('sound_enabled') !== 'false' ? '🔊' : '🔇';
});
</script>
{% include '_activity_sidebar.html' %}
</body>