Dildo items, admin fixes, UI refactor

- Add 85 custom Dildo items (knife/skin pattern names, various rarities)
- Create Dildo Case config (hidden, items obtained via contracts)
- Fix admin user_detail.html nested script tag (Notify fallback removed)
- Fix admin cases.html embedded script tag
- Add notifications.js to admin base.html
- Fix admin users.html: use counts instead of relationship lists
- Replace old money format with |money filter across all templates
- Standardize nav partial (_nav.html) with active_page highlighting
- Fix RARITY_COLORS rename in activity sidebar
- Fix toast animation (forwards) and remove duplicate CSS
- Replace showToast() with Notify.* in upgrade page
- Add cs2_simulator.db and *.log to .gitignore
- Create remote-deploy.sh with DB exclusion
This commit is contained in:
root
2026-07-05 16:15:12 +00:00
parent 50535ec777
commit 3fe8a47e04
25 changed files with 2669 additions and 390 deletions
+2
View File
@@ -2,3 +2,5 @@ __pycache__/
*.pyc
*.pyo
.DS_Store
cs2_simulator.db
*.log
+11 -2
View File
@@ -15,6 +15,14 @@ from rpu import get_user_rpu, calculate_rpu_level, get_rpu_description
admin_router = APIRouter(prefix="/admin", tags=["admin"])
templates = Jinja2Templates(directory="templates")
def money_filter(value):
try:
return "{:,.0f}".format(float(value)).replace(",", " ")
except (ValueError, TypeError):
return "0"
templates.env.filters["money"] = money_filter
# Загрузка конфигов
CASES_FILE = Path("cases.json")
MAINPAGE_FILE = Path("mainpage.json")
@@ -137,8 +145,9 @@ async def admin_users(
"balance": u.balance,
"is_admin": u.is_admin,
"created_at": u.created_at,
"inventory_items": u.inventory_items,
"case_openings": u.case_openings,
"case_openings": db.query(CaseOpening).filter(CaseOpening.user_id == u.id).count(),
"contracts": db.query(Contract).filter(Contract.user_id == u.id).count(),
"inventory_count": db.query(InventoryItem).filter(InventoryItem.user_id == u.id).count(),
"rpu_level": round(calculate_rpu_level(rpu))
})
+8
View File
@@ -1448,5 +1448,13 @@
],
"image_url": "",
"description": ""
},
{
"name": "dildo_case",
"display_name": "Dildo Case",
"price_open": 149.0,
"items": [],
"image_url": "",
"description": "Самые сомнительные предметы коллекции Dildo"
}
]
+2473 -13
View File
File diff suppressed because it is too large Load Diff
+69 -12
View File
@@ -58,6 +58,14 @@ app.include_router(admin_router)
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
def money_filter(value):
try:
return "{:,.0f}".format(float(value)).replace(",", " ")
except (ValueError, TypeError):
return "0"
templates.env.filters["money"] = money_filter
# Создаем отдельный роутер для API оригинального симулятора
from backend import app as api_app
@@ -301,7 +309,8 @@ async def index(request: Request, user: Optional[User] = Depends(get_current_use
return templates.TemplateResponse("index.html", {
"request": request,
"user": None
"user": None,
"active_page": "home"
})
@app.get("/login", response_class=HTMLResponse)
@@ -312,7 +321,8 @@ async def login_page(request: Request, user: Optional[User] = Depends(get_curren
return templates.TemplateResponse("login.html", {
"request": request,
"user": None
"user": None,
"active_page": "login"
})
@app.get("/register", response_class=HTMLResponse)
@@ -323,7 +333,8 @@ async def register_page(request: Request, user: Optional[User] = Depends(get_cur
return templates.TemplateResponse("register.html", {
"request": request,
"user": None
"user": None,
"active_page": "register"
})
@app.get("/profile", response_class=HTMLResponse)
@@ -403,7 +414,8 @@ async def profile_page(
"types": sorted(types),
"rarity_order": RARITY_ORDER,
"is_public": False,
"profile_user": user
"profile_user": user,
"active_page": "profile"
})
# ═══════════════════════════════════════════════════════════════════════════════
@@ -436,9 +448,42 @@ async def public_profile_page(
inventory_items = db.query(InventoryItem).filter(
InventoryItem.user_id == profile_user.id
).all()
).order_by(desc(InventoryItem.obtained_at)).all()
rarity_order = {k: v for k, v in RARITY_ORDER.items()}
rarities = set()
types = set()
enriched_items = []
for item in inventory_items:
item_data = None
for idx, it in enumerate(ALL_ITEMS):
if it.get("_id", idx) == item.item_id:
item_data = it
break
if not item_data:
item_data = get_item(item.item_id)
image_url = ""
price_rub = 100
if item_data:
image_url = item_data.get("image_url", "")
price_rub = item_data.get("price_rub", 100)
enriched_items.append({
"id": item.id,
"item_id": item.item_id,
"name": item.market_hash_name,
"rarity": item.rarity,
"wear": item.wear,
"float": item.float_value,
"type": item.type,
"image_url": image_url,
"price_rub": price_rub
})
if item.rarity:
rarities.add(item.rarity)
if item.type:
types.add(item.type)
valuable_items = sorted(
inventory_items,
key=lambda x: rarity_order.get(x.rarity, 0),
@@ -455,7 +500,12 @@ async def public_profile_page(
"contracts_completed": contracts_completed,
"recent_openings": recent_openings,
"recent_contracts": recent_contracts,
"valuable_items": valuable_items
"valuable_items": valuable_items,
"inventory_items": enriched_items,
"rarities": sorted(rarities),
"types": sorted(types),
"rarity_order": RARITY_ORDER,
"active_page": "profile"
})
@@ -539,7 +589,8 @@ async def cases_page(
return templates.TemplateResponse("cases.html", {
"request": request,
"user": user,
"sections": sections
"sections": sections,
"active_page": "cases"
})
@@ -598,7 +649,8 @@ async def contracts_page(
"user": user,
"inventory_items": all_items + knife_items,
"has_contract": has_contract or has_knife_contract,
"knife_count": len(knife_items)
"knife_count": len(knife_items),
"active_page": "contracts"
})
@app.get("/inventory")
@@ -1432,7 +1484,8 @@ async def case_detail_page(
"items_by_rarity": items_by_rarity,
"sorted_rarities": sorted_rarities,
"rarities_count": rarities_count,
"case_items": case_items_json
"case_items": case_items_json,
"active_page": "cases"
})
@@ -2175,7 +2228,8 @@ async def upgrade_page(
"total_attempts": total_attempts,
"total_success": total_success,
"success_rate": round(success_rate, 1),
"recent_upgrades": recent_upgrades
"recent_upgrades": recent_upgrades,
"active_page": "upgrade"
})
@app.post("/web/api/upgrade/calculate")
@@ -2438,7 +2492,8 @@ async def achievements_page(
"user": user,
"achievements": achievements,
"total": total,
"unlocked": unlocked
"unlocked": unlocked,
"active_page": "achievements"
})
@@ -2479,7 +2534,8 @@ async def activity_page(
"created_at": a.created_at.isoformat()
}
for a in activities
]
],
"active_page": "activity"
})
@@ -2614,6 +2670,7 @@ async def crash_page(
return templates.TemplateResponse("crash.html", {
"request": request,
"user": user,
"active_page": "crash"
})
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# Deploy to remote server, excluding DB and other local-only files
rsync -av \
--exclude '.git' \
--exclude '__pycache__' \
--exclude '*.pyc' \
--exclude '.gitignore' \
--exclude 'cs2_simulator.db' \
--exclude '*.log' \
--exclude 'remote-deploy.sh' \
--delete \
/root/dodep-simulator/ \
root@zernmc.ru:/root/dodep/
ssh root@zernmc.ru "cd /root/dodep && sleep 1 && pkill -f 'frontend:app' 2>/dev/null; setsid python3 -m uvicorn frontend:app --host 0.0.0.0 --port 13669 > /tmp/frontend.log 2>&1 &"
echo "Deployed and restarted."
+1 -44
View File
@@ -3984,49 +3984,6 @@ h1, h2, h3, h4, h5, h6, .logo, .nav-link, .btn {
/* Результат */
/* Toast */
.toast-container {
position: fixed;
top: 70px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
display: flex;
flex-direction: column;
gap: 0.4rem;
pointer-events: none;
}
.toast {
padding: 0.5rem 1rem;
border-radius: 8px;
font-size: 0.8rem;
font-weight: 600;
white-space: nowrap;
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.2);
pointer-events: auto;
background: var(--bg-card);
border: 1px solid var(--border-color);
color: var(--text-primary);
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
.toast.show {
opacity: 1;
transform: translateY(0);
}
.toast.success {
border-color: #10b981;
box-shadow: 0 4px 20px rgba(16,185,129,0.3);
}
.toast.fail {
border-color: #ef4444;
box-shadow: 0 4px 20px rgba(239,68,68,0.2);
}
.upgrade-page .result-item-display img {
width: 72px;
height: 60px;
@@ -4515,7 +4472,7 @@ a:not(.btn):not(.nav-link):not(.logo):hover {
gap: 10px;
pointer-events: auto;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
animation: toastSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
animation: toastSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
transform-origin: top right;
position: relative;
overflow: hidden;
+20 -25
View File
@@ -172,40 +172,37 @@
</button>
<script>
document.addEventListener('DOMContentLoaded', async () => {
document.addEventListener('DOMContentLoaded', () => {
const list = document.getElementById('activitySidebarList');
const sidebar = document.getElementById('activitySidebar');
document.body.classList.add('has-activity-sidebar');
// Восстанавливаем состояние из localStorage
if (localStorage.getItem('sidebar_closed') === 'true') {
sidebar.classList.add('closed');
document.body.classList.add('sidebar-closed');
document.getElementById('activityToggleBtn').classList.add('visible');
}
async function loadActivities() {
try {
const res = await fetch('/web/api/activity?limit=10');
const data = await res.json();
if (data.success && data.activities) {
renderActivities(data.activities);
}
} catch (e) {
if (list) list.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-secondary);">Не удалось загрузить</div>';
}
function loadActivities() {
fetch('/web/api/activity?limit=10')
.then(r => r.json())
.then(data => {
if (data.success && data.activities) {
renderActivities(data.activities);
}
})
.catch(() => {
if (list && !list.querySelector('.activity-sidebar-item')) {
list.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-secondary);font-size:0.85rem;">Не удалось загрузить</div>';
}
});
}
WS.on('online_count', (data) => {
document.getElementById('onlineCount').textContent = data.online || 0;
const el = document.getElementById('onlineCount');
if (el) el.textContent = data.online || 0;
});
WS.on('connected', () => {
loadActivities();
});
await loadActivities();
WS.on('activity', (data) => {
const act = data.activity;
if (!act || !list) return;
@@ -219,14 +216,12 @@ document.addEventListener('DOMContentLoaded', async () => {
}
});
// Periodic refresh every 30s to catch missed activities
setInterval(loadActivities, 30000);
WS.on('connected', loadActivities);
// Expose for manual refresh from other pages
window.refreshActivityFeed = loadActivities;
loadActivities();
});
const RARITY_COLORS = {
const ACTIVITY_RARITY_COLORS = {
'consumer-grade': '#b0b0b0', 'industrial-grade': '#5e98d9',
'mil-spec': '#4b69ff', 'mil-spec-grade': '#4b69ff',
'restricted': '#8847ff', 'classified': '#d32ce6',
@@ -245,7 +240,7 @@ function createActivityItem(act) {
item.style.cursor = 'pointer';
const raritySlug = (act.data && act.data.rarity || '').toLowerCase().replace(/ /g, '-').replace(/_/g, '-').replace(/™/g, '');
const rarityColor = RARITY_COLORS[raritySlug] || '';
const rarityColor = ACTIVITY_RARITY_COLORS[raritySlug] || '';
if (rarityColor) {
item.style.background = `rgba(${hexToRgb(rarityColor)}, 0.08)`;
}
+25
View File
@@ -0,0 +1,25 @@
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
<a href="/cases" class="nav-link{% if active_page == 'cases' %} active{% endif %}">Кейсы</a>
<a href="/contracts" class="nav-link{% if active_page == 'contracts' %} active{% endif %}">Контракты</a>
<a href="/upgrade" class="nav-link{% if active_page == 'upgrade' %} active{% endif %}">🆙 Апгрейд</a>
{% if user %}
<a href="/crash" class="nav-link{% if active_page == 'crash' %} active{% endif %}">💥 Crash</a>
<a href="/profile" class="nav-link{% if active_page == 'profile' %} active{% endif %}">Профиль</a>
<a href="/activity" class="nav-link{% if active_page == 'activity' %} active{% endif %}">📰 Лента</a>
<a href="/achievements" class="nav-link{% if active_page == 'achievements' %} active{% endif %}">🏆 Достижения</a>
<span class="user-balance">{{ "{:,.0f}".format(user.balance).replace(',', ' ') }} ₽</span>
<button onclick="toggleSound()" class="btn btn-outline" id="soundToggle" title="Звук">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
{% else %}
<a href="/activity" class="nav-link{% if active_page == 'activity' %} active{% endif %}">📰 Лента</a>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/login" class="btn btn-outline">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
{% endif %}
</div>
</div>
</nav>
+2 -20
View File
@@ -183,25 +183,7 @@
</style>
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/crash" class="nav-link">💥 Crash</a>
<a href="/profile" class="nav-link">Профиль</a>
<a href="/achievements" class="nav-link active">🏆 Достижения</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<a href="/profile" class="nav-link">Профиль</a>
<span class="user-balance">{{ "%.0f"|format(user.balance) }} ₽</span>
<button onclick="toggleSound()" class="btn btn-outline" id="soundToggle" title="Звук">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="container" style="padding: 2rem 1rem;">
<div class="achievements-header">
@@ -243,7 +225,7 @@
<div class="achievement-title">{{ ach.title }}</div>
<div class="achievement-desc">{{ ach.description }}</div>
{% if ach.reward_amount > 0 %}
<div class="achievement-reward">+{{ "%.0f"|format(ach.reward_amount) }} ₽</div>
<div class="achievement-reward">+{{ ach.reward_amount|money }} ₽</div>
{% endif %}
<div class="achievement-progress">
<div class="progress-bar">
+1 -34
View File
@@ -97,35 +97,7 @@
</style>
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
{% if user %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/crash" class="nav-link">💥 Crash</a>
<a href="/profile" class="nav-link">Профиль</a>
<a href="/activity" class="nav-link active">📰 Лента</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" title="Звук">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
{% else %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/activity" class="nav-link active">📰 Лента</a>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/login" class="btn btn-outline">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
{% endif %}
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="container" style="padding: 2rem 1rem;">
<div class="activity-header">
@@ -176,11 +148,6 @@
<script src="/static/js/safemode.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Reload activities when WS connects/reconnects
WS.on('connected', () => {
location.reload();
});
WS.on('activity', (data) => {
const act = data.activity;
if (!act) return;
+1
View File
@@ -356,6 +356,7 @@
</div>
{% block modals %}{% endblock %}
<script src="/static/js/notifications.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>
-2
View File
@@ -252,8 +252,6 @@ async function searchCaseItems() {
document.getElementById('caseItemSearchResults').innerHTML = html;
}
<script>if(typeof Notify==="undefined"){window.Notify={toast:function(m){alert(m)},error:function(m){alert(m)},success:function(m){alert(m)},info:function(m){alert(m)},confirm:function(m,t,c){if(c&&confirm(m))c(!0)}}}</script>
<script>
async function saveCase() {
const name = document.getElementById('caseNameInput').value.trim();
if (!name) { Notify.error('Введите название кейса'); return; }
+2 -2
View File
@@ -27,7 +27,7 @@
</div>
<div class="a-stat">
<div class="as-label">Общий баланс</div>
<div class="as-value">{{ "%.0f"|format(total_balance) }} ₽</div>
<div class="as-value">{{ total_balance|money }} ₽</div>
<div class="as-bar" style="width:40%;background:#34d399"></div>
</div>
<div class="a-stat">
@@ -47,7 +47,7 @@
<div><div class="as-value" style="font-size:1.2rem" style="color:#22c55e">{{ lucky_count }}</div><div class="as-label" style="font-size:0.7rem">🍀 Везучих</div></div>
<div><div class="as-value" style="font-size:1.2rem" style="color:#f59e0b">{{ normal_count }}</div><div class="as-label" style="font-size:0.7rem">📊 Обычных</div></div>
<div><div class="as-value" style="font-size:1.2rem" style="color:#ef4444">{{ unlucky_count }}</div><div class="as-label" style="font-size:0.7rem">🌧️ Невезучих</div></div>
<div><div class="as-value" style="font-size:1.2rem">{{ "%.0f"|format(total_spent) }} ₽</div><div class="as-label" style="font-size:0.7rem">Потрачено всего</div></div>
<div><div class="as-value" style="font-size:1.2rem">{{ total_spent|money }} ₽</div><div class="as-label" style="font-size:0.7rem">Потрачено всего</div></div>
</div>
{% else %}
<div class="a-empty"><div class="a-empty-icon">📭</div>Нет данных RPU</div>
+2 -3
View File
@@ -14,7 +14,7 @@
<div class="a-stats" style="grid-template-columns:repeat(4,1fr)">
<div class="a-stat">
<div class="as-label">Баланс</div>
<div class="as-value">{{ "%.0f"|format(target_user.balance) }} ₽</div>
<div class="as-value">{{ target_user.balance|money }} ₽</div>
</div>
<div class="a-stat">
<div class="as-label">Предметов</div>
@@ -94,7 +94,7 @@
<div class="a-modal">
<div class="a-modal-title">💰 Изменить баланс</div>
<p style="font-size:0.85rem;color:rgba(255,255,255,0.5);margin-bottom:1rem">
Текущий: <strong style="color:#f59e0b">{{ "%.2f"|format(target_user.balance) }} ₽</strong>
Текущий: <strong style="color:#f59e0b">{{ target_user.balance|money }} ₽</strong>
</p>
<div class="a-mb">
<label class="a-label">Сумма</label>
@@ -172,7 +172,6 @@ let currentRPU = null;
function closeModal(id) { document.getElementById(id).classList.remove('open'); }
function openModal(id) { document.getElementById(id).classList.add('open'); }
<script>if(typeof Notify==="undefined"){window.Notify={toast:function(m){alert(m)},error:function(m){alert(m)},success:function(m){alert(m)},info:function(m){alert(m)},confirm:function(m,t,c){if(c&&confirm(m))c(!0)}}}</script>
// ====== BALANCE ======
function openBalanceModal() { openModal('balanceModal'); }
async function updateBalance() {
+5 -5
View File
@@ -21,7 +21,7 @@
<th>Статус</th>
<th>Кейсов</th>
<th>Контрактов</th>
<th>Достижений</th>
<th>Предметов</th>
<th></th>
</tr>
</thead>
@@ -34,15 +34,15 @@
{{ u.username }}
</a>
</td>
<td>{{ "%.0f"|format(u.balance) }} ₽</td>
<td>{{ u.balance|money }} ₽</td>
<td>
{% if u.is_admin %}<span class="a-badge a-badge-admin">Админ</span>{% endif %}
{% if u.is_banned %}<span class="a-badge a-badge-banned">Забанен</span>{% endif %}
{% if not u.is_admin and not u.is_banned %}<span class="a-badge a-badge-user">Игрок</span>{% endif %}
</td>
<td>{{ u.case_openings|default(0) }}</td>
<td>{{ u.contracts|default(0) }}</td>
<td>{{ u.achievements|default(0) }}</td>
<td>{{ u.case_openings }}</td>
<td>{{ u.contracts }}</td>
<td>{{ u.inventory_count }}</td>
<td class="td-actions">
<a href="/admin/user/{{ u.id }}" class="a-btn a-btn-sm">✏️</a>
</td>
+5 -33
View File
@@ -214,35 +214,7 @@
</style>
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
{% if user %}
<a href="/cases" class="nav-link active">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/crash" class="nav-link">💥 Crash</a>
<a href="/profile" class="nav-link">Профиль</a>
<a href="/activity" 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" title="Звук">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
{% else %}
<a href="/cases" class="nav-link active">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/login" class="btn btn-outline">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
{% endif %}
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="case-detail-container">
<div class="container">
@@ -255,7 +227,7 @@
<p class="case-subtitle">{{ total_items }} предметов в кейсе</p>
</div>
<div class="case-info-badge">
<span class="case-price-tag">🔑 {{ "%.0f"|format(case_price) }} ₽ за шт.</span>
<span class="case-price-tag">🔑 {{ case_price|money }} ₽ за шт.</span>
</div>
</div>
@@ -266,7 +238,7 @@
<div class="count-buttons" id="countButtons"></div>
</div>
<div class="total-price-display" id="totalPriceDisplay">
<span>Итого:</span> {{ "%.0f"|format(case_price) }} ₽
<span>Итого:</span> {{ case_price|money }} ₽
</div>
<button class="btn btn-primary btn-large" onclick="startOpening()" id="openButton">
🎲 Открыть
@@ -389,9 +361,9 @@
{% if item.min_price and item.max_price %}
<div class="item-price-compact">
{% if item.min_price == item.max_price %}
{{ "%.0f"|format(item.min_price) }} ₽
{{ item.min_price|money }} ₽
{% else %}
{{ "%.0f"|format(item.min_price) }} - {{ "%.0f"|format(item.max_price) }} ₽
{{ item.min_price|money }} - {{ item.max_price|money }} ₽
{% endif %}
</div>
{% endif %}
+3 -31
View File
@@ -155,35 +155,7 @@
</style>
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
{% if user %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/crash" class="nav-link">💥 Crash</a>
<a href="/profile" class="nav-link">Профиль</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<a href="/achievements" class="nav-link">🏆 Достижения</a>
<a href="/profile" class="nav-link">Профиль</a>
<span class="user-balance">{{ "%.2f"|format(user.balance) }} ₽</span>
<button onclick="toggleSound()" class="btn btn-outline" id="soundToggle" title="Звук">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
{% else %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/login" class="btn btn-outline">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
{% endif %}
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="cases-container">
<div class="container">
@@ -203,7 +175,7 @@
{% else %}
<span style="font-size: 3rem;">📦</span>
{% endif %}
<span class="case-price-badge">{{ "%.0f"|format(case.price) }} ₽</span>
<span class="case-price-badge">{{ case.price|money }} ₽</span>
</div>
<div class="case-info">
<div class="case-title">{{ case.display_name }}</div>
@@ -222,7 +194,7 @@
</div>
</div>
<div class="case-footer">
<span class="case-price">{{ "%.0f"|format(case.price) }} ₽</span>
<span class="case-price">{{ case.price|money }} ₽</span>
<span class="btn-open">Открыть</span>
</div>
</div>
+1 -27
View File
@@ -7,33 +7,7 @@
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
{% 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="/profile" 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>
{% 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="/login" class="btn btn-outline">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
{% endif %}
</div>
</div>
</nav>
{% include "_nav.html" %}
<style>
.ct-page { max-width: 1100px; margin: 0 auto; padding: 1rem 1.5rem 3rem; }
+1 -19
View File
@@ -65,25 +65,7 @@
</style>
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/crash" class="nav-link active">💥 Crash</a>
<a href="/profile" class="nav-link">Профиль</a>
<a href="/achievements" class="nav-link">🏆 Достижения</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<a href="/profile" class="nav-link">Профиль</a>
<span class="user-balance">{{ "%.0f"|format(user.balance) }} ₽</span>
<button onclick="toggleSound()" class="btn btn-outline" id="soundToggle">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="container" style="padding: 2rem 1rem;">
<div class="crash-layout">
+1 -14
View File
@@ -7,20 +7,7 @@
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/login" class="btn btn-outline">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="hero">
<div class="container">
+1 -9
View File
@@ -7,15 +7,7 @@
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/register" class="btn btn-outline">Регистрация</a>
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="auth-container">
<div class="auth-card">
+15 -41
View File
@@ -446,43 +446,7 @@
</style>
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
{% if user and not is_public %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/crash" class="nav-link">💥 Crash</a>
<a href="/profile" class="nav-link active">Профиль</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<a href="/achievements" class="nav-link">🏆 Достижения</a>
<span class="user-balance">{{ "%.2f"|format(user.balance) }} ₽</span>
<button onclick="toggleSound()" class="btn btn-outline" id="soundToggle" title="Звук">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
{% elif user %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<span class="user-balance">{{ "%.2f"|format(user.balance) }} ₽</span>
<button onclick="toggleSound()" class="btn btn-outline" id="soundToggle" title="Звук">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
{% else %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link">🆙 Апгрейд</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/login" class="btn btn-outline">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
{% endif %}
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="container">
<div class="profile-layout">
@@ -519,18 +483,17 @@
</div>
{% if not is_public %}
<div class="stat-card">
<div class="stat-value">{{ "%.0f"|format(balance) }} ₽</div>
<div class="stat-value">{{ balance|money }} ₽</div>
<div class="stat-label">Баланс</div>
</div>
{% endif %}
</div>
{% if not is_public %}
<!-- Инвентарь -->
<div>
<div class="section-title">🎒 Инвентарь <span style="font-size:0.78rem;font-weight:400;color:var(--text-dim);font-family:'Rajdhani',sans-serif;">— {{ inventory_items|length }} предметов</span></div>
{% if inventory_items %}
{% if not is_public and inventory_items %}
<div class="inventory-actions">
<div class="selection-controls">
<label class="select-all-checkbox">
@@ -562,7 +525,9 @@
<input type="text" id="searchInput" class="filter-select" style="min-width:150px;" placeholder="Поиск..." oninput="filterItems()">
<button onclick="resetFilters()" class="btn btn-outline" style="font-size:0.72rem;padding:0.35rem 0.8rem;">Сброс</button>
</div>
{% endif %}
{% if inventory_items %}
<div class="inventory-grid-detailed" id="inventoryGrid">
{% for item in inventory_items %}
<div class="inventory-skin-card rarity-{{ item.rarity|lower|replace(' ', '-') }}"
@@ -571,10 +536,12 @@
data-type="{{ item.type }}"
data-name="{{ item.name|lower }}"
data-price="{{ item.price_rub or 100 }}">
{% if not is_public %}
<input type="checkbox" class="item-select-checkbox"
onchange="onItemSelect(this)"
data-id="{{ item.id }}"
data-price="{{ item.price_rub or 100 }}">
{% endif %}
<div class="skin-image-container">
<img src="{{ item.image_url or '/static/placeholder.png' }}"
alt="{{ item.name }}"
@@ -591,11 +558,13 @@
<span>{{ item.wear }}</span>
</div>
<div class="skin-details" style="margin-top:3px;color:var(--success-color);">
<span>💰 {{ "%.0f"|format(item.price_rub or 100) }} ₽</span>
<span>💰 {{ (item.price_rub or 100)|money }} ₽</span>
</div>
{% if not is_public %}
<div class="item-actions">
<button onclick="sellSingleItem({{ item.id }})" class="btn-sell-single">Продать</button>
</div>
{% endif %}
</div>
</div>
{% endfor %}
@@ -604,12 +573,17 @@
<div class="empty-state-large">
<div class="empty-icon">📭</div>
<h3>Инвентарь пуст</h3>
{% if is_public %}
<p>У пользователя пока нет предметов.</p>
{% else %}
<p>Откройте кейсы, чтобы получить первые предметы!</p>
<a href="/cases" class="btn btn-primary">Открыть кейсы</a>
{% endif %}
</div>
{% endif %}
</div>
{% if not is_public %}
<!-- Модалка массовой продажи -->
<div id="bulkSellModal" class="bulk-sell-modal" style="display:none;">
<div class="bulk-sell-content">
+1 -9
View File
@@ -7,15 +7,7 @@
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/login" class="btn btn-outline">Войти</a>
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="auth-container">
<div class="auth-card">
+3 -45
View File
@@ -7,35 +7,7 @@
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body class="upgrade-page">
<nav class="navbar">
<div class="container">
<a href="/" class="logo">🎮 CS2 Simulator</a>
<div class="nav-links">
{% if user %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link active">🆙 Апгрейд</a>
<a href="/crash" class="nav-link">💥 Crash</a>
<a href="/profile" class="nav-link">Профиль</a>
<a href="/activity" 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" title="Звук">🔊</button>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<button onclick="logout()" class="btn btn-outline">Выйти</button>
{% else %}
<a href="/cases" class="nav-link">Кейсы</a>
<a href="/contracts" class="nav-link">Контракты</a>
<a href="/upgrade" class="nav-link active">🆙 Апгрейд</a>
<a href="/activity" class="nav-link">📰 Лента</a>
<button onclick="toggleSafeMode()" class="btn btn-outline" id="safeModeToggle" title="Режим записи">🎙️</button>
<a href="/login" class="btn btn-outline">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
{% endif %}
</div>
</div>
</nav>
{% include "_nav.html" %}
<main class="upgrade-container">
<div class="container">
@@ -109,7 +81,6 @@
{% endif %}
</div>
<div id="toastContainer" class="toast-container"></div>
</div>
<!-- Target -->
@@ -596,19 +567,6 @@
`).join('');
}
function showToast(message, type) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
requestAnimationFrame(() => toast.classList.add('show'));
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
async function executeUpgrade() {
if (!selectedInput || !selectedTarget || isSpinning) return;
@@ -709,12 +667,12 @@
document.getElementById('displayTargetPrice').textContent = `${Math.floor(data.received_item.price).toLocaleString()} ₽`;
document.getElementById('selectedTargetDisplay').style.borderColor = '#10b981';
document.getElementById('selectedTargetDisplay').style.boxShadow = '0 0 20px rgba(16,185,129,0.3)';
showToast(`${getRandomPhrase('win')} ${data.received_item.name}`, 'success');
Notify.success(`${getRandomPhrase('win')} ${data.received_item.name}`);
SoundManager.wheelWin();
} else {
document.getElementById('selectedInputDisplay').style.borderColor = '#ef4444';
document.getElementById('selectedInputDisplay').style.boxShadow = '0 0 20px rgba(239,68,68,0.3)';
showToast(getRandomPhrase('lose'), 'fail');
Notify.error(getRandomPhrase('lose'));
SoundManager.wheelLose();
}