Files
dodep-simulator/templates/crash.html
T

201 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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; }
</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="/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="/inventory" 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>
</div>
</div>
</nav>
<main class="section">
<div class="container" style="max-width:960px;">
<div class="card" style="position:relative;overflow:hidden;">
<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-multiplier waiting" id="crashMultiplier">Ожидание...</div>
<div class="text-dim text-sm" style="margin-top:0.5rem;" id="crashStatus">Следующий раунд скоро начнётся</div>
</div>
</div>
<div class="flex gap-2" style="flex-wrap:wrap;padding:0.5rem 0;" 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>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0.35rem;margin-bottom:0.75rem;">
<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>
</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>
</div>
</div>
</main>
<script src="/static/js/sounds.js"></script>
<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;
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 graphPoints = [];
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;
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); }
}
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 (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 (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; }
lastPhase = phase;
}
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>`;
if (gameState.phase === 'running' || gameState.phase === 'crashed') {
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>`;
}
}
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('');
}
WS.on('crash_state', (data) => { if (!data.game) return; gameState = data.game; updateUI(); });
async function placeBet() {
const amount = parseInt(betInput.value);
if (!amount || amount <= 0) { SoundManager.error(); return; }
const fd = new FormData(); fd.append('amount', amount);
try {
const res = await fetch('/web/api/crash/bet', { method:'POST', body:fd });
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('Ошибка соединения'); }
}
async function cashOut() {
if (!myActiveBet || myCashoutMult !== null) return;
try {
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('Ошибка соединения'); }
}
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' ? '🔊' : '🔇'; });
</script>
{% include '_activity_sidebar.html' %}
</body>
</html>