RPU v2, promo/bank система, админка, оптимизации

— RPU v2: бюджет удачи, потолок (ceiling), комбек после
  проигрышей, hot/cold анализ, интеграция с promo-фазами
— Bank API: mock-банк для карты (депозит/вывод с балансом 1000₽)
— Promo-сервис: автогенерация промокодов каждый час, типы
  (card_to_site, luck_boost, ceiling_boost, free_case, reset_streak)
— Promo-фазы: LUCKY/NEUTRAL/UNLUCKY, переключение каждые 1-6ч
— Admin: история транзакций (user_history), управление промокодами,
  управление promo-фазами, API фильтров/коллекций, RPU info
— Database: WAL mode, новые поля RPU v2, TransactionLog,
  карточный баланс, статистики (streaks, ceiling, budget)
— Frontend: /deposit, /promo/activate, /withdraw, /card-balance,
  /online-count, /rpu-info, lifespan вместо startup event
— UI: user_history.html, улучшения профиля, кейсов, навигации
— Cases: обновление cases.json (1892 строки новых данных)
— Achievements: кэш коллекций, оптимизация is_custom проверок
— Sounds: дополнительные звуковые эффекты
— Upgrade: интеграция с RPU (множители, streaks)
— .gitignore: исключены .db, .bak, __pycache__
This commit is contained in:
2026-07-25 13:11:08 +00:00
parent 6638ab99f3
commit 47c7f17c90
30 changed files with 5862 additions and 1295 deletions
+77 -10
View File
@@ -140,6 +140,56 @@
from { opacity: 0; transform: translateY(-5px); }
to { opacity: 1; transform: translateY(0); }
}
/* Иммерсивные эффекты для ленты */
.asi-effect {
position: relative;
overflow: hidden;
}
.asi-effect::before {
content: attr(data-effect-text);
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
font-size: 2.5rem;
font-weight: 900;
color: currentColor;
opacity: 0.06;
pointer-events: none;
white-space: nowrap;
line-height: 1;
}
.asi-effect-glow {
box-shadow: 0 0 15px 2px rgba(255, 200, 50, 0.15), inset 0 0 20px rgba(255, 200, 50, 0.05);
animation: glowPulse 2s ease-in-out infinite;
}
@keyframes glowPulse {
0%,100% { box-shadow: 0 0 15px 2px rgba(255,200,50,0.15), inset 0 0 20px rgba(255,200,50,0.05); }
50% { box-shadow: 0 0 25px 5px rgba(255,200,50,0.25), inset 0 0 30px rgba(255,200,50,0.08); }
}
.asi-effect-shimmer {
position: absolute;
top: 0; left: -100%;
width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.07), transparent);
animation: shimmer 3s ease-in-out infinite;
pointer-events: none;
}
@keyframes shimmer {
0% { left: -100%; }
50% { left: 200%; }
100% { left: -100%; }
}
.asi-effect-badge {
display: inline-block;
font-size: 0.6rem;
font-weight: 700;
padding: 0.1rem 0.4rem;
border-radius: 4px;
margin-top: 0.15rem;
}
.asi-effect-badge.upgrade { background: rgba(139,92,246,0.25); color: #a78bfa; }
.asi-effect-badge.drop { background: rgba(251,191,36,0.25); color: #fbbf24; }
.has-activity-sidebar {
padding-left: 260px;
transition: padding-left 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
@@ -157,9 +207,6 @@
<div class="activity-sidebar" id="activitySidebar">
<div class="activity-sidebar-header">
<h3><span class="live-dot"></span> Лента</h3>
<span style="font-size:0.7rem;color:var(--text-secondary);display:flex;align-items:center;gap:0.25rem;">
<span id="onlineCount">0</span> 👤
</span>
<button class="activity-sidebar-close" onclick="toggleActivitySidebar()" title="Скрыть"></button>
</div>
<div class="activity-sidebar-list" id="activitySidebarList">
@@ -198,11 +245,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
WS.on('online_count', (data) => {
const el = document.getElementById('onlineCount');
if (el) el.textContent = data.online || 0;
});
WS.on('activity', (data) => {
const act = data.activity;
if (!act || !list) return;
@@ -241,8 +283,32 @@ function createActivityItem(act) {
const raritySlug = (act.data && act.data.rarity || '').toLowerCase().replace(/ /g, '-').replace(/_/g, '-').replace(/™/g, '');
const rarityColor = ACTIVITY_RARITY_COLORS[raritySlug] || '';
if (rarityColor) {
item.style.background = `rgba(${hexToRgb(rarityColor)}, 0.08)`;
// ── Эффект ──
const effect = act.data && act.data.effect;
let effectBadgeHtml = '';
if (effect) {
const isUpgrade = effect.startsWith('upgrade:');
const isDrop = effect.startsWith('drop:');
const label = effect.replace('upgrade:', '').replace('drop:', '');
const cls = isUpgrade ? 'upgrade' : 'drop';
effectBadgeHtml = `<span class="asi-effect-badge ${cls}">${label}</span>`;
item.classList.add('asi-effect');
item.dataset.effectText = label;
if (isDrop) {
item.classList.add('asi-effect-glow');
const sh = document.createElement('div');
sh.className = 'asi-effect-shimmer';
item.appendChild(sh);
}
if (rarityColor) {
item.style.background = `rgba(${hexToRgb(rarityColor)}, 0.12)`;
item.style.borderLeft = `3px solid ${rarityColor}`;
}
} else {
if (rarityColor) {
item.style.background = `rgba(${hexToRgb(rarityColor)}, 0.08)`;
}
}
item.addEventListener('click', () => window.location.href = `/profiles/${act.user_id}`);
@@ -260,6 +326,7 @@ function createActivityItem(act) {
<span class="asi-username">${escapeHtml(act.username)}</span>
<span>${escapeHtml(act.message)}</span>
<div class="asi-time">${timeStr}</div>
${effectBadgeHtml}
</div>
${imgHtml}
`;