UI redesign: CS2 gambling aesthetic, glassmorphism, responsive layout, enhanced sounds, admin panel overhaul
This commit is contained in:
+186
-561
@@ -9,25 +9,25 @@
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
<a href="/" class="logo">🎮 CS2 Simulator</a>
|
||||
<div class="nav-links">
|
||||
<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="/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="/achievements" class="nav-link">Достижения</a>
|
||||
<a href="/profile" class="nav-link">Профиль</a>
|
||||
<span class="user-balance">{{ "%.0f"|format(user.balance) }} ₽</span>
|
||||
<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>
|
||||
<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-outline" id="safeModeToggle">🎙️</button>
|
||||
<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 %}
|
||||
@@ -35,445 +35,150 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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 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 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>
|
||||
{% 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>
|
||||
{% endfor %}
|
||||
<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>
|
||||
<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 %}
|
||||
|
||||
<!-- 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>
|
||||
</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>
|
||||
|
||||
{% 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 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>
|
||||
<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>
|
||||
{% 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>
|
||||
<!-- 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 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>
|
||||
<!-- 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 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 = '';
|
||||
|
||||
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;
|
||||
@@ -481,177 +186,111 @@
|
||||
}
|
||||
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 '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('');
|
||||
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('');
|
||||
}
|
||||
|
||||
// ─── 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 (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();
|
||||
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++;
|
||||
}
|
||||
let i = 0; for (const [,v] of selected) v.idx = i++;
|
||||
if (selected.size === 0) ctMode = '';
|
||||
ctRenderInv();
|
||||
ctRenderSel();
|
||||
ctRenderInv(); ctRenderSel();
|
||||
}
|
||||
|
||||
function ctClearAll() {
|
||||
selected.clear();
|
||||
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';
|
||||
}
|
||||
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;
|
||||
|
||||
// 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 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>
|
||||
`;
|
||||
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.className = 'ct-sel-slot';
|
||||
slot.innerHTML = `<span class="s-num">${i+1}</span>`;
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}));
|
||||
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);
|
||||
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').classList.add('open');
|
||||
document.getElementById('ctStamp').classList.remove('active');
|
||||
document.getElementById('ctPaperModal').style.display = 'flex';
|
||||
document.getElementById('ctStamp').style.opacity = '0';
|
||||
setTimeout(ctInitSign, 100);
|
||||
ctAnimateSlots();
|
||||
}
|
||||
|
||||
function ctOpenKnife() {
|
||||
if (selected.size !== 10) return;
|
||||
ctOpenPaper();
|
||||
}
|
||||
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));
|
||||
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').classList.add('active');
|
||||
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';
|
||||
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(); };
|
||||
@@ -661,78 +300,64 @@
|
||||
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);}
|
||||
}
|
||||
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 = '⏳ ПОДПИСЫВАЕМ...';
|
||||
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)));
|
||||
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 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();
|
||||
SoundManager.contractResult(); ctClosePaper();
|
||||
setTimeout(() => ctShowResult(data.received_item, data.is_knife_contract), 200);
|
||||
} else {
|
||||
SoundManager.error(); alert(data.error || 'Ошибка'); ctClosePaper();
|
||||
}
|
||||
} else { SoundManager.error(); alert(data.error); ctClosePaper(); }
|
||||
} catch(_) { SoundManager.error(); alert('Ошибка соединения'); 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 g = getRarityColor(item.rarity);
|
||||
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>
|
||||
<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').classList.add('open');
|
||||
document.getElementById('ctResultModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function ctClosePaper() { document.getElementById('ctPaperModal').classList.remove('open'); }
|
||||
function ctCloseResult() { document.getElementById('ctResultModal').classList.remove('open'); window.location.reload(); }
|
||||
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 e = SoundManager.toggle();
|
||||
document.getElementById('soundToggle').textContent = e ? '🔊' : '🔇';
|
||||
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();
|
||||
ctRenderInv(); ctRenderSel();
|
||||
});
|
||||
|
||||
document.querySelectorAll('.ct-overlay').forEach(m => {
|
||||
m.addEventListener('click', e => { if(e.target === m) m.classList.remove('open'); });
|
||||
document.querySelectorAll('.modal-overlay').forEach(m => {
|
||||
m.addEventListener('click', e => { if(e.target === m) m.style.display = 'none'; });
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="/static/js/sounds.js"></script>
|
||||
<script src="/static/js/websocket.js"></script>
|
||||
<script src="/static/js/safemode.js"></script>
|
||||
{% if user %}{% include '_activity_sidebar.html' %}{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user