1508 lines
72 KiB
Vue
1508 lines
72 KiB
Vue
<script setup lang="ts">
|
|
import { usePage, Link, router } from '@inertiajs/vue3';
|
|
import { ref, onMounted, nextTick, computed, watch, onUnmounted } from 'vue';
|
|
import { useNotifications } from '@/composables/useNotifications';
|
|
import { usePrimaryColor } from '@/composables/usePrimaryColor';
|
|
import { setLocale as setI18nLocale } from '@/i18n';
|
|
import { initializeApiUrl } from '@/utils/api'; // Import helper
|
|
import { csrfFetch } from '@/utils/csrfFetch';
|
|
import AuthModals from '../../components/auth/AuthModals.vue';
|
|
import GlobalChat from '../../components/chat/GlobalChat.vue';
|
|
import SupportChat from '../../components/support/SupportChat.vue';
|
|
import AppLoading from '../../components/ui/AppLoading.vue';
|
|
import Footer from '../../components/ui/Footer.vue';
|
|
import SearchModal from '../../components/ui/SearchModal.vue';
|
|
import Notification from '../../components/ui/Notification.vue';
|
|
import VaultModal from '../../components/vault/VaultModal.vue';
|
|
|
|
const page = usePage();
|
|
const user = computed(() => page.props.auth.user);
|
|
const stats = computed(() => user.value?.stats || {});
|
|
|
|
// Initialize API URL globally
|
|
onMounted(() => {
|
|
const apiUrlFromProps = (page.props as any).api_url;
|
|
initializeApiUrl(apiUrlFromProps);
|
|
|
|
if (!(window as any).lucide) {
|
|
const script = document.createElement('script');
|
|
script.src = "https://unpkg.com/lucide@latest";
|
|
script.onload = () => { (window as any).lucide.createIcons(); };
|
|
document.head.appendChild(script);
|
|
} else {
|
|
(window as any).lucide.createIcons();
|
|
}
|
|
document.addEventListener('click', handleClickOutside);
|
|
document.addEventListener('keydown', handleKeydown);
|
|
document.addEventListener('keydown', handleGlobalKeydown);
|
|
|
|
// Listen for events from GamePlay to control sidebar state
|
|
document.addEventListener('collapse-sidebar', forceCollapseSidebar);
|
|
document.addEventListener('expand-sidebar', forceExpandSidebar);
|
|
});
|
|
|
|
// --- VIP Logic ---
|
|
const vipLevelsConfig = [
|
|
{ name: 'Newbie', color: '#888888', class: 'rank-newbie' },
|
|
{ name: 'Bronze', color: '#cd7f32', class: 'rank-bronze' },
|
|
{ name: 'Silver', color: '#c0c0c0', class: 'rank-silver' },
|
|
{ name: 'Gold', color: '#ffd700', class: 'rank-gold' },
|
|
{ name: 'Platinum', color: '#00f2ff', class: 'rank-platinum' },
|
|
{ name: 'Diamond', color: 'var(--primary)', class: 'rank-diamond' },
|
|
{ name: 'Obsidian', color: '#ff3e3e', class: 'rank-obsidian' }
|
|
];
|
|
|
|
const vipLevel = computed(() => {
|
|
if (user.value?.vip_level !== undefined) return parseInt(user.value.vip_level);
|
|
if (stats.value.vip_level !== undefined) return parseInt(stats.value.vip_level);
|
|
return 0;
|
|
});
|
|
|
|
const currentVipStyle = computed(() => {
|
|
const idx = Math.min(Math.max(vipLevel.value, 0), vipLevelsConfig.length - 1);
|
|
return vipLevelsConfig[idx];
|
|
});
|
|
|
|
const vipPoints = computed(() => parseFloat(stats.value.vip_points || 0));
|
|
const vipProgress = computed(() => {
|
|
const points = vipPoints.value;
|
|
const pointsPerLevel = 1000;
|
|
const currentLevelPoints = points % pointsPerLevel;
|
|
return (currentLevelPoints / pointsPerLevel) * 100;
|
|
});
|
|
|
|
// --- Sidebar Logic ---
|
|
const isSidebarOpen = ref(false);
|
|
const isSidebarCollapsed = ref(false);
|
|
|
|
const toggleMenu = () => { isSidebarOpen.value = !isSidebarOpen.value; };
|
|
const toggleSidebar = () => { isSidebarCollapsed.value = !isSidebarCollapsed.value; };
|
|
|
|
const forceCollapseSidebar = () => { isSidebarCollapsed.value = true; };
|
|
const forceExpandSidebar = () => { isSidebarCollapsed.value = false; };
|
|
|
|
// --- Auth Modal Logic ---
|
|
const showLoginModal = ref(false);
|
|
const showRegisterModal = ref(false);
|
|
|
|
const openLogin = () => {
|
|
showLoginModal.value = true;
|
|
showRegisterModal.value = false;
|
|
};
|
|
|
|
const openRegister = () => {
|
|
showRegisterModal.value = true;
|
|
showLoginModal.value = false;
|
|
};
|
|
|
|
const closeAuthModals = () => {
|
|
showLoginModal.value = false;
|
|
showRegisterModal.value = false;
|
|
};
|
|
|
|
const handleAuthSwitch = (type: 'login' | 'register') => {
|
|
if (type === 'login') openLogin();
|
|
else openRegister();
|
|
};
|
|
|
|
// Global event listeners for auth modals
|
|
onMounted(() => {
|
|
window.addEventListener('require-login', openLogin);
|
|
window.addEventListener('require-register', openRegister);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('require-login', openLogin);
|
|
window.removeEventListener('require-register', openRegister);
|
|
});
|
|
|
|
// --- Wallet Logic ---
|
|
const isBalanceAnimatingOut = ref(false);
|
|
const isBalanceAnimatingIn = ref(false);
|
|
const selectedCurrencyCode = ref('BTX');
|
|
|
|
const CURRENCY_META: Record<string, { name: string; icon: string; color: string }> = {
|
|
'BTC': { name: 'Bitcoin', icon: 'bitcoin', color: '#f7931a' },
|
|
'ETH': { name: 'Ethereum', icon: 'coins', color: '#627eea' },
|
|
'LTC': { name: 'Litecoin', icon: 'coins', color: '#345d9d' },
|
|
'SOL': { name: 'Solana', icon: 'layers', color: '#00ff9d' },
|
|
'USDT_TRC20': { name: 'Tether (TRC20)', icon: 'circle-dollar-sign', color: '#26a17b' },
|
|
'USDT_ERC20': { name: 'Tether (ERC20)', icon: 'circle-dollar-sign', color: '#26a17b' },
|
|
'XRP': { name: 'Ripple', icon: 'zap', color: '#23292f' },
|
|
'DOGE': { name: 'Dogecoin', icon: 'bone', color: '#c2a633' },
|
|
'ADA': { name: 'Cardano', icon: 'coins', color: '#0033ad' },
|
|
'TRX': { name: 'Tron', icon: 'gem', color: '#ff060a' },
|
|
'BNB': { name: 'Binance Coin', icon: 'circle-dot', color: '#f3ba2f' },
|
|
'MATIC': { name: 'Polygon', icon: 'hexagon', color: '#8247e5' },
|
|
'BCH': { name: 'Bitcoin Cash', icon: 'bitcoin', color: '#8dc351' },
|
|
'SHIB': { name: 'Shiba Inu', icon: 'paw-print', color: '#e1b303' },
|
|
'DOT': { name: 'Polkadot', icon: 'waypoints', color: '#e6007a' },
|
|
'AVAX': { name: 'Avalanche', icon: 'triangle', color: '#e84142' },
|
|
'BTX': { name: 'BetiX Coin', icon: 'gem', color: 'var(--primary)' },
|
|
};
|
|
|
|
// Currencies loaded from NowPayments (enabled list) or fallback
|
|
const enabledCurrencies = ref<string[]>([]);
|
|
|
|
async function loadWalletCurrencies() {
|
|
try {
|
|
const resp = await fetch('/wallet/deposits/currencies', {
|
|
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
|
|
});
|
|
if (!resp.ok) throw new Error('failed');
|
|
const json = await resp.json();
|
|
if (Array.isArray(json.enabled) && json.enabled.length) {
|
|
enabledCurrencies.value = json.enabled;
|
|
}
|
|
} catch {
|
|
// keep empty — wallets computed falls back to BTC/ETH/SOL
|
|
}
|
|
}
|
|
|
|
const wallets = computed(() => {
|
|
const cryptos = enabledCurrencies.value.length
|
|
? enabledCurrencies.value
|
|
: ['BTC', 'ETH', 'SOL'];
|
|
|
|
const list = cryptos.map((ticker: string) => {
|
|
const meta = CURRENCY_META[ticker] || { name: ticker, icon: 'badge-cent', color: '#a1a1aa' };
|
|
return { currency: ticker, name: meta.name, amount: 0, icon: meta.icon, color: meta.color };
|
|
});
|
|
|
|
// BTX always at the end
|
|
const btxMeta = CURRENCY_META['BTX'];
|
|
list.push({ currency: 'BTX', name: btxMeta.name, amount: parseFloat(user.value?.balance || '0'), icon: btxMeta.icon, color: btxMeta.color });
|
|
|
|
// Update amounts from user.wallets if available
|
|
if (user.value?.wallets) {
|
|
user.value.wallets.forEach((w: any) => {
|
|
const item = list.find((l: any) => l.currency === w.currency);
|
|
if (item) item.amount = parseFloat(w.balance);
|
|
});
|
|
}
|
|
return list;
|
|
});
|
|
|
|
const currentWallet = computed(() => {
|
|
return wallets.value.find(w => w.currency === selectedCurrencyCode.value)
|
|
|| wallets.value.find(w => w.currency === 'BTX')
|
|
|| wallets.value[0];
|
|
});
|
|
|
|
onMounted(() => {
|
|
const btx = wallets.value.find(w => w.currency === 'BTX');
|
|
if (!btx || btx.amount === 0) {
|
|
const highest = wallets.value.reduce((prev, current) => (prev.amount > current.amount) ? prev : current);
|
|
if (highest.amount > 0) {
|
|
selectedCurrencyCode.value = highest.currency;
|
|
}
|
|
}
|
|
});
|
|
|
|
const selectCurrency = (code: string) => {
|
|
if (selectedCurrencyCode.value === code) return;
|
|
isBalanceAnimatingOut.value = true;
|
|
setTimeout(() => {
|
|
selectedCurrencyCode.value = code;
|
|
isBalanceAnimatingOut.value = false;
|
|
isBalanceAnimatingIn.value = true;
|
|
nextTick(() => { if ((window as any).lucide) (window as any).lucide.createIcons(); });
|
|
setTimeout(() => { isBalanceAnimatingIn.value = false; }, 300);
|
|
}, 200);
|
|
};
|
|
|
|
// --- Notifications & Dropdowns ---
|
|
const { toasts, history, closeToast, markAllRead, clearAll } = useNotifications();
|
|
const showNotifDropdown = ref(false);
|
|
const isProfileOpen = ref(false);
|
|
const isLangOpen = ref(false);
|
|
|
|
// Primary color control
|
|
const { primaryColor, updatePrimaryColor } = usePrimaryColor();
|
|
const onPrimaryColorInput = (e: Event) => {
|
|
const value = (e.target as HTMLInputElement).value;
|
|
updatePrimaryColor(value);
|
|
};
|
|
|
|
// Ensure primary color is set
|
|
onMounted(() => {
|
|
if (!primaryColor.value || primaryColor.value === '#ff007a') {
|
|
updatePrimaryColor('#DF006A');
|
|
}
|
|
});
|
|
|
|
// --- Dark / Light Mode ---
|
|
const isLightMode = ref(false);
|
|
|
|
function applyTheme(light: boolean) {
|
|
document.documentElement.setAttribute('data-theme', light ? 'light' : 'dark');
|
|
}
|
|
|
|
function toggleTheme() {
|
|
isLightMode.value = !isLightMode.value;
|
|
try { localStorage.setItem('casino-theme', isLightMode.value ? 'light' : 'dark'); } catch {}
|
|
applyTheme(isLightMode.value);
|
|
}
|
|
|
|
onMounted(() => {
|
|
try {
|
|
const saved = localStorage.getItem('casino-theme');
|
|
if (saved === 'light') {
|
|
isLightMode.value = true;
|
|
applyTheme(true);
|
|
}
|
|
} catch {}
|
|
});
|
|
|
|
// Server notifications (database) shared via Inertia
|
|
const serverNotifications = computed(() => (page.props as any).serverNotifications || []);
|
|
const serverUnreadCount = computed(() => (page.props as any).serverUnreadCount || 0);
|
|
|
|
function acceptFriend(id: number) {
|
|
router.post(`/friends/${id}/accept`, {}, {
|
|
preserveScroll: true,
|
|
onSuccess: () => router.reload({ only: ['auth'] })
|
|
});
|
|
}
|
|
function declineFriend(id: number) {
|
|
router.post(`/friends/${id}/decline`, {}, {
|
|
preserveScroll: true,
|
|
onSuccess: () => router.reload({ only: ['auth'] })
|
|
});
|
|
}
|
|
|
|
// Refs for click-outside detection
|
|
const notifWrapper = ref<HTMLElement | null>(null);
|
|
const profileWrapper = ref<HTMLElement | null>(null);
|
|
const langWrapper = ref<HTMLElement | null>(null);
|
|
const walletWrapperRef = ref<HTMLElement | null>(null);
|
|
const walletMenuOpen = ref(false);
|
|
|
|
const toggleNotifDropdown = () => {
|
|
showNotifDropdown.value = !showNotifDropdown.value;
|
|
if (showNotifDropdown.value) markAllRead();
|
|
};
|
|
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
const target = event.target as Node;
|
|
if (notifWrapper.value && !notifWrapper.value.contains(target)) showNotifDropdown.value = false;
|
|
if (profileWrapper.value && !profileWrapper.value.contains(target)) isProfileOpen.value = false;
|
|
if (langWrapper.value && !langWrapper.value.contains(target)) isLangOpen.value = false;
|
|
if (walletWrapperRef.value && !walletWrapperRef.value.contains(target)) walletMenuOpen.value = false;
|
|
};
|
|
|
|
onUnmounted(() => {
|
|
document.removeEventListener('click', handleClickOutside);
|
|
document.removeEventListener('keydown', handleGlobalKeydown);
|
|
document.removeEventListener('collapse-sidebar', forceCollapseSidebar);
|
|
document.removeEventListener('expand-sidebar', forceExpandSidebar);
|
|
});
|
|
|
|
// Global Chat & Vault
|
|
const isChatOpen = ref(false);
|
|
const chatUnread = ref(0);
|
|
const isVaultOpen = ref(false);
|
|
|
|
// --- Search Modal ---
|
|
const isSearchOpen = ref(false);
|
|
|
|
const handleGlobalKeydown = (e: KeyboardEvent) => {
|
|
// Ctrl+K or Cmd+K to open search
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
e.preventDefault();
|
|
isSearchOpen.value = true;
|
|
}
|
|
};
|
|
|
|
// --- Locale switcher ---
|
|
const availableLocales = computed(() => (page.props as any).availableLocales || []);
|
|
const currentLocale = computed(() => (page.props as any).locale || 'en');
|
|
|
|
async function changeLocale(code: string) {
|
|
try {
|
|
await csrfFetch('/locale', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
body: JSON.stringify({ locale: code })
|
|
});
|
|
await setI18nLocale(code);
|
|
router.reload({ only: ['locale'] });
|
|
isLangOpen.value = false;
|
|
} catch {}
|
|
}
|
|
|
|
// --- Nav Categories ---
|
|
const navCategories = ref({
|
|
gaming: true,
|
|
user: true,
|
|
support: true
|
|
});
|
|
|
|
const toggleCategory = (cat: keyof typeof navCategories.value) => {
|
|
navCategories.value[cat] = !navCategories.value[cat];
|
|
};
|
|
|
|
const logout = () => {
|
|
const postUrl = typeof (window as any).route === 'function' ? (window as any).route('logout') : '/logout';
|
|
router.post(postUrl);
|
|
};
|
|
|
|
// --- REAL MODE BALANCE SYNC ---
|
|
const balanceInterval = ref<any>(null);
|
|
|
|
const fetchBalance = async () => {
|
|
if (!user.value) return;
|
|
try {
|
|
const response = await fetch('/api/wallet/balance', {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
});
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (data.balance !== undefined && user.value.balance !== data.balance) {
|
|
user.value.balance = data.balance;
|
|
}
|
|
if (data.wallets && user.value.wallets) {
|
|
user.value.wallets = data.wallets;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Balance sync failed:', e);
|
|
}
|
|
};
|
|
|
|
// Adaptive polling: 5 s on game pages, 30 s elsewhere
|
|
function getPollingInterval(): number {
|
|
return window.location.pathname.startsWith('/games/play/') ? 5000 : 30000;
|
|
}
|
|
|
|
function restartBalanceInterval() {
|
|
if (balanceInterval.value) clearInterval(balanceInterval.value);
|
|
balanceInterval.value = setInterval(fetchBalance, getPollingInterval());
|
|
}
|
|
|
|
// Immediate refresh triggered by game iframe / round webhook signal
|
|
function onBalanceRefresh() {
|
|
fetchBalance();
|
|
restartBalanceInterval();
|
|
}
|
|
|
|
onMounted(() => {
|
|
restartBalanceInterval();
|
|
fetchBalance();
|
|
loadWalletCurrencies();
|
|
document.addEventListener('balance:refresh', onBalanceRefresh);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (balanceInterval.value) clearInterval(balanceInterval.value);
|
|
document.removeEventListener('balance:refresh', onBalanceRefresh);
|
|
});
|
|
|
|
watch(() => currentWallet.value.icon, () => {
|
|
nextTick(() => { if ((window as any).lucide) (window as any).lucide.createIcons(); });
|
|
});
|
|
|
|
// Mobile: close sidebar with ESC and prevent background scroll when open
|
|
function handleKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape' && isSidebarOpen.value) {
|
|
isSidebarOpen.value = false;
|
|
}
|
|
}
|
|
|
|
watch(isSidebarOpen, (open) => {
|
|
try {
|
|
const isMobile = window.matchMedia('(max-width: 1000px)').matches;
|
|
if (isMobile) {
|
|
document.body.style.overflow = open ? 'hidden' : '';
|
|
}
|
|
} catch {}
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
document.removeEventListener('keydown', handleKeydown);
|
|
try { document.body.style.overflow = ''; } catch {}
|
|
});
|
|
|
|
// Prevent clicks inside the sidebar from bubbling to document
|
|
function onSidebarClick(e: MouseEvent) {
|
|
e.stopPropagation();
|
|
}
|
|
|
|
// Auto-close sidebar on route change
|
|
watch(() => page.url, () => {
|
|
if (isSidebarOpen.value) isSidebarOpen.value = false;
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="app-container">
|
|
<AppLoading />
|
|
<Notification :toasts="toasts" @close="closeToast" />
|
|
|
|
<div class="app">
|
|
<!-- SIDEBAR -->
|
|
<aside :class="['sidebar', { open: isSidebarOpen, collapsed: isSidebarCollapsed }]" id="sidebar" @click="onSidebarClick">
|
|
<div class="sidebar-header">
|
|
<div class="brand">Beti<span>X</span></div>
|
|
<button class="collapse-btn" @click="toggleSidebar">
|
|
<i data-lucide="panel-left"></i>
|
|
</button>
|
|
<button class="close-mobile" @click="isSidebarOpen = false">
|
|
<i data-lucide="x"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Profile Card -->
|
|
<div class="profile-card" v-if="user" :class="[currentVipStyle.class, user.role === 'admin' && 'role-admin']">
|
|
<div class="pc-bg">
|
|
<div class="pc-inner">
|
|
<Link :href="`/social/${user.username}`" class="pc-avatar-wrap">
|
|
<div class="pc-avatar">
|
|
<img v-if="user.avatar || user.avatar_url"
|
|
:src="user.avatar || user.avatar_url"
|
|
alt="Avatar"
|
|
@error="($event.target as HTMLImageElement).style.display = 'none'">
|
|
<span v-else>{{ (user.username || user.name || '?').charAt(0).toUpperCase() }}</span>
|
|
</div>
|
|
<span class="pc-status-dot" :style="{ background: currentVipStyle.color }"></span>
|
|
<span v-if="user.role === 'admin'" class="pc-crown"><i data-lucide="crown"></i></span>
|
|
</Link>
|
|
<div class="pc-info">
|
|
<Link :href="`/social/${user.username}`" class="pc-name">{{ user.username || user.name }}</Link>
|
|
<div class="pc-meta">
|
|
<span v-if="user.role === 'admin'" class="pc-badge-admin"><i data-lucide="crown"></i> Admin</span>
|
|
<span class="pc-badge-vip" :style="{ color: currentVipStyle.color }">
|
|
<span class="pc-dot" :style="{ background: currentVipStyle.color }"></span>
|
|
{{ currentVipStyle.name }}
|
|
</span>
|
|
</div>
|
|
<div class="pc-progress">
|
|
<div class="pc-track"><div class="pc-fill" :style="{ width: `${vipProgress}%` }"></div></div>
|
|
<div class="pc-xp">LVL {{ vipLevel }} · {{ Math.floor(vipPoints % 1000) }}/1000 XP</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Guest Card -->
|
|
<div class="profile-card guest" v-else>
|
|
<div class="pc-bg">
|
|
<div class="pc-inner">
|
|
<div class="pc-avatar-wrap" style="pointer-events:none">
|
|
<div class="pc-avatar pc-avatar--guest"><i data-lucide="user"></i></div>
|
|
</div>
|
|
<div class="pc-info">
|
|
<span class="pc-name" style="color:#555">{{ $t('common.guest') }}</span>
|
|
<div class="pc-guest-btns">
|
|
<button class="pca-guest-login" @click="openLogin">{{ $t('auth.login') }}</button>
|
|
<button class="pca-guest-reg" @click="openRegister">{{ $t('auth.register') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Link href="/wallet" class="sidebar-deposit-btn">
|
|
<div class="btn-content">
|
|
<i data-lucide="plus-circle"></i>
|
|
<span class="btn-text">{{ $t('topbar.deposit') }}</span>
|
|
</div>
|
|
<div class="btn-shine"></div>
|
|
</Link>
|
|
|
|
<nav class="nav-menu">
|
|
<!-- Gaming -->
|
|
<div class="nav-group">
|
|
<div class="nav-head" @click="toggleCategory('gaming')">
|
|
<span class="nav-cat-title">{{ $t('sections.gaming') }}</span>
|
|
<i data-lucide="chevron-down" :class="{ rotated: !navCategories.gaming }"></i>
|
|
</div>
|
|
<div class="nav-items" :class="{ collapsed: !navCategories.gaming }">
|
|
<Link href="/dashboard" :class="{ active: $page.url === '/dashboard' }">
|
|
<div class="nav-icon"><i data-lucide="layout"></i></div>
|
|
<span class="nav-text">{{ $t('nav.lobby') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
<Link href="/dashboard" :class="{ active: $page.url === '/live' }">
|
|
<div class="nav-icon"><i data-lucide="clapperboard"></i></div>
|
|
<span class="nav-text">{{ $t('nav.liveCasino') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
<Link href="/dashboard" :class="{ active: $page.url === '/slots' }">
|
|
<div class="nav-icon"><i data-lucide="disc"></i></div>
|
|
<span class="nav-text">{{ $t('nav.slots') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User -->
|
|
<div class="nav-group">
|
|
<div class="nav-head" @click="toggleCategory('user')">
|
|
<span class="nav-cat-title">{{ $t('sections.user') }}</span>
|
|
<i data-lucide="chevron-down" :class="{ rotated: !navCategories.user }"></i>
|
|
</div>
|
|
<div class="nav-items" :class="{ collapsed: !navCategories.user }">
|
|
<Link href="/bonuses" :class="{ active: $page.url === '/bonuses' }">
|
|
<div class="nav-icon"><i data-lucide="gift"></i></div>
|
|
<span class="nav-text">{{ $t('nav.bonuses') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
<Link href="/wallet" :class="{ active: $page.url === '/wallet' }">
|
|
<div class="nav-icon"><i data-lucide="wallet"></i></div>
|
|
<span class="nav-text">{{ $t('nav.wallet') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
<Link href="/vip-levels" :class="{ active: $page.url === '/vip-levels' }">
|
|
<div class="nav-icon"><i data-lucide="crown"></i></div>
|
|
<span class="nav-text">{{ $t('nav.vip') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
<Link href="/guilds" :class="{ active: $page.url.startsWith('/guilds') }">
|
|
<div class="nav-icon"><i data-lucide="shield"></i></div>
|
|
<span class="nav-text">{{ $t('nav.guilds') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
<Link href="/social" :class="{ active: $page.url === '/social' }">
|
|
<div class="nav-icon"><i data-lucide="users"></i></div>
|
|
<span class="nav-text">Social</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
<Link href="/trophy" :class="{ active: $page.url === '/trophy' }">
|
|
<div class="nav-icon"><i data-lucide="trophy"></i></div>
|
|
<span class="nav-text">Trophy Room</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Support -->
|
|
<div class="nav-group">
|
|
<div class="nav-head" @click="toggleCategory('support')">
|
|
<span class="nav-cat-title">{{ $t('sections.supportInfo') }}</span>
|
|
<i data-lucide="chevron-down" :class="{ rotated: !navCategories.support }"></i>
|
|
</div>
|
|
<div class="nav-items" :class="{ collapsed: !navCategories.support }">
|
|
<Link href="/faq" :class="{ active: $page.url === '/faq' }">
|
|
<div class="nav-icon"><i data-lucide="help-circle"></i></div>
|
|
<span class="nav-text">{{ $t('nav.faq') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
<Link href="/self-exclusion" :class="{ active: $page.url === '/self-exclusion' }">
|
|
<div class="nav-icon"><i data-lucide="alert-octagon"></i></div>
|
|
<span class="nav-text">{{ $t('nav.responsible') }}</span>
|
|
<div class="active-glow"></div>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
|
|
</aside>
|
|
|
|
<div v-if="isSidebarOpen" class="sidebar-backdrop" @click="isSidebarOpen = false"></div>
|
|
|
|
<main class="main" :class="{ expanded: isSidebarCollapsed }">
|
|
<header class="topbar">
|
|
<div class="tb-left">
|
|
<div class="mobile-toggle" @click="toggleMenu"><i data-lucide="menu"></i></div>
|
|
<button class="header-search-bar" @click="isSearchOpen = true" aria-label="Suche öffnen">
|
|
<i data-lucide="search" class="hsr-icon"></i>
|
|
<span class="hsr-text">Spiele, Provider, @User suchen...</span>
|
|
<kbd class="hsr-kbd">⌘K</kbd>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Centered Wallet Display -->
|
|
<div class="wallet-wrapper" ref="walletWrapperRef" :class="{ open: walletMenuOpen }" @mouseenter="walletMenuOpen = true" @mouseleave="walletMenuOpen = false">
|
|
<div class="wallet-pill" id="wallet-btn" @click.stop="walletMenuOpen = !walletMenuOpen" role="button" tabindex="0" aria-haspopup="true" :aria-expanded="walletMenuOpen">
|
|
<div class="wp-icon-box" :style="{ borderColor: currentWallet.color, boxShadow: `0 0 10px ${currentWallet.color}40` }">
|
|
<i :data-lucide="currentWallet.icon" :style="{ color: currentWallet.color }"></i>
|
|
</div>
|
|
<div class="wp-info">
|
|
<span id="balance-display" :class="{ 'anim-out': isBalanceAnimatingOut, 'anim-in': isBalanceAnimatingIn }">
|
|
{{ currentWallet.amount.toFixed(4) }}
|
|
</span>
|
|
</div>
|
|
<div class="wp-actions">
|
|
<button class="wp-btn vault" @click="isVaultOpen = true" title="Vault">
|
|
<i data-lucide="lock"></i>
|
|
</button>
|
|
<button class="wp-btn deposit" @click="$inertia.visit('/wallet')" title="Deposit">
|
|
<i data-lucide="plus"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Currency Dropdown -->
|
|
<div class="wallet-dropdown">
|
|
<div
|
|
v-for="coin in wallets"
|
|
:key="coin.currency"
|
|
class="wd-item"
|
|
@click="selectCurrency(coin.currency)"
|
|
:class="{ active: selectedCurrencyCode === coin.currency }"
|
|
>
|
|
<div class="wd-left">
|
|
<i :data-lucide="coin.icon" :style="{ color: coin.color }"></i>
|
|
<span>{{ coin.name }}</span>
|
|
</div>
|
|
<div class="wd-right">{{ coin.amount.toFixed(4) }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="h-actions">
|
|
<SearchModal v-if="isSearchOpen" @close="isSearchOpen = false" />
|
|
|
|
<!-- Language Switcher -->
|
|
<div class="lang-wrapper" ref="langWrapper">
|
|
<button class="lang-btn" :class="{ active: isLangOpen }" @click="isLangOpen = !isLangOpen">
|
|
<img :src="(availableLocales.find(l=>l.code===currentLocale)?.flag) || 'https://flagcdn.com/w40/gb.png'" class="flag">
|
|
<i data-lucide="chevron-down" class="lang-arrow"></i>
|
|
</button>
|
|
<transition name="pop-down">
|
|
<div v-if="isLangOpen" class="lang-menu">
|
|
<button v-for="loc in availableLocales" :key="loc.code" class="lang-opt" @click.stop="changeLocale(loc.code)">
|
|
<img :src="loc.flag" class="flag"> {{ loc.label }}
|
|
</button>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
|
|
<button class="h-icon-btn chat-btn" :class="{ 'has-unread': chatUnread > 0 }" title="Global Chat" @click="isChatOpen = true">
|
|
<i data-lucide="message-circle"></i>
|
|
<span v-if="chatUnread > 0" class="chat-badge">{{ chatUnread > 99 ? '99+' : chatUnread }}</span>
|
|
</button>
|
|
|
|
<!-- Notification Bell -->
|
|
<div class="notif-wrapper" ref="notifWrapper">
|
|
<button class="h-icon-btn" @click="toggleNotifDropdown" :class="{ active: showNotifDropdown }">
|
|
<div v-if="serverUnreadCount > 0" class="badge-dot"></div>
|
|
<i data-lucide="bell" :class="{ 'bell-shake': serverUnreadCount > 0 }"></i>
|
|
</button>
|
|
|
|
<transition name="pop-down">
|
|
<div v-if="showNotifDropdown" class="notif-dropdown">
|
|
<div class="nd-head">
|
|
<span>Notifications</span>
|
|
<!-- keep client clear for toasts history; server notifications are persisted -->
|
|
<button class="nd-clear" @click="clearAll" v-if="history.length">Clear</button>
|
|
</div>
|
|
<div class="nd-list">
|
|
<div v-if="!serverNotifications.length" class="nd-empty">No new notifications</div>
|
|
<div v-for="n in serverNotifications" :key="n.id" class="nd-item" :class="{ unread: !n.read }">
|
|
<div class="nd-icon-box"><i :data-lucide="n.icon || 'bell'"></i></div>
|
|
<div class="nd-content" style="flex:1;">
|
|
<div class="nd-title">{{ n.title }}</div>
|
|
<div class="nd-desc">{{ n.desc }}</div>
|
|
<div class="nd-time">{{ new Date(n.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) }}</div>
|
|
</div>
|
|
<div v-if="n.kind === 'friend_request' && n.friend_request_id" class="nd-actions" style="display:flex; gap:6px; align-items:center;">
|
|
<button class="h-icon-btn" title="Accept" @click.stop="acceptFriend(n.friend_request_id)"><i data-lucide="check"></i></button>
|
|
<button class="h-icon-btn" title="Decline" @click.stop="declineFriend(n.friend_request_id)"><i data-lucide="x"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
|
|
<!-- Profile Dropdown -->
|
|
<div class="profile-wrapper" ref="profileWrapper" v-if="user">
|
|
<button class="user-avatar-btn"
|
|
@click="isProfileOpen = !isProfileOpen"
|
|
:class="['role-' + (user.role || 'user'), { active: isProfileOpen }]"
|
|
:title="user.username || user.name">
|
|
<img v-if="user.avatar || user.avatar_url" :src="user.avatar || user.avatar_url" alt="User">
|
|
<span v-else>{{ (user.username || user.name).charAt(0).toUpperCase() }}</span>
|
|
<span v-if="user.role === 'admin'" class="uab-role-dot uab-role-dot--admin">
|
|
<i data-lucide="crown"></i>
|
|
</span>
|
|
<span v-else-if="user.role === 'mod'" class="uab-role-dot uab-role-dot--mod">
|
|
<i data-lucide="shield"></i>
|
|
</span>
|
|
</button>
|
|
|
|
<transition name="pop-down">
|
|
<div v-if="isProfileOpen" class="profile-dropdown">
|
|
<div class="pd-header">
|
|
<div class="pd-name">{{ user.username || user.name }}</div>
|
|
<div class="pd-email">{{ user.email }}</div>
|
|
</div>
|
|
<div class="pd-links">
|
|
<Link href="/settings" class="pd-item">
|
|
<i data-lucide="globe"></i> {{ $t('nav.public_profile') }}
|
|
</Link>
|
|
<Link href="/settings/profile" class="pd-item">
|
|
<i data-lucide="user"></i> {{ $t('nav.account_settings') }}
|
|
</Link>
|
|
<Link href="/wallet" class="pd-item">
|
|
<i data-lucide="wallet"></i> {{ $t('nav.wallet') }}
|
|
</Link>
|
|
<Link href="/self-exclusion" class="pd-item">
|
|
<i data-lucide="shield-alert"></i> {{ $t('nav.responsible') }}
|
|
</Link>
|
|
</div>
|
|
<div class="pd-theme">
|
|
<div class="pd-theme-row">
|
|
<i data-lucide="palette"></i>
|
|
<span>Accent color</span>
|
|
<input class="pd-color" type="color" :value="primaryColor" @input="onPrimaryColorInput" />
|
|
</div>
|
|
<div class="pd-theme-row">
|
|
<i :data-lucide="isLightMode ? 'sun' : 'moon'"></i>
|
|
<span>{{ isLightMode ? 'Light Mode' : 'Dark Mode' }}</span>
|
|
<button class="theme-toggle-btn" :class="{ light: isLightMode }" @click="toggleTheme" :title="isLightMode ? 'Switch to Dark' : 'Switch to Light'">
|
|
<span class="tt-thumb"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="pd-footer">
|
|
<button class="pd-logout" @click="logout">
|
|
<i data-lucide="log-out"></i> {{ $t('auth.logout') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
|
|
<!-- Guest Actions -->
|
|
<div class="guest-actions" v-else>
|
|
<button class="auth-btn login" @click="openLogin">
|
|
{{ $t('auth.login') }}
|
|
</button>
|
|
<button class="auth-btn register" @click="openRegister">
|
|
<span class="btn-text">{{ $t('auth.register') }}</span>
|
|
<div class="btn-glow"></div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="content-wrapper">
|
|
<transition name="page-fade" mode="out-in">
|
|
<div :key="$page.url">
|
|
<slot></slot>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
|
|
<Footer />
|
|
</main>
|
|
</div>
|
|
<GlobalChat v-model:open="isChatOpen" @update:unread="chatUnread = $event" />
|
|
<SupportChat />
|
|
|
|
<!-- Mobile Bottom Navigation -->
|
|
<div class="mobile-nav-bottom">
|
|
<Link href="/dashboard" class="m-nav-item" :class="{ active: $page.url === '/dashboard' }">
|
|
<i data-lucide="layout"></i>
|
|
<span>Lobby</span>
|
|
</Link>
|
|
<Link href="/wallet" class="m-nav-item" :class="{ active: $page.url.startsWith('/wallet') }">
|
|
<i data-lucide="wallet"></i>
|
|
<span>Wallet</span>
|
|
</Link>
|
|
<button class="m-nav-item" @click="toggleMenu">
|
|
<i data-lucide="menu"></i>
|
|
<span>Menu</span>
|
|
</button>
|
|
<Link href="/trophy" class="m-nav-item" :class="{ active: $page.url === '/trophy' }">
|
|
<i data-lucide="trophy"></i>
|
|
<span>Trophies</span>
|
|
</Link>
|
|
<button class="m-nav-item" @click="isChatOpen = !isChatOpen">
|
|
<i data-lucide="message-square"></i>
|
|
<span>Chat</span>
|
|
</button>
|
|
</div>
|
|
|
|
<VaultModal :open="isVaultOpen" :coins="wallets" @close="isVaultOpen = false" />
|
|
<AuthModals
|
|
:show-login="showLoginModal"
|
|
:show-register="showRegisterModal"
|
|
@close="closeAuthModals"
|
|
@switch="handleAuthSwitch"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
:root {
|
|
--bg-deep: #050505;
|
|
--bg-side: #0a0a0a;
|
|
--bg-card: #0f0f0f;
|
|
--border: #1f1f1f;
|
|
--primary: #df006a; /* Unified primary color */
|
|
--primary-foreground: #ffffff;
|
|
--magenta: var(--primary); /* Keep for backwards compatibility */
|
|
--cyan: #00f2ff;
|
|
--text: #ffffff;
|
|
--muted: #888;
|
|
--radius: 16px;
|
|
--ease: cubic-bezier(0.2, 0, 0, 1);
|
|
--vip-color: var(--primary);
|
|
}
|
|
|
|
/* Light Mode */
|
|
[data-theme="light"] {
|
|
--bg-deep: #f0f1f5;
|
|
--bg-side: #ffffff;
|
|
--bg-card: #ffffff;
|
|
--border: #e2e2e7;
|
|
--text: #111111;
|
|
--muted: #555555;
|
|
}
|
|
[data-theme="light"] body { background: var(--bg-deep); color: var(--text); }
|
|
[data-theme="light"] ::-webkit-scrollbar-thumb { background: #ccc; }
|
|
[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--primary); }
|
|
|
|
/* VIP level color variables */
|
|
.rank-newbie { --vip-color: #888888; }
|
|
.rank-bronze { --vip-color: #cd7f32; }
|
|
.rank-silver { --vip-color: #c0c0c0; }
|
|
.rank-gold { --vip-color: #ffd700; }
|
|
.rank-platinum { --vip-color: #00f2ff; }
|
|
.rank-diamond { --vip-color: var(--primary); }
|
|
.rank-obsidian { --vip-color: #ff3e3e; }
|
|
|
|
/* Scrollbar */
|
|
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: #333; border-radius: 99px; }
|
|
::-webkit-scrollbar-thumb:hover { background: var(--primary); }
|
|
|
|
* { box-sizing: border-box; outline: none; -webkit-tap-highlight-color: transparent; }
|
|
body { margin: 0; background: var(--bg-deep); font-family: 'Inter', sans-serif; color: var(--text); overflow-x: hidden; }
|
|
|
|
.app-container { min-height: 100vh; display: flex; flex-direction: column; }
|
|
.app { display: flex; min-height: 100vh; width: 100%; overflow-x: hidden; }
|
|
|
|
/* --- Sidebar --- */
|
|
.sidebar {
|
|
width: 260px; background: var(--bg-side); border-right: 1px solid var(--border);
|
|
position: fixed; top: 0; bottom: 0; left: 0; z-index: 1000;
|
|
display: flex; flex-direction: column; padding: 20px 16px; gap: 20px;
|
|
transition: width 0.3s var(--ease), padding 0.3s, transform 0.3s var(--ease); overflow-y: auto; overflow-x: hidden;
|
|
}
|
|
.sidebar.collapsed { width: 80px; padding: 20px 10px; }
|
|
|
|
.sidebar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
|
|
|
/* Animated Logo */
|
|
.brand {
|
|
font-size: 24px; font-weight: 900; letter-spacing: -1px; cursor: pointer; user-select: none;
|
|
white-space: nowrap;
|
|
}
|
|
.brand span {
|
|
background: linear-gradient(to right, var(--primary) 20%, #fff 50%, var(--primary) 80%);
|
|
background-size: 200% auto;
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
animation: shine 4s linear infinite;
|
|
display: inline-block;
|
|
}
|
|
@keyframes shine { to { background-position: 200% center; } }
|
|
|
|
.sidebar.collapsed .brand { display: none; }
|
|
|
|
.collapse-btn {
|
|
background: transparent; border: none; color: #666; cursor: pointer; transition: 0.2s;
|
|
display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 8px;
|
|
}
|
|
.collapse-btn:hover { color: #fff; background: #1a1a1a; }
|
|
.sidebar.collapsed .collapse-btn { width: 100%; margin-bottom: 10px; }
|
|
.sidebar.collapsed .collapse-btn i { transform: rotate(180deg); }
|
|
|
|
/* ─── Profile Card ─────────────────────────────────── */
|
|
.profile-card {
|
|
border-radius: 13px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
padding: 1.5px;
|
|
background: #111;
|
|
}
|
|
/* Spinning gradient border */
|
|
.profile-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
width: 300%; height: 300%;
|
|
top: 50%; left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: conic-gradient(
|
|
from 0deg,
|
|
transparent 0deg,
|
|
rgba(223,0,106,0.35) 50deg,
|
|
var(--primary) 110deg,
|
|
rgba(223,0,106,0.35) 170deg,
|
|
transparent 230deg,
|
|
transparent 360deg
|
|
);
|
|
animation: pc-border-spin 4s linear infinite;
|
|
}
|
|
.role-admin.profile-card::before {
|
|
background: conic-gradient(
|
|
from 0deg,
|
|
transparent 0deg,
|
|
rgba(255,180,0,0.4) 50deg,
|
|
#ffcc00 110deg,
|
|
rgba(255,180,0,0.4) 170deg,
|
|
transparent 230deg,
|
|
transparent 360deg
|
|
);
|
|
animation-duration: 2.5s;
|
|
}
|
|
.profile-card.guest::before { display: none; }
|
|
@keyframes pc-border-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
|
|
|
|
/* Inner solid background sits above the spinning border */
|
|
.pc-bg {
|
|
position: relative;
|
|
z-index: 1;
|
|
border-radius: 11px;
|
|
background: linear-gradient(145deg, #141414 0%, #0d0d0d 100%);
|
|
padding: 12px 12px 10px;
|
|
}
|
|
.profile-card.guest .pc-bg {
|
|
background: #0f0f0f;
|
|
}
|
|
|
|
/* ── Row: avatar + info ── */
|
|
.pc-inner {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
/* ── Avatar ── */
|
|
.pc-avatar-wrap {
|
|
position: relative;
|
|
width: 46px; height: 46px;
|
|
flex-shrink: 0;
|
|
display: block; text-decoration: none;
|
|
}
|
|
.pc-avatar {
|
|
width: 100%; height: 100%; border-radius: 50%;
|
|
background: #1c1c1c; border: 2px solid #2a2a2a;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 18px; font-weight: 900; color: #fff;
|
|
overflow: hidden; position: relative; z-index: 2;
|
|
}
|
|
.pc-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }
|
|
.pc-avatar--guest { color: #333; font-size: 22px; }
|
|
|
|
/* VIP status dot */
|
|
.pc-status-dot {
|
|
position: absolute; bottom: 1px; right: 1px;
|
|
width: 11px; height: 11px; border-radius: 50%;
|
|
border: 2px solid #0d0d0d;
|
|
z-index: 3;
|
|
}
|
|
/* Admin crown badge on avatar */
|
|
.pc-crown {
|
|
position: absolute; bottom: 0; right: 0; z-index: 4;
|
|
width: 18px; height: 18px; border-radius: 50%;
|
|
background: #ffcc00; border: 2px solid #0d0d0d;
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.pc-crown i { width: 9px; height: 9px; color: #111; }
|
|
|
|
/* ── Info column ── */
|
|
.pc-info {
|
|
flex: 1; min-width: 0;
|
|
display: flex; flex-direction: column; gap: 3px;
|
|
}
|
|
.pc-name {
|
|
font-size: 13px; font-weight: 800; color: #e0e0e0;
|
|
text-decoration: none;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
display: block; line-height: 1.2;
|
|
}
|
|
.pc-name:hover { color: var(--primary); }
|
|
|
|
/* ── Badges row ── */
|
|
.pc-meta { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
|
|
.pc-badge-admin {
|
|
display: inline-flex; align-items: center; gap: 3px;
|
|
background: rgba(255,200,0,0.1); color: #ffcc00;
|
|
border: 1px solid rgba(255,200,0,0.2);
|
|
border-radius: 99px; padding: 1px 6px;
|
|
font-size: 9px; font-weight: 900; text-transform: uppercase; letter-spacing: 0.5px;
|
|
}
|
|
.pc-badge-admin i { width: 8px; height: 8px; }
|
|
.pc-badge-vip {
|
|
display: inline-flex; align-items: center; gap: 4px;
|
|
font-size: 9px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.5px;
|
|
}
|
|
.pc-dot { width: 5px; height: 5px; border-radius: 50%; flex-shrink: 0; }
|
|
|
|
/* ── XP Progress ── */
|
|
.pc-progress { display: flex; flex-direction: column; gap: 3px; }
|
|
.pc-track { height: 3px; background: rgba(255,255,255,0.06); border-radius: 99px; overflow: hidden; }
|
|
.pc-fill {
|
|
height: 100%; border-radius: 99px;
|
|
background: var(--vip-color);
|
|
box-shadow: 0 0 6px var(--vip-color);
|
|
transition: width 1.4s cubic-bezier(0.22,1,0.36,1);
|
|
min-width: 3px;
|
|
}
|
|
.pc-xp { font-size: 9px; color: #484848; font-weight: 700; letter-spacing: 0.2px; }
|
|
|
|
/* ── Action buttons ── */
|
|
.pc-actions {
|
|
display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
|
|
margin-top: 8px; padding-top: 8px;
|
|
border-top: 1px solid rgba(255,255,255,0.04);
|
|
}
|
|
.pca {
|
|
height: 30px; border-radius: 7px;
|
|
background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.05);
|
|
color: #3e3e3e; display: flex; align-items: center; justify-content: center;
|
|
cursor: pointer; transition: all 0.15s; text-decoration: none;
|
|
}
|
|
.pca i { width: 14px; height: 14px; }
|
|
.pca:hover { background: rgba(255,255,255,0.07); color: #bbb; border-color: rgba(255,255,255,0.1); }
|
|
.pca--primary:hover { background: rgba(223,0,106,0.1); color: var(--primary); border-color: rgba(223,0,106,0.15); }
|
|
|
|
/* ── Collapsed sidebar: avatar only ── */
|
|
.sidebar.collapsed .profile-card { background: transparent; padding: 0; overflow: visible; }
|
|
.sidebar.collapsed .profile-card::before { display: none; }
|
|
.sidebar.collapsed .pc-bg { background: transparent; padding: 4px 0; }
|
|
.sidebar.collapsed .pc-avatar-wrap { width: 42px; height: 42px; }
|
|
.sidebar.collapsed .pc-info,
|
|
.sidebar.collapsed .pc-actions { display: none; }
|
|
.sidebar.collapsed .pc-inner { justify-content: center; }
|
|
|
|
/* ── Guest buttons ── */
|
|
.pc-guest-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: 2px; }
|
|
.pca-guest-login {
|
|
height: 30px; border-radius: 7px; background: transparent;
|
|
border: 1px solid rgba(255,255,255,0.1); color: #555;
|
|
font-size: 11px; font-weight: 700; cursor: pointer; transition: 0.2s;
|
|
}
|
|
.pca-guest-login:hover { background: rgba(255,255,255,0.06); color: #bbb; }
|
|
.pca-guest-reg {
|
|
height: 30px; border-radius: 7px;
|
|
background: linear-gradient(135deg, var(--primary), #a3004d);
|
|
border: none; color: #fff; font-size: 11px; font-weight: 800;
|
|
cursor: pointer; box-shadow: 0 3px 12px rgba(223,0,106,0.25); transition: 0.2s;
|
|
}
|
|
.pca-guest-reg:hover { transform: translateY(-1px); box-shadow: 0 5px 18px rgba(223,0,106,0.4); }
|
|
|
|
/* Deposit Button - Fixed */
|
|
.sidebar-deposit-btn {
|
|
display: flex; align-items: center; justify-content: center; gap: 10px;
|
|
height: 48px; width: 100%;
|
|
background: linear-gradient(90deg, var(--primary), #a3004d); border-radius: 12px;
|
|
color: #fff; text-decoration: none; font-weight: 800; font-size: 13px;
|
|
position: relative; overflow: hidden; transition: 0.3s;
|
|
box-shadow: 0 4px 20px rgba(223,0,106,0.25);
|
|
}
|
|
.sidebar-deposit-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(223,0,106,0.4); }
|
|
.btn-content { display: flex; align-items: center; gap: 8px; z-index: 2; }
|
|
.btn-shine {
|
|
position: absolute; top: 0; left: -100%; width: 50%; height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
|
transform: skewX(-20deg); animation: btn-shine 3s infinite; z-index: 1;
|
|
}
|
|
@keyframes btn-shine { 0% { left: -100%; } 20% { left: 200%; } 100% { left: 200%; } }
|
|
|
|
.sidebar.collapsed .sidebar-deposit-btn { padding: 0; width: 48px; height: 48px; margin: 0 auto; }
|
|
.sidebar.collapsed .btn-text { display: none; }
|
|
.sidebar.collapsed .btn-content { gap: 0; }
|
|
|
|
/* Nav Menu - Enhanced Hover/Active */
|
|
.nav-menu { display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
|
.nav-group { margin-bottom: 6px; }
|
|
.nav-head {
|
|
padding: 10px 12px; font-size: 10px; font-weight: 800; color: #555; text-transform: uppercase; letter-spacing: 1px;
|
|
cursor: pointer; display: flex; justify-content: space-between; align-items: center; transition: 0.2s;
|
|
border-radius: 8px;
|
|
}
|
|
.nav-head:hover { color: #fff; background: rgba(255,255,255,0.03); }
|
|
.nav-head i { width: 12px; height: 12px; transition: transform 0.3s; }
|
|
.nav-head i.rotated { transform: rotate(-90deg); }
|
|
|
|
.sidebar.collapsed .nav-head { justify-content: center; padding: 10px 0; }
|
|
.sidebar.collapsed .nav-cat-title, .sidebar.collapsed .nav-head i { display: none; }
|
|
.sidebar.collapsed .nav-head::after { content: '•'; color: #444; font-size: 16px; }
|
|
|
|
.nav-items { display: flex; flex-direction: column; gap: 4px; overflow: hidden; transition: max-height 0.3s ease; }
|
|
.nav-items.collapsed { display: none; }
|
|
.sidebar.collapsed .nav-items { display: flex !important; }
|
|
|
|
.nav-items a {
|
|
display: flex; align-items: center; gap: 12px; padding: 10px 12px;
|
|
color: #888; text-decoration: none; font-size: 13px; font-weight: 600;
|
|
border-radius: 10px; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); position: relative; overflow: hidden;
|
|
}
|
|
/* Hover Effect */
|
|
.nav-items a:hover { color: #fff; background: #161616; padding-left: 16px; }
|
|
.nav-items a:hover .nav-icon { color: var(--primary); transform: scale(1.1); }
|
|
|
|
/* Active State */
|
|
.nav-items a.active { background: linear-gradient(90deg, rgba(223,0,106,0.1), transparent); color: #fff; }
|
|
.nav-items a.active .nav-icon { color: var(--primary); filter: drop-shadow(0 0 5px rgba(223,0,106,0.5)); }
|
|
.nav-items a.active::before {
|
|
content: ''; position: absolute; left: 0; top: 15%; bottom: 15%; width: 3px;
|
|
background: var(--primary); border-radius: 0 4px 4px 0; box-shadow: 2px 0 10px var(--primary);
|
|
}
|
|
|
|
.nav-icon { width: 20px; display: flex; justify-content: center; flex-shrink: 0; transition: 0.2s; }
|
|
.nav-icon i { width: 18px; height: 18px; }
|
|
|
|
.sidebar.collapsed .nav-items a { justify-content: center; padding: 12px 0; }
|
|
.sidebar.collapsed .nav-text { display: none; }
|
|
.sidebar.collapsed .nav-items a:hover { transform: none; background: #1a1a1a; padding: 12px 0; }
|
|
|
|
.sidebar-footer { margin-top: auto; font-size: 9px; font-weight: 800; color: #333; display: flex; align-items: center; justify-content: center; gap: 6px; letter-spacing: 1px; padding-top: 20px; }
|
|
.sidebar-footer i { width: 12px; }
|
|
.sidebar.collapsed .footer-text { display: none; }
|
|
|
|
/* Mobile close button inside sidebar header */
|
|
.close-mobile { display: none; }
|
|
|
|
@media (max-width: 1000px) {
|
|
.close-mobile {
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
width: 32px; height: 32px; border: none; background: transparent; color: #777; border-radius: 8px;
|
|
}
|
|
.close-mobile:hover { background: #1a1a1a; color: #fff; }
|
|
}
|
|
|
|
/* Backdrop for mobile sidebar */
|
|
.sidebar-backdrop {
|
|
display: none;
|
|
}
|
|
@media (max-width: 1000px) {
|
|
.sidebar-backdrop {
|
|
display: block; position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 900;
|
|
backdrop-filter: blur(2px);
|
|
}
|
|
}
|
|
|
|
/* --- Main Content --- */
|
|
.main { margin-left: 260px; flex: 1; display: flex; flex-direction: column; width: calc(100% - 260px); min-width: 0; transition: margin-left 0.3s var(--ease), width 0.3s var(--ease); }
|
|
.main.expanded { margin-left: 80px; width: calc(100% - 80px); }
|
|
|
|
.topbar {
|
|
height: 76px; padding: 0 32px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
|
|
background: rgba(5,5,5,0.85); backdrop-filter: blur(16px); border-bottom: 1px solid var(--border);
|
|
position: sticky; top: 0; z-index: 900;
|
|
}
|
|
.tb-left { display: flex; align-items: center; gap: 12px; }
|
|
|
|
/* Header search bar */
|
|
.header-search-bar {
|
|
display: flex; align-items: center; gap: 10px;
|
|
height: 40px; padding: 0 14px;
|
|
background: rgba(255,255,255,.04);
|
|
border: 1px solid rgba(255,255,255,.08);
|
|
border-radius: 12px;
|
|
cursor: pointer; transition: .2s;
|
|
color: #555; font-size: 13px;
|
|
min-width: 240px; max-width: 320px;
|
|
}
|
|
.header-search-bar:hover {
|
|
border-color: rgba(223,0,106,.3);
|
|
background: rgba(223,0,106,.04);
|
|
color: #888;
|
|
box-shadow: 0 0 0 3px rgba(223,0,106,.06);
|
|
}
|
|
.hsr-icon { width: 15px; height: 15px; flex-shrink: 0; color: #444; transition: color .2s; }
|
|
.header-search-bar:hover .hsr-icon { color: var(--primary, #df006a); }
|
|
.hsr-text { flex: 1; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12.5px; }
|
|
.hsr-kbd {
|
|
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1);
|
|
border-radius: 5px; padding: 1px 6px; font-size: 10px;
|
|
color: #444; font-family: monospace; flex-shrink: 0; letter-spacing: .5px;
|
|
}
|
|
|
|
/* Wallet Pill - Centered */
|
|
.wallet-wrapper { position: relative; display: flex; justify-content: center; }
|
|
.wallet-pill {
|
|
display: flex; align-items: center; background: #0a0a0a; border: 1px solid #222;
|
|
border-radius: 14px; padding: 4px; height: 48px; transition: 0.3s; gap: 10px;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
|
}
|
|
.wallet-pill:hover { border-color: #333; box-shadow: 0 8px 30px rgba(0,0,0,0.4); transform: translateY(-1px); }
|
|
|
|
.wp-icon-box { width: 38px; height: 38px; background: #141414; border-radius: 10px; display: flex; align-items: center; justify-content: center; border: 1px solid transparent; }
|
|
.wp-icon-box i { width: 20px; height: 20px; }
|
|
|
|
.wp-info { display: flex; flex-direction: column; justify-content: center; min-width: 80px; cursor: pointer; }
|
|
.wp-info span { font-weight: 800; font-size: 15px; color: #fff; letter-spacing: -0.5px; }
|
|
.anim-out { opacity: 0; transform: translateY(-5px); filter: blur(4px); transition: 0.2s; }
|
|
.anim-in { opacity: 1; transform: translateY(0); filter: blur(0); transition: 0.2s; }
|
|
|
|
.wp-actions { display: flex; gap: 4px; padding-right: 4px; }
|
|
.wp-btn {
|
|
width: 32px; height: 32px; border-radius: 8px; border: none; background: transparent;
|
|
color: #666; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s;
|
|
}
|
|
.wp-btn:hover { background: #1a1a1a; color: #fff; }
|
|
.wp-btn.vault:hover { color: var(--primary); background: rgba(223,0,106,0.1); }
|
|
.wp-btn.deposit:hover { color: var(--cyan); background: rgba(0,242,255,0.1); }
|
|
.wp-btn i { width: 16px; height: 16px; }
|
|
|
|
.wallet-dropdown {
|
|
position: absolute; top: 120%; left: 50%; transform: translateX(-50%) translateY(10px); width: min(90vw, 260px); background: #0f0f0f; border: 1px solid #222;
|
|
border-radius: 16px; padding: 8px; opacity: 0; visibility: hidden;
|
|
transition: 0.2s cubic-bezier(0.2, 0, 0, 1); box-shadow: 0 10px 40px rgba(0,0,0,0.8); z-index: 100;
|
|
max-height: min(60vh, 420px); overflow-y: auto; -webkit-overflow-scrolling: touch;
|
|
}
|
|
.wallet-wrapper.open .wallet-dropdown { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
|
|
|
.wd-item { display: flex; justify-content: space-between; padding: 12px; border-radius: 10px; cursor: pointer; font-size: 13px; font-weight: 700; color: #888; transition: 0.2s; }
|
|
.wd-item:hover { background: #18181b; color: #fff; }
|
|
.wd-item.active { background: #18181b; color: #fff; }
|
|
.wd-left { display: flex; align-items: center; gap: 10px; }
|
|
.wd-left i { width: 16px; }
|
|
|
|
/* Header Actions */
|
|
.h-actions { display: flex; align-items: center; gap: 12px; justify-content: flex-end; }
|
|
.h-icon-btn {
|
|
width: 42px; height: 42px; border-radius: 12px; background: #0a0a0a; border: 1px solid #222;
|
|
color: #888; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: 0.2s; position: relative;
|
|
}
|
|
.h-icon-btn:hover, .h-icon-btn.active { background: #18181b; color: #fff; border-color: #333; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
|
|
.h-icon-btn i { width: 20px; height: 20px; }
|
|
.h-icon-btn.has-unread { border-color: rgba(223,0,106,0.4); color: #df006a; animation: chat-pulse 2s ease-in-out infinite; }
|
|
@keyframes chat-pulse { 0%,100% { box-shadow: 0 0 0 0 rgba(223,0,106,0); } 50% { box-shadow: 0 0 0 4px rgba(223,0,106,0.2); } }
|
|
.chat-badge {
|
|
position: absolute; top: -5px; right: -5px;
|
|
background: #df006a; color: #fff;
|
|
font-size: 9px; font-weight: 900; line-height: 1;
|
|
padding: 2px 4px; border-radius: 20px; min-width: 16px; text-align: center;
|
|
border: 1.5px solid #0a0a0a; pointer-events: none;
|
|
animation: badge-pop .2s cubic-bezier(0.34,1.56,0.64,1);
|
|
}
|
|
@keyframes badge-pop { from { transform: scale(0); } to { transform: scale(1); } }
|
|
|
|
.badge-dot { position: absolute; top: 10px; right: 10px; width: 8px; height: 8px; background: var(--primary); border-radius: 50%; box-shadow: 0 0 8px var(--primary); border: 2px solid #0a0a0a; }
|
|
.bell-shake { animation: bell-shake 2s infinite; }
|
|
@keyframes bell-shake { 0%, 100% { transform: rotate(0); } 10%, 30%, 50%, 70%, 90% { transform: rotate(10deg); } 20%, 40%, 60%, 80% { transform: rotate(-10deg); } }
|
|
|
|
/* Language */
|
|
.lang-wrapper { position: relative; }
|
|
.lang-btn {
|
|
height: 42px; padding: 0 12px; background: #0a0a0a; border: 1px solid #222; border-radius: 12px;
|
|
display: flex; align-items: center; gap: 8px; cursor: pointer; transition: 0.2s;
|
|
}
|
|
.lang-btn:hover, .lang-btn.active { background: #18181b; border-color: #333; }
|
|
.lang-arrow { width: 14px; color: #666; }
|
|
.flag { width: 20px; height: 14px; border-radius: 3px; object-fit: cover; }
|
|
|
|
/* Guest Actions */
|
|
.guest-actions { display: flex; align-items: center; gap: 8px; }
|
|
.auth-btn {
|
|
height: 42px; padding: 0 20px; border-radius: 12px; font-weight: 700; font-size: 13px;
|
|
cursor: pointer; transition: 0.2s; border: 1px solid transparent;
|
|
}
|
|
.auth-btn.login {
|
|
background: transparent; color: #888; border-color: #222;
|
|
}
|
|
.auth-btn.login:hover {
|
|
color: #fff; border-color: #444; background: #111;
|
|
}
|
|
.auth-btn.register {
|
|
background: var(--primary); color: #fff; position: relative; overflow: hidden;
|
|
}
|
|
.auth-btn.register:hover {
|
|
transform: translateY(-2px); box-shadow: 0 4px 15px rgba(223,0,106,0.3);
|
|
}
|
|
.btn-glow {
|
|
position: absolute; inset: 0; background: linear-gradient(45deg, transparent, rgba(255,255,255,0.2), transparent);
|
|
transform: translateX(-100%); transition: 0.5s;
|
|
}
|
|
.auth-btn.register:hover .btn-glow { transform: translateX(100%); }
|
|
|
|
.lang-menu {
|
|
position: absolute; top: 120%; right: 0; width: 160px; background: #0f0f0f; border: 1px solid #222;
|
|
border-radius: 12px; padding: 6px; box-shadow: 0 10px 40px rgba(0,0,0,0.8); z-index: 100;
|
|
}
|
|
.lang-opt {
|
|
width: 100%; display: flex; align-items: center; gap: 10px; padding: 10px; border: none; background: transparent;
|
|
color: #888; font-size: 13px; font-weight: 600; border-radius: 8px; cursor: pointer; transition: 0.2s;
|
|
}
|
|
.lang-opt:hover { background: #18181b; color: #fff; }
|
|
|
|
/* User Avatar & Dropdown */
|
|
.profile-wrapper { position: relative; }
|
|
.user-avatar-btn {
|
|
width: 42px; height: 42px; background: #18181b; border-radius: 12px; display: flex; align-items: center; justify-content: center;
|
|
font-weight: 800; font-size: 16px; color: #fff; border: 1px solid #2a2a2a; cursor: pointer; transition: 0.2s;
|
|
overflow: visible; position: relative; flex-shrink: 0;
|
|
}
|
|
.user-avatar-btn img { width: 100%; height: 100%; object-fit: cover; border-radius: 11px; }
|
|
.user-avatar-btn span:not(.uab-role-dot) { pointer-events: none; }
|
|
.user-avatar-btn:hover, .user-avatar-btn.active { border-color: #444; transform: scale(1.05); box-shadow: 0 0 16px rgba(255,255,255,0.1); }
|
|
/* Admin ring */
|
|
.user-avatar-btn.role-admin { border-color: rgba(255,200,0,0.5); box-shadow: 0 0 12px rgba(255,180,0,0.25); }
|
|
.user-avatar-btn.role-mod { border-color: rgba(74,144,226,0.45); }
|
|
/* Role dot on topbar avatar */
|
|
.uab-role-dot {
|
|
position: absolute; bottom: -4px; right: -4px; z-index: 3;
|
|
width: 16px; height: 16px; border-radius: 50%; border: 2px solid #0a0a0a;
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.uab-role-dot i { width: 9px; height: 9px; }
|
|
.uab-role-dot--admin { background: #ffcc00; }
|
|
.uab-role-dot--admin i { color: #000; }
|
|
.uab-role-dot--mod { background: #4a90e2; }
|
|
.uab-role-dot--mod i { color: #fff; }
|
|
|
|
.profile-dropdown { /* container */
|
|
position: absolute; top: 130%; right: 0; width: min(92vw, 240px); background: #0f0f0f; border: 1px solid #222;
|
|
border-radius: 16px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.9); z-index: 100;
|
|
max-height: min(70vh, 420px); overflow-y: auto; -webkit-overflow-scrolling: touch;
|
|
}
|
|
.pd-header { padding: 16px; border-bottom: 1px solid #1a1a1a; background: #141414; }
|
|
.pd-theme { padding: 12px 16px; border-top: 1px solid #151515; border-bottom: 1px solid #151515; background: #0f0f0f; display: flex; flex-direction: column; gap: 10px; }
|
|
.pd-theme-row { display: flex; align-items: center; gap: 10px; font-size: 12px; color: #aaa; justify-content: space-between; }
|
|
.pd-theme-row i { width: 16px; height: 16px; color: var(--primary); }
|
|
.pd-color { width: 28px; height: 18px; border: 1px solid #333; background: #111; border-radius: 6px; padding: 0; cursor: pointer; }
|
|
|
|
/* Theme toggle pill */
|
|
.theme-toggle-btn {
|
|
width: 42px; height: 22px; border-radius: 11px;
|
|
background: #222; border: 1px solid #333; cursor: pointer;
|
|
position: relative; transition: background 0.25s, border-color 0.25s; padding: 0;
|
|
flex-shrink: 0;
|
|
}
|
|
.theme-toggle-btn.light { background: var(--primary); border-color: var(--primary); }
|
|
.tt-thumb {
|
|
position: absolute; top: 2px; left: 2px;
|
|
width: 16px; height: 16px; border-radius: 50%;
|
|
background: #555; transition: transform 0.25s, background 0.25s;
|
|
display: block;
|
|
}
|
|
.theme-toggle-btn.light .tt-thumb { transform: translateX(20px); background: #fff; }
|
|
.pd-name { font-size: 14px; font-weight: 800; color: #fff; margin-bottom: 2px; }
|
|
.pd-email { font-size: 11px; color: #888; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
|
.pd-links { padding: 8px; display: flex; flex-direction: column; gap: 2px; }
|
|
.pd-item {
|
|
display: flex; align-items: center; gap: 10px; padding: 10px 12px; color: #aaa; text-decoration: none;
|
|
font-size: 13px; font-weight: 600; border-radius: 8px; transition: 0.2s;
|
|
}
|
|
.pd-item:hover { background: #1a1a1a; color: #fff; }
|
|
.pd-item i { width: 16px; height: 16px; }
|
|
|
|
.pd-footer { padding: 8px; border-top: 1px solid #1a1a1a; }
|
|
.pd-logout {
|
|
width: 100%; display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: transparent;
|
|
border: none; color: #ff3e3e; font-size: 13px; font-weight: 700; border-radius: 8px; cursor: pointer; transition: 0.2s;
|
|
}
|
|
.pd-logout:hover { background: rgba(255, 62, 62, 0.1); }
|
|
.pd-logout i { width: 16px; height: 16px; }
|
|
|
|
/* Notifications */
|
|
.notif-dropdown {
|
|
position: absolute; top: 130%; right: 0; width: min(95vw, 360px); background: #0f0f0f; border: 1px solid #222;
|
|
border-radius: 16px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.9); z-index: 100;
|
|
max-height: min(70vh, 480px); overflow-y: auto; -webkit-overflow-scrolling: touch;
|
|
}
|
|
.nd-head { display: flex; justify-content: space-between; padding: 16px; background: #141414; border-bottom: 1px solid #222; font-size: 12px; font-weight: 800; color: #fff; text-transform: uppercase; }
|
|
.nd-clear { background: transparent; border: none; color: #666; font-size: 11px; font-weight: 700; cursor: pointer; }
|
|
.nd-clear:hover { color: var(--primary); }
|
|
.nd-list { max-height: 350px; overflow-y: auto; }
|
|
.nd-empty { padding: 40px; text-align: center; color: #444; font-size: 13px; }
|
|
.nd-item { display: flex; gap: 14px; padding: 16px; border-bottom: 1px solid #1a1a1a; transition: 0.2s; }
|
|
.nd-item:hover { background: #141414; }
|
|
.nd-item.unread { background: rgba(223,0,106,0.03); }
|
|
.nd-icon-box { width: 36px; height: 36px; background: #1a1a1a; border-radius: 10px; display: flex; align-items: center; justify-content: center; color: #888; flex-shrink: 0; }
|
|
.nd-title { font-size: 13px; font-weight: 700; color: #fff; margin-bottom: 4px; }
|
|
.nd-desc { font-size: 12px; color: #888; line-height: 1.4; }
|
|
.nd-time { font-size: 10px; color: #444; margin-top: 6px; text-align: right; }
|
|
|
|
/* Transitions */
|
|
.pop-down-enter-active, .pop-down-leave-active { transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); }
|
|
.pop-down-enter-from, .pop-down-leave-to { opacity: 0; transform: translateY(-10px) scale(0.95); }
|
|
|
|
|
|
/* Mobile */
|
|
.mobile-toggle { display: none; width: 40px; height: 40px; align-items: center; justify-content: center; color: #fff; cursor: pointer; }
|
|
|
|
@media (max-width: 1000px) {
|
|
.sidebar { transform: translateX(-100%); box-shadow: none; width: 280px; transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
.sidebar.open { transform: translateX(0); box-shadow: 15px 0 50px rgba(0,0,0,0.9); }
|
|
.main { margin-left: 0; width: 100%; }
|
|
.main.expanded { margin-left: 0; width: 100%; }
|
|
.mobile-toggle { display: flex; }
|
|
.topbar {
|
|
padding: calc(env(safe-area-inset-top, 0) + 8px) 16px 8px 16px;
|
|
grid-template-columns: 40px 1fr 40px;
|
|
gap: 8px;
|
|
height: auto;
|
|
min-height: 64px;
|
|
z-index: 950;
|
|
}
|
|
.tb-left { gap: 8px; }
|
|
.header-search-bar { display: none; }
|
|
.wallet-wrapper { width: 100%; display: flex; justify-content: center; }
|
|
.wallet-pill { padding: 2px 4px; height: 44px; width: auto; max-width: 180px; }
|
|
.wp-icon-box { width: 34px; height: 34px; }
|
|
.wp-info { min-width: 60px; padding: 0 4px; }
|
|
.wp-info span { font-size: 13px; }
|
|
.wp-actions { display: none; }
|
|
|
|
.h-actions { gap: 8px; }
|
|
.h-actions .lang-wrapper { display: none; }
|
|
.h-actions .notif-wrapper { display: none; }
|
|
.collapse-btn { display: none; }
|
|
|
|
/* Profile card compact on mobile */
|
|
.profile-card .pc-inner { padding: 12px; }
|
|
.profile-card .pc-avatar-wrap { width: 40px; height: 40px; }
|
|
.profile-card .pc-name { font-size: 14px; }
|
|
.profile-card .pc-badge { font-size: 9px; }
|
|
|
|
/* Nav Menu adjustments */
|
|
.nav-items a { padding: 12px 16px; font-size: 14px; }
|
|
|
|
/* Deposit button responsive */
|
|
.sidebar-deposit-btn { height: 50px; font-size: 14px; margin: 10px 0; }
|
|
|
|
/* Bottom Navigation for Mobile */
|
|
.mobile-nav-bottom {
|
|
display: flex; position: fixed; bottom: 0; left: 0; right: 0; height: 64px;
|
|
background: rgba(10, 10, 10, 0.95); backdrop-filter: blur(10px);
|
|
border-top: 1px solid #222; z-index: 1000; padding: 0 10px;
|
|
justify-content: space-around; align-items: center;
|
|
padding-bottom: env(safe-area-inset-bottom, 0);
|
|
}
|
|
.m-nav-item {
|
|
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
|
color: #666; text-decoration: none; font-size: 10px; font-weight: 700;
|
|
transition: 0.2s;
|
|
}
|
|
.m-nav-item.active { color: var(--primary); }
|
|
.m-nav-item i { width: 22px; height: 22px; }
|
|
|
|
.content-wrapper { padding-bottom: 80px; } /* Space for mobile nav */
|
|
|
|
/* Auth buttons on mobile */
|
|
.auth-btn { height: 40px; padding: 0 12px; font-size: 12px; }
|
|
.auth-btn.register .btn-text { display: inline; }
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.topbar { grid-template-columns: 40px 1fr 40px; }
|
|
.wallet-pill { max-width: 140px; }
|
|
.wp-info span { font-size: 12px; }
|
|
.user-avatar-btn { width: 38px; height: 38px; }
|
|
}
|
|
|
|
/* Page transition */
|
|
.page-fade-enter-active { transition: opacity 0.18s ease, transform 0.18s ease; }
|
|
.page-fade-leave-active { transition: opacity 0.12s ease; }
|
|
.page-fade-enter-from { opacity: 0; transform: translateY(6px); }
|
|
.page-fade-leave-to { opacity: 0; }
|
|
</style>
|