364 lines
25 KiB
HTML
364 lines
25 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Контракты — CS2 Simulator</title>
|
||
<link rel="stylesheet" href="/static/css/style.css">
|
||
</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">
|
||
{% if user %}
|
||
<a href="/cases" class="nav-link">Кейсы</a>
|
||
<a href="/contracts" class="nav-link active">Контракты</a>
|
||
<a href="/upgrade" class="nav-link">Апгрейд</a>
|
||
<a href="/crash" class="nav-link">Crash</a>
|
||
<a href="/inventory" class="nav-link">Инвентарь</a>
|
||
<a href="/achievements" 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>
|
||
{% else %}
|
||
<a href="/cases" class="nav-link">Кейсы</a>
|
||
<a href="/contracts" class="nav-link active">Контракты</a>
|
||
<a href="/upgrade" class="nav-link">Апгрейд</a>
|
||
<button onclick="toggleSafeMode()" class="btn btn-ghost" id="safeModeToggle" title="Режим записи">🎙️</button>
|
||
<a href="/login" class="btn btn-outline">Войти</a>
|
||
<a href="/register" class="btn btn-primary">Регистрация</a>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="section">
|
||
<div class="container" style="max-width:1100px;">
|
||
<div class="section-header" style="margin-bottom:1rem;">
|
||
<div>
|
||
<h1 class="section-title">🔄 Trade Up Контракт</h1>
|
||
<p class="section-subtitle">Выберите 10 предметов одинаковой редкости и типа</p>
|
||
</div>
|
||
</div>
|
||
|
||
{% if inventory_items and inventory_items|length >= 10 %}
|
||
<div style="display:grid;grid-template-columns:1fr 260px;gap:0.75rem;align-items:start;">
|
||
<!-- Inventory -->
|
||
<div class="card" style="padding:0;overflow:hidden;">
|
||
<div style="display:flex;flex-wrap:wrap;gap:0.35rem;align-items:center;padding:0.6rem 0.75rem;border-bottom:1px solid var(--border);background:rgba(0,0,0,0.1);">
|
||
<select id="ctRarityFilter" class="form-select" style="width:auto;min-width:120px;font-size:0.78rem;padding:0.3rem 0.5rem;" onchange="ctRenderInv()">
|
||
<option value="">Все редкости</option>
|
||
{% set rarities = inventory_items|map(attribute='rarity')|unique|list %}
|
||
{% for r in rarities %}
|
||
<option value="{{ r }}">{{ r }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<select id="ctTypeFilter" class="form-select" style="width:auto;min-width:100px;font-size:0.78rem;padding:0.3rem 0.5rem;" onchange="ctRenderInv()">
|
||
<option value="">Все типы</option>
|
||
{% set types = inventory_items|map(attribute='type')|unique|list %}
|
||
{% for t in types %}
|
||
<option value="{{ t }}">{{ t }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<input type="text" id="ctSearch" class="form-input" placeholder="🔍 Поиск..." style="flex:1;min-width:100px;font-size:0.78rem;padding:0.3rem 0.5rem;" oninput="ctRenderInv()">
|
||
<select id="ctSort" class="form-select" style="width:auto;font-size:0.78rem;padding:0.3rem 0.5rem;" onchange="ctRenderInv()">
|
||
<option value="default">По умолч.</option>
|
||
<option value="price_asc">Цена ↑</option>
|
||
<option value="price_desc">Цена ↓</option>
|
||
<option value="name">Имя</option>
|
||
<option value="float">Float</option>
|
||
</select>
|
||
<span class="text-xs text-dim" id="ctInvCount"></span>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:0.4rem;padding:0.6rem;max-height:520px;overflow-y:auto;" id="ctInvGrid"></div>
|
||
</div>
|
||
|
||
<!-- Selection Panel -->
|
||
<div class="card" style="position:sticky;top:80px;min-height:300px;" id="ctSelPanel">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem;">
|
||
<span class="display-font" style="font-size:0.85rem;">📋 Контракт</span>
|
||
<span style="font-size:1.2rem;font-weight:700;font-family:var(--font-display);color:var(--primary);" id="ctSelCount" class="">0/10</span>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.35rem;margin-bottom:0.75rem;" id="ctSelGrid">
|
||
{% for i in range(10) %}
|
||
<div style="background:rgba(0,0,0,0.12);border:1px dashed rgba(255,255,255,0.06);border-radius:8px;padding:0.3rem;min-height:48px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.1rem;transition:all 0.3s;" class="ct-sel-slot" id="ctSlot-{{ i }}" data-idx="{{ i }}">
|
||
<span style="font-size:0.5rem;color:var(--text-dim);">{{ i+1 }}</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="text-xs text-dim text-center" style="padding:1rem 0;display:block;" id="ctSelHint">Выберите предмет из инвентаря</div>
|
||
<div style="display:flex;flex-direction:column;gap:0.35rem;">
|
||
<button class="btn btn-ghost btn-sm" onclick="ctClearAll()" style="color:var(--text-dim);border:1px solid var(--border);">✕ Сбросить</button>
|
||
<button class="btn btn-success btn-sm" id="ctSubmitBtn" disabled onclick="ctOpenPaper()">🔄 Заключить контракт</button>
|
||
{% if knife_count >= 10 %}
|
||
<button class="btn btn-sm" id="ctKnifeBtn" onclick="ctOpenKnife()" style="background:linear-gradient(135deg,#ffd700,#f97316);color:#000;">🔞 Крафт дилдок ×{{ knife_count }}</button>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% if not user %}
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">🔒</div>
|
||
<div class="empty-state-title">Контракты доступны только авторизованным</div>
|
||
<div class="empty-state-desc">Войдите в аккаунт, чтобы просматривать инвентарь и заключать контракты</div>
|
||
<div class="flex gap-2 justify-center">
|
||
<a href="/login" class="btn btn-primary">Войти</a>
|
||
<a href="/register" class="btn btn-outline">Регистрация</a>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% else %}
|
||
<div class="empty-state">
|
||
{% if user %}
|
||
<div style="font-size:2rem;margin-bottom:0.5rem;">📭</div>
|
||
<div class="empty-state-title">Недостаточно предметов</div>
|
||
<div class="empty-state-desc">Нужно минимум 10 предметов одинаковой редкости и типа</div>
|
||
<a href="/cases" class="btn btn-primary">Открыть кейсы</a>
|
||
{% else %}
|
||
<div style="font-size:2rem;margin-bottom:0.5rem;">🔒</div>
|
||
<div class="empty-state-title">Контракты доступны только авторизованным</div>
|
||
<div class="empty-state-desc">Войдите в аккаунт, чтобы просматривать инвентарь и заключать контракты</div>
|
||
<a href="/login" class="btn btn-primary">Войти</a>
|
||
<a href="/register" class="btn btn-outline">Регистрация</a>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Paper Modal -->
|
||
<div class="modal-overlay" id="ctPaperModal" style="display:none;">
|
||
<div class="modal" style="max-width:500px;">
|
||
<div class="modal-header">
|
||
<div class="modal-title">📜 TRADE UP CONTRACT</div>
|
||
<button class="modal-close" onclick="ctClosePaper()">✕</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="text-xs text-dim text-center" style="margin-bottom:1rem;">CS2 Weapon Case — проверка подлинности</p>
|
||
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:0.35rem;margin-bottom:1rem;" id="ctPaperSlots">
|
||
{% for i in range(10) %}
|
||
<div style="background:rgba(0,0,0,0.12);border:1px dashed rgba(255,255,255,0.06);border-radius:8px;padding:0.35rem;min-height:56px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.1rem;" class="ct-paper-slot" id="ctSlotPaper{{ i }}"><span style="font-size:0.5rem;color:var(--text-dim)">{{ i+1 }}</span></div>
|
||
{% endfor %}
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem;">
|
||
<canvas id="ctSignCanvas" width="400" height="50" style="flex:1;border:1px dashed rgba(255,255,255,0.1);border-radius:6px;cursor:crosshair;background:#0d0e12;height:50px;"></canvas>
|
||
<button class="btn btn-ghost btn-sm" onclick="ctClearSign()" style="flex-shrink:0;">✕</button>
|
||
</div>
|
||
<div class="display-font text-center" style="font-size:1.2rem;color:var(--success);opacity:0;transition:opacity 0.5s;" id="ctStamp">✔ УТВЕРЖДЕНО</div>
|
||
<button class="btn btn-primary btn-block" id="ctSignBtn" onclick="ctSign()" style="margin-top:0.75rem;">✍ ПОДПИСАТЬ</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Result Modal -->
|
||
<div class="modal-overlay" id="ctResultModal" style="display:none;">
|
||
<div class="modal" style="max-width:380px;">
|
||
<div class="modal-body text-center" id="ctResultContent"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/static/js/sounds.js"></script>
|
||
<script src="/static/js/websocket.js"></script>
|
||
<script src="/static/js/safemode.js"></script>
|
||
<script>
|
||
const allItems = [{% for item in inventory_items %}{invId:{{ item.inventory_id }},name:{{ item.name|tojson }},rarity:{{ item.rarity|tojson }},type:{{ item.type|tojson }},float:{{ item.float }},image:{{ item.image_url|tojson }},isKnife:{{ 1 if item.is_knife else 0 }}}{% if not loop.last %},{% endif %}{% endfor %}];
|
||
const selected = new Map();
|
||
let ctMode = '';
|
||
|
||
function ctRenderInv() {
|
||
const rarity = document.getElementById('ctRarityFilter').value;
|
||
const type = document.getElementById('ctTypeFilter').value;
|
||
const q = document.getElementById('ctSearch').value.toLowerCase();
|
||
const sort = document.getElementById('ctSort').value;
|
||
let items = allItems.filter(it => {
|
||
if (selected.has(it.invId)) return false;
|
||
if (rarity && it.rarity !== rarity) return false;
|
||
if (type && it.type !== type) return false;
|
||
if (q && !it.name.toLowerCase().includes(q)) return false;
|
||
if (ctMode === 'knife' && !it.isKnife) return false;
|
||
if (ctMode === 'normal') {
|
||
const first = selected.values().next().value;
|
||
if (first && (it.isKnife || it.rarity !== first.data.rarity || it.type !== first.data.type)) return false;
|
||
}
|
||
return true;
|
||
});
|
||
switch (sort) {
|
||
case 'price_asc': items.sort((a,b) => (a.float||0)-(b.float||0)); break;
|
||
case 'price_desc': items.sort((a,b) => (b.float||0)-(a.float||0)); break;
|
||
case 'name': items.sort((a,b) => a.name.localeCompare(b.name)); break;
|
||
case 'float': items.sort((a,b) => a.float - b.float); break;
|
||
}
|
||
document.getElementById('ctInvCount').textContent = items.length;
|
||
const grid = document.getElementById('ctInvGrid');
|
||
grid.innerHTML = items.map((it,i) => `<div style="background:rgba(0,0,0,0.15);border:2px solid transparent;border-radius:8px;padding:0.4rem 0.35rem;cursor:pointer;transition:all 0.2s;display:flex;flex-direction:column;align-items:center;gap:0.15rem;animation:slideInUp 0.25s ease both;animation-delay:${(i%30)*0.02}s;${it.isKnife?'border-color:rgba(255,215,0,0.12);':''}" onclick="ctPick(${it.invId})" onmouseover="this.style.borderColor='rgba(255,255,255,0.1)'" onmouseout="this.style.borderColor='transparent'">
|
||
${it.isKnife ? '<span style="position:absolute;top:2px;left:2px;font-size:0.5rem;padding:0.05rem 0.25rem;background:rgba(255,215,0,0.15);color:#ffd700;border-radius:3px;font-weight:700;">🔪</span>' : ''}
|
||
<img src="${it.image||'/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'" loading="lazy" style="width:100%;height:48px;object-fit:contain;">
|
||
<div style="font-size:0.55rem;text-align:center;line-height:1.1;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;">${it.name}</div>
|
||
<div style="font-size:0.5rem;color:var(--text-dim);">★ ${it.float.toFixed(4)}</div>
|
||
</div>`).join('');
|
||
}
|
||
|
||
function ctPick(invId) {
|
||
const data = allItems.find(it => it.invId === invId);
|
||
if (!data) return;
|
||
if (selected.size === 0) ctMode = data.isKnife ? 'knife' : 'normal';
|
||
if (ctMode === 'knife' && !data.isKnife) return;
|
||
if (ctMode === 'normal') {
|
||
const first = selected.values().next().value;
|
||
const ref = first ? first.data : data;
|
||
if (data.isKnife || data.rarity !== ref.rarity || data.type !== ref.type) return;
|
||
}
|
||
if (selected.size >= 10) return;
|
||
selected.set(invId, { data, idx: selected.size });
|
||
ctRenderInv(); ctRenderSel();
|
||
}
|
||
|
||
function ctUnpick(invId) {
|
||
if (!selected.has(invId)) return;
|
||
selected.delete(invId);
|
||
let i = 0; for (const [,v] of selected) v.idx = i++;
|
||
if (selected.size === 0) ctMode = '';
|
||
ctRenderInv(); ctRenderSel();
|
||
}
|
||
|
||
function ctClearAll() { selected.clear(); ctMode = ''; ctRenderInv(); ctRenderSel(); }
|
||
|
||
function ctRenderSel() {
|
||
const count = document.getElementById('ctSelCount');
|
||
const hint = document.getElementById('ctSelHint');
|
||
const submitBtn = document.getElementById('ctSubmitBtn');
|
||
const knifeBtn = document.getElementById('ctKnifeBtn');
|
||
count.textContent = selected.size+'/10';
|
||
count.style.color = selected.size === 10 ? 'var(--success)' : 'var(--primary)';
|
||
if (selected.size === 0) { hint.style.display = 'block'; submitBtn.style.display = 'none'; if (knifeBtn) knifeBtn.style.display = 'none'; }
|
||
else { hint.style.display = 'none';
|
||
if (ctMode === 'knife') { submitBtn.style.display = 'none'; if (knifeBtn) knifeBtn.style.display = selected.size === 10 ? '' : 'none'; }
|
||
else { if (knifeBtn) knifeBtn.style.display = 'none'; submitBtn.style.display = selected.size === 10 ? '' : 'none'; }
|
||
}
|
||
submitBtn.disabled = selected.size !== 10;
|
||
const items = [...selected.entries()].sort((a,b) => a[1].idx - b[1].idx);
|
||
for (let i = 0; i < 10; i++) {
|
||
const slot = document.getElementById('ctSlot-'+i);
|
||
const entry = items[i];
|
||
if (entry) {
|
||
const [id, v] = entry;
|
||
slot.style.borderStyle = 'solid'; slot.style.borderColor = 'rgba(255,255,255,0.1)'; slot.style.background = 'rgba(255,255,255,0.02)';
|
||
slot.innerHTML = `<button style="position:absolute;top:1px;right:1px;width:14px;height:14px;border-radius:50%;background:rgba(239,68,68,0.8);color:white;border:none;font-size:0.45rem;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;" onclick="ctUnpick(${id})">✕</button>
|
||
<img src="${v.data.image||'/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'" style="width:100%;height:24px;object-fit:contain;">
|
||
<div style="font-size:0.45rem;text-align:center;line-height:1.05;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100%;">${v.data.name}</div>`;
|
||
} else {
|
||
slot.style.borderStyle = 'dashed'; slot.style.borderColor = 'rgba(255,255,255,0.06)'; slot.style.background = '';
|
||
slot.innerHTML = `<span style="font-size:0.5rem;color:var(--text-dim);">${i+1}</span>`;
|
||
}
|
||
}
|
||
}
|
||
|
||
function ctOpenPaper() {
|
||
if (selected.size !== 10) return;
|
||
const items = [...selected.entries()].sort((a,b) => a[1].idx - b[1].idx).map(([id,v]) => ({invId:id,name:v.data.name,rarity:v.data.rarity,float:v.data.float,image:v.data.image}));
|
||
window.__ctItems = items;
|
||
const slots = document.getElementById('ctPaperSlots');
|
||
slots.innerHTML = '';
|
||
for (let i = 0; i < 10; i++) {
|
||
const s = document.createElement('div'); s.style.cssText = 'background:rgba(0,0,0,0.12);border:1px dashed rgba(255,255,255,0.06);border-radius:8px;padding:0.35rem;min-height:56px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.1rem;'; s.id = 'ctSlotPaper'+i;
|
||
s.innerHTML = '<span style="font-size:0.5rem;color:var(--text-dim)">'+(i+1)+'</span>'; slots.appendChild(s);
|
||
}
|
||
document.getElementById('ctPaperModal').style.display = 'flex';
|
||
document.getElementById('ctStamp').style.opacity = '0';
|
||
setTimeout(ctInitSign, 100);
|
||
ctAnimateSlots();
|
||
}
|
||
|
||
function ctOpenKnife() { if (selected.size !== 10) return; ctOpenPaper(); }
|
||
|
||
async function ctAnimateSlots() {
|
||
const items = window.__ctItems || [];
|
||
for (let i = 0; i < 10; i++) {
|
||
const s = document.getElementById('ctSlotPaper'+i);
|
||
const item = items[i]; s.style.borderStyle = 'solid'; s.style.borderColor = 'rgba(255,255,255,0.1)'; s.style.background = 'rgba(255,255,255,0.02)';
|
||
s.innerHTML = `<img src="${item.image}" onerror="this.src='/static/placeholder.png'" style="width:100%;height:28px;object-fit:contain;"><div style="font-size:0.48rem;text-align:center;line-height:1.1;">${item.name}</div>`;
|
||
await new Promise(r => setTimeout(r, 40 + Math.random()*30));
|
||
}
|
||
await new Promise(r => setTimeout(r, 250));
|
||
document.getElementById('ctStamp').style.opacity = '1';
|
||
}
|
||
|
||
let ctSignCtx = null;
|
||
function ctInitSign() {
|
||
const c = document.getElementById('ctSignCanvas'); const ctx = c.getContext('2d'); ctSignCtx = ctx;
|
||
ctx.fillStyle = '#0d0e12'; ctx.fillRect(0,0,c.width,c.height); ctx.strokeStyle = '#d4c5a9'; ctx.lineWidth = 2; ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
||
let drawing = false;
|
||
const s = e => { const r=c.getBoundingClientRect(); drawing=true; ctx.beginPath(); ctx.moveTo(e.clientX-r.left, e.clientY-r.top); };
|
||
const m = e => { if(!drawing)return; const r=c.getBoundingClientRect(); ctx.lineTo(e.clientX-r.left, e.clientY-r.top); ctx.stroke(); };
|
||
const st = () => drawing = false;
|
||
c.onmousedown = s; c.onmousemove = m; c.onmouseup = st; c.onmouseleave = st;
|
||
c.ontouchstart = e=>{e.preventDefault();const t=e.touches[0];const r=c.getBoundingClientRect();drawing=true;ctx.beginPath();ctx.moveTo(t.clientX-r.left,t.clientY-r.top);};
|
||
c.ontouchmove = e=>{e.preventDefault();if(!drawing)return;const t=e.touches[0];const r=c.getBoundingClientRect();ctx.lineTo(t.clientX-r.left,t.clientY-r.top);ctx.stroke();};
|
||
c.ontouchend = ()=>drawing=false;
|
||
}
|
||
function ctClearSign() { const c = document.getElementById('ctSignCanvas'); if(ctSignCtx){ctSignCtx.fillStyle='#0d0e12';ctSignCtx.fillRect(0,0,c.width,c.height);} }
|
||
|
||
async function ctSign() {
|
||
const btn = document.getElementById('ctSignBtn'); btn.disabled = true; btn.textContent = '⏳ ПОДПИСЫВАЕМ...';
|
||
SoundManager.contractSubmit();
|
||
const items = window.__ctItems || [];
|
||
const fd = new FormData(); fd.append('inventory_ids', JSON.stringify(items.map(i=>i.invId)));
|
||
try {
|
||
const res = await fetch('/web/api/contracts/submit', { method:'POST', body:fd });
|
||
const data = await res.json();
|
||
if (res.ok && data.success) {
|
||
if (typeof handleAchievements === 'function') handleAchievements(data);
|
||
SoundManager.contractResult(); ctClosePaper();
|
||
setTimeout(() => ctShowResult(data.received_item, data.is_knife_contract), 200);
|
||
} else { SoundManager.error(); alert(data.error); ctClosePaper(); }
|
||
} catch(_) { SoundManager.error(); alert('Ошибка соединения'); ctClosePaper(); }
|
||
btn.disabled = false; btn.textContent = '✍ ПОДПИСАТЬ';
|
||
}
|
||
|
||
function ctShowResult(item, isKnife) {
|
||
const g = getRarityColor(item.rarity);
|
||
const title = isKnife ? '🔞 КОНТРАКТ ВЫПОЛНЕН' : '🎉 КОНТРАКТ ВЫПОЛНЕН';
|
||
document.getElementById('ctResultContent').innerHTML = `
|
||
<div style="font-size:2rem;margin-bottom:0.5rem">${isKnife ? '🔞' : '🎉'}</div>
|
||
<div class="display-font" style="font-size:1rem;letter-spacing:0.04em;margin-bottom:1rem">${title}</div>
|
||
<img src="${item.image_url||'/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'" style="width:120px;height:90px;object-fit:contain;margin-bottom:0.5rem;filter:drop-shadow(0 0 20px ${g})">
|
||
<div class="display-font" style="font-size:1rem;">${item.name}</div>
|
||
<div style="color:${g};font-weight:600;font-size:0.85rem;margin-bottom:0.3rem;">${item.rarity}</div>
|
||
<div class="text-xs text-dim">Float: ${item.float.toFixed(6)}</div>
|
||
<div class="text-xs text-dim">Шанс: ${(item.probability||0).toFixed(2)}%</div>
|
||
<button class="btn btn-primary" onclick="ctCloseResult()" style="margin-top:1rem;">Продолжить</button>
|
||
`;
|
||
document.getElementById('ctResultModal').style.display = 'flex';
|
||
}
|
||
|
||
function ctClosePaper() { document.getElementById('ctPaperModal').style.display = 'none'; }
|
||
function ctCloseResult() { document.getElementById('ctResultModal').style.display = 'none'; window.location.reload(); }
|
||
|
||
function handleAchievements(d) {
|
||
if(d && d.achievements_unlocked && Array.isArray(d.achievements_unlocked) && d.achievements_unlocked.length)
|
||
d.achievements_unlocked.forEach(a => showAchievementPopup(a.title || a.name || 'Достижение', a.reward || ''));
|
||
}
|
||
|
||
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' ? '🔊' : '🔇';
|
||
ctRenderInv(); ctRenderSel();
|
||
});
|
||
|
||
document.querySelectorAll('.modal-overlay').forEach(m => {
|
||
m.addEventListener('click', e => { if(e.target === m) m.style.display = 'none'; });
|
||
});
|
||
</script>
|
||
{% if user %}{% include '_activity_sidebar.html' %}{% endif %}
|
||
</body>
|
||
</html>
|