Files

715 lines
33 KiB
HTML
Raw Permalink 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>Контракты — CS2 Simulator</title>
<link rel="stylesheet" href="/static/css/style.min.css">
</head>
<body>
{% include "_nav.html" %}
<style>
.ct-page { max-width: 1100px; margin: 0 auto; padding: 1rem 1.5rem 3rem; }
.ct-header { text-align: center; margin-bottom: 1.5rem; }
.ct-header h1 { font-size: 1.6rem; font-family: 'Russo One', sans-serif; letter-spacing: 0.05em; }
.ct-header h1 span { color: var(--primary-color); }
.ct-header p { color: var(--text-secondary); font-size: 0.85rem; margin-top: 0.2rem; }
/* ─── Two-panel layout ─── */
.ct-layout {
display: grid;
grid-template-columns: 1fr 280px;
gap: 1rem;
align-items: start;
}
@media(max-width:800px){
.ct-layout{grid-template-columns:1fr}
.ct-sel-panel{order:-1}
}
/* ─── Left: Inventory panel ─── */
.ct-inv-panel {
background: rgba(20,21,26,0.5);
border: 1px solid var(--border-color);
border-radius: 14px;
overflow: hidden;
}
.ct-inv-toolbar {
display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border-color);
background: rgba(0,0,0,0.15);
}
.ct-inv-toolbar select, .ct-inv-toolbar input {
background: rgba(0,0,0,0.3);
border: 1px solid var(--border-color);
border-radius: 6px;
color: white;
padding: 0.35rem 0.6rem;
font-size: 0.78rem;
outline: none;
transition: border-color 0.2s;
}
.ct-inv-toolbar select:focus, .ct-inv-toolbar input:focus {
border-color: var(--primary-color);
}
.ct-inv-toolbar input {
flex: 1; min-width: 100px;
}
.ct-inv-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 0.5rem;
padding: 0.75rem;
max-height: 560px;
overflow-y: auto;
}
.ct-inv-grid::-webkit-scrollbar { width: 4px; }
.ct-inv-grid::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 2px; }
.ct-card {
position: relative;
background: rgba(0,0,0,0.2);
border: 2px solid transparent;
border-radius: 8px;
padding: 0.5rem 0.4rem;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.34,1.56,0.64,1);
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
user-select: none;
animation: ctCardIn 0.25s ease both;
}
@keyframes ctCardIn { from{opacity:0;transform:translateY(8px) scale(0.95)} to{opacity:1;transform:translateY(0) scale(1)} }
.ct-card:hover { border-color: rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); }
.ct-card:active { transform: scale(0.96); }
.ct-card img {
width: 100%; height: 56px;
object-fit: contain; pointer-events: none;
transition: transform 0.3s;
}
.ct-card:hover img { transform: scale(1.06); }
.ct-card .ct-name {
font-size: 0.6rem; text-align: center; line-height: 1.1;
overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.ct-card .ct-float { font-size: 0.55rem; color: var(--text-secondary); }
.ct-card.selected {
border-color: var(--success-color);
background: rgba(16,185,129,0.08);
}
.ct-card.spent {
opacity: 0.2;
cursor: not-allowed;
filter: grayscale(0.8);
pointer-events: none;
}
.ct-card.spent::after {
content: '✔';
position: absolute; top: 4px; right: 4px;
font-size: 0.65rem; color: var(--success-color);
font-weight: 800;
}
.ct-card.blocked {
opacity: 0.25;
cursor: not-allowed;
filter: grayscale(0.8);
}
.ct-card.knife-item { border-color: rgba(255,215,0,0.12); }
.ct-card .ct-knife-badge {
position: absolute; top: 3px; left: 3px;
font-size: 0.55rem; padding: 0.05rem 0.25rem;
background: rgba(255,215,0,0.15); color: #ffd700;
border-radius: 3px; font-weight: 700;
}
/* ─── Right: Selection panel ─── */
.ct-sel-panel {
background: rgba(20,21,26,0.5);
border: 1px solid var(--border-color);
border-radius: 14px;
padding: 1rem;
position: sticky; top: 80px;
min-height: 300px;
}
.ct-sel-header {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 1rem;
}
.ct-sel-header h3 {
font-family: 'Russo One', sans-serif;
font-size: 0.9rem;
letter-spacing: 0.04em;
}
.ct-sel-header .ct-sel-count {
font-size: 1.3rem;
font-weight: 800;
font-family: 'Russo One', sans-serif;
color: var(--primary-color);
}
.ct-sel-count.full { color: var(--success-color); }
.ct-sel-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.4rem;
margin-bottom: 1rem;
}
.ct-sel-slot {
background: rgba(0,0,0,0.15);
border: 1px dashed rgba(255,255,255,0.06);
border-radius: 8px;
padding: 0.35rem;
min-height: 52px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.1rem;
transition: all 0.3s;
position: relative;
}
.ct-sel-slot.fill {
border-color: rgba(255,255,255,0.1);
border-style: solid;
background: rgba(255,255,255,0.02);
}
.ct-sel-slot img {
width: 100%; height: 28px;
object-fit: contain;
}
.ct-sel-slot .s-name {
font-size: 0.48rem; text-align: center; line-height: 1.05;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%;
}
.ct-sel-slot .s-remove {
position: absolute; top: 1px; right: 1px;
width: 16px; height: 16px; border-radius: 50%;
background: rgba(239,68,68,0.8); color: white;
border: none; font-size: 0.55rem; cursor: pointer;
display: none; align-items: center; justify-content: center;
line-height: 1;
}
.ct-sel-slot.fill .s-remove { display: flex; }
.ct-sel-slot .s-remove:hover { background: rgba(239,68,68,1); transform: scale(1.15); }
.ct-sel-slot .s-num {
font-size: 0.5rem; color: var(--text-secondary);
}
.ct-sel-actions {
display: flex; flex-direction: column; gap: 0.4rem;
}
.ct-sel-actions button {
width: 100%; padding: 0.5rem;
border: none; border-radius: 8px;
font-family: 'Russo One', sans-serif;
font-size: 0.75rem;
letter-spacing: 0.04em;
cursor: pointer;
transition: all 0.25s;
}
.ct-sel-btn {
background: linear-gradient(135deg, var(--success-color), #059669);
color: white;
}
.ct-sel-btn:hover:not(:disabled) {
filter: brightness(1.1);
box-shadow: 0 4px 20px rgba(16,185,129,0.3);
}
.ct-sel-btn:disabled {
opacity: 0.3; cursor: not-allowed;
}
.ct-sel-btn.knife {
background: linear-gradient(135deg, #ffd700, #f97316);
}
.ct-sel-btn.knife:hover:not(:disabled) {
box-shadow: 0 4px 20px rgba(255,215,0,0.3);
}
.ct-sel-clear {
background: rgba(255,255,255,0.04);
border: 1px solid var(--border-color) !important;
color: var(--text-secondary);
}
.ct-sel-clear:hover { border-color: var(--danger-color) !important; color: var(--danger-color); }
.ct-sel-hint {
text-align: center;
font-size: 0.7rem;
color: var(--text-secondary);
padding: 1.5rem 0;
}
/* ─── Modals (unchanged) ─── */
.ct-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.7); backdrop-filter: blur(8px);
z-index: 200; display: none; align-items: center; justify-content: center;
padding: 1rem; animation: ctOvlIn 0.25s ease;
}
.ct-overlay.open { display: flex; }
@keyframes ctOvlIn { from{opacity:0} to{opacity:1} }
.ct-paper {
background: linear-gradient(145deg, #1c1d24, #14151a);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 20px; max-width: 640px; width: 100%; padding: 2rem;
position: relative; box-shadow: 0 20px 60px rgba(0,0,0,0.5);
animation: ctPaperIn 0.4s cubic-bezier(0.34,1.56,0.64,1);
}
@keyframes ctPaperIn { from{opacity:0;transform:scale(0.92) translateY(20px)} to{opacity:1;transform:scale(1) translateY(0)} }
.ct-paper-title { text-align: center; font-family:'Russo One',sans-serif; font-size:1.3rem; letter-spacing:0.08em; }
.ct-paper-sub { text-align: center; font-size:0.75rem; color:var(--text-secondary); letter-spacing:0.05em; margin-bottom:1.5rem; }
.ct-paper-line { height:1px; background:linear-gradient(90deg,transparent,rgba(255,255,255,0.08),transparent); margin-bottom:1.25rem; }
.ct-paper-grid { display:grid; grid-template-columns:repeat(5,1fr); gap:0.5rem; margin-bottom:1.25rem; }
.ct-paper-slot {
background:rgba(0,0,0,0.15); border:1px dashed rgba(255,255,255,0.06);
border-radius:8px; padding:0.35rem; min-height:60px;
display:flex; flex-direction:column; align-items:center; justify-content:center;
gap:0.15rem; transition:all 0.3s;
}
.ct-paper-slot.fill { border-color:rgba(255,255,255,0.12); border-style:solid; background:rgba(255,255,255,0.02); }
.ct-paper-slot img { width:100%; height:30px; object-fit:contain; }
.ct-paper-slot .s-name { font-size:0.5rem; text-align:center; line-height:1.1; }
.ct-sign-area { display:flex; align-items:center; gap:0.75rem; margin-bottom:0.75rem; }
.ct-sign-canvas { flex:1; border:1px dashed rgba(255,255,255,0.1); border-radius:6px; cursor:crosshair; background:#0d0e12; height:50px; }
.ct-stamp { text-align:center; font-family:'Russo One',sans-serif; font-size:1.4rem; letter-spacing:0.15em; color:var(--success-color); opacity:0; transition:opacity 0.5s; }
.ct-stamp.active { opacity:1; }
.ct-sign-btn {
width:100%; padding:0.7rem; border:none; border-radius:10px;
background:linear-gradient(135deg,var(--primary-color),#f97316); color:white;
font-family:'Russo One',sans-serif; font-size:0.85rem; letter-spacing:0.06em;
cursor:pointer; transition:all 0.25s; margin-top:0.75rem;
}
.ct-sign-btn:hover{filter:brightness(1.1);box-shadow:0 4px 24px rgba(245,158,11,0.3)}
.ct-sign-btn:disabled{opacity:0.5;cursor:not-allowed;filter:none;box-shadow:none}
.ct-result { text-align:center; }
.ct-result-img { width:160px; height:120px; object-fit:contain; margin-bottom:0.75rem; }
.ct-result-name { font-size:1.15rem; font-weight:700; margin-bottom:0.3rem; }
.ct-result-rarity { font-size:0.85rem; font-weight:600; margin-bottom:0.4rem; }
.ct-result-detail { font-size:0.75rem; color:var(--text-secondary); margin-bottom:0.15rem; }
.ct-result-btn {
margin-top:1.25rem; padding:0.6rem 1.8rem; border:none; border-radius:8px;
background:linear-gradient(135deg,var(--primary-color),#f97316); color:white;
font-family:'Russo One',sans-serif; font-size:0.85rem; cursor:pointer; transition:all 0.25s;
}
.ct-result-btn:hover{filter:brightness(1.1);box-shadow:0 4px 24px rgba(245,158,11,0.3)}
.ct-empty {
text-align: center; padding: 3rem 1rem;
color: var(--text-secondary);
}
.ct-empty h3 { margin-bottom: 0.5rem; }
.ct-empty p { font-size: 0.85rem; margin-bottom: 1rem; }
</style>
<div class="ct-page">
<div class="ct-header">
<h1>🔄 <span>Trade Up</span> Контракт</h1>
<p>Выберите 10 предметов одинаковой редкости и типа</p>
</div>
{% if inventory_items and inventory_items|length >= 10 %}
<div class="ct-layout">
<!-- Left: Inventory -->
<div class="ct-inv-panel">
<div class="ct-inv-toolbar">
<select id="ctRarityFilter" 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" 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" placeholder="🔍 Поиск..." oninput="ctRenderInv()">
<select id="ctSort" 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 style="font-size:0.7rem;color:var(--text-secondary)" id="ctInvCount"></span>
</div>
<div class="ct-inv-grid" id="ctInvGrid"></div>
</div>
<!-- Right: Selection -->
<div class="ct-sel-panel" id="ctSelPanel">
<div class="ct-sel-header">
<h3>📋 Контракт</h3>
<span class="ct-sel-count" id="ctSelCount">0/10</span>
</div>
<div class="ct-sel-grid" id="ctSelGrid">
{% for i in range(10) %}
<div class="ct-sel-slot" id="ctSlot-{{ i }}" data-idx="{{ i }}">
<span class="s-num">{{ i+1 }}</span>
</div>
{% endfor %}
</div>
<div class="ct-sel-hint" id="ctSelHint">Выберите предмет из инвентаря</div>
<div class="ct-sel-actions">
<button class="ct-sel-clear" onclick="ctClearAll()">✕ Сбросить</button>
<button class="ct-sel-btn" id="ctSubmitBtn" disabled onclick="ctOpenPaper()">🔄 Заключить контракт</button>
{% if knife_count >= 10 %}
<button class="ct-sel-btn knife" id="ctKnifeBtn" onclick="ctOpenKnife()">🔞 Крафт дилдок ×{{ knife_count }}</button>
{% endif %}
</div>
</div>
</div>
{% else %}
<div class="ct-empty">
{% if user %}
<div style="font-size:2rem;margin-bottom:0.5rem">📭</div>
<h3>Недостаточно предметов</h3>
<p>Нужно минимум 10 предметов одинаковой редкости и типа</p>
<a href="/cases" class="btn btn-primary">Открыть кейсы</a>
{% else %}
<div style="font-size:2rem;margin-bottom:0.5rem">🔒</div>
<h3>Контракты доступны только авторизованным</h3>
<p>Войдите в аккаунт, чтобы просматривать инвентарь и заключать контракты</p>
<a href="/login" class="btn btn-primary">Войти</a>
<a href="/register" class="btn btn-outline">Регистрация</a>
{% endif %}
</div>
{% endif %}
</div>
<!-- Модалка бумаги -->
<div class="ct-overlay" id="ctPaperModal">
<div class="ct-paper">
<div class="ct-paper-title">📜 TRADE UP CONTRACT</div>
<div class="ct-paper-sub">CS2 Weapon Case — проверка подлинности</div>
<div class="ct-paper-line"></div>
<div class="ct-paper-grid" id="ctPaperSlots"></div>
<div class="ct-paper-line"></div>
<div class="ct-sign-area">
<canvas class="ct-sign-canvas" id="ctSignCanvas" width="400" height="50"></canvas>
<button class="ct-sel-clear" onclick="ctClearSign()" style="padding:0.3rem 0.6rem"></button>
</div>
<div class="ct-stamp" id="ctStamp">✔ УТВЕРЖДЕНО</div>
<button class="ct-sign-btn" id="ctSignBtn" onclick="ctSign()">✍ ПОДПИСАТЬ</button>
</div>
</div>
<!-- Модалка результата -->
<div class="ct-overlay" id="ctResultModal">
<div class="ct-paper" style="max-width:420px">
<div class="ct-result" id="ctResultContent"></div>
</div>
</div>
<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(); // invId -> {data, idx}
let ctMode = ''; // '' | 'knife' | 'normal'
// ─── Render inventory ──────────────────────────────────────────────────
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 class="ct-card ${it.isKnife ? 'knife-item' : ''}"
onclick="ctPick(${it.invId})"
style="animation-delay:${(i%30)*0.025}s"
data-inv="${it.invId}">
${it.isKnife ? '<div class="ct-knife-badge">🔪</div>' : ''}
<img src="${it.image || '/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'" loading="lazy">
<div class="ct-name">${it.name}</div>
<div class="ct-float">★ ${it.float.toFixed(4)}</div>
</div>
`).join('');
}
// ─── Pick / Unpick ────────────────────────────────────────────────────
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 [id, v] of selected) {
v.idx = i++;
}
if (selected.size === 0) ctMode = '';
ctRenderInv();
ctRenderSel();
}
function ctClearAll() {
selected.clear();
ctMode = '';
ctRenderInv();
ctRenderSel();
}
// ─── Render selection panel ───────────────────────────────────────────
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.classList.toggle('full', selected.size === 10);
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;
// Fill slots
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.className = 'ct-sel-slot fill';
slot.innerHTML = `
<button class="s-remove" onclick="ctUnpick(${id})">✕</button>
<img src="${v.data.image || '/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'">
<div class="s-name">${v.data.name}</div>
`;
} else {
slot.className = 'ct-sel-slot';
slot.innerHTML = `<span class="s-num">${i+1}</span>`;
}
}
document.getElementById('ctSubmitBtn').textContent = ctMode === 'knife' ? '🔞 Крафт дилдок' : '🔄 Заключить контракт';
}
// ─── Paper modal ──────────────────────────────────────────────────────
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.className = 'ct-paper-slot';
s.id = 'ctSlotPaper' + i;
s.innerHTML = '<span style="font-size:0.5rem;color:var(--text-secondary)">' + (i+1) + '</span>';
slots.appendChild(s);
}
document.getElementById('ctPaperModal').classList.add('open');
document.getElementById('ctStamp').classList.remove('active');
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.classList.add('fill');
s.innerHTML = `<img src="${item.image}" onerror="this.src='/static/placeholder.png'">
<div class="s-name">${item.name}</div>`;
await new Promise(r => setTimeout(r, 50 + Math.random() * 40));
}
await new Promise(r => setTimeout(r, 250));
document.getElementById('ctStamp').classList.add('active');
}
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(); Notify.error(data.error || 'Ошибка'); ctClosePaper();
}
} catch(_) { SoundManager.error(); Notify.error('Ошибка соединения'); ctClosePaper(); }
btn.disabled = false; btn.textContent = '✍ ПОДПИСАТЬ';
}
function ctShowResult(item, isKnife) {
const g = getComputedStyle(document.documentElement).getPropertyValue('--rarity-' + (item.rarity||'').toLowerCase().replace(/ /g, '-').split('-')[0]).trim() || '#ffd700';
const title = isKnife ? '🔞 КОНТРАКТ ВЫПОЛНЕН' : '🎉 КОНТРАКТ ВЫПОЛНЕН';
document.getElementById('ctResultContent').innerHTML = `
<div style="--g:${g}">
<div style="font-size:2rem;margin-bottom:0.5rem">${isKnife ? '🔞' : '🎉'}</div>
<div style="font-family:'Russo One',sans-serif;font-size:1.1rem;letter-spacing:0.04em;margin-bottom:1rem">${title}</div>
<img class="ct-result-img" src="${item.image_url || '/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'" style="filter:drop-shadow(0 0 30px var(--g))">
<div class="ct-result-name">${item.name}</div>
<div class="ct-result-rarity" style="color:${g}">${item.rarity}</div>
<div class="ct-result-detail">Float: ${item.float.toFixed(6)}</div>
<div class="ct-result-detail">Шанс: ${(item.probability || 0).toFixed(2)}%</div>
<button class="ct-result-btn" onclick="ctCloseResult()">Продолжить</button>
</div>
`;
document.getElementById('ctResultModal').classList.add('open');
}
function ctClosePaper() { document.getElementById('ctPaperModal').classList.remove('open'); }
function ctCloseResult() { document.getElementById('ctResultModal').classList.remove('open'); 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 || 'Достижение'));
}
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' ? '🔊' : '🔇';
ctRenderInv();
ctRenderSel();
});
document.querySelectorAll('.ct-overlay').forEach(m => {
m.addEventListener('click', e => { if(e.target === m) m.classList.remove('open'); });
});
</script>
<script src="/static/js/app.min.js"></script>
<script src="/static/js/sounds.min.js"></script>
<script src="/static/js/websocket.min.js"></script>
<script src="/static/js/notifications.min.js"></script>
<script src="/static/js/safemode.js"></script>
{% if user %}{% include '_activity_sidebar.html' %}{% endif %}
</body>
</html>