1190 lines
46 KiB
Vue
1190 lines
46 KiB
Vue
<script setup lang="ts">
|
||
import { Head, router, usePage } from '@inertiajs/vue3';
|
||
import { Flame, Star, Zap, Trophy, ChevronDown, HelpCircle, PlayCircle, Heart, Shield, Gamepad2 } from 'lucide-vue-next';
|
||
import { ref, onMounted, computed, Teleport } from 'vue';
|
||
import { useI18n } from 'vue-i18n';
|
||
|
||
const { t } = useI18n();
|
||
import Button from '@/components/ui/button.vue';
|
||
import UserLayout from '@/layouts/user/userlayout.vue';
|
||
|
||
const page = usePage();
|
||
const authUser = computed(() => (page.props as any)?.auth?.user ?? null);
|
||
|
||
// Server-side initial games (available on first paint, no loading flicker)
|
||
const props = defineProps<{ initialGames?: any[] }>();
|
||
|
||
function mapGame(g: any, idx: number) {
|
||
const rawName = g.name ?? g.title ?? `game-${idx}`;
|
||
const rawId = g.slug ?? g.id ?? null;
|
||
return {
|
||
id: rawId ?? idx,
|
||
slug: String(rawId || rawName).toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
||
name: rawName,
|
||
provider: g.provider ?? 'BetiX',
|
||
image: g.image ?? g.thumbnail_url ?? g.thumbnail ?? '',
|
||
tag: g.tag ?? '',
|
||
rtp: g.rtp ?? 96.0,
|
||
type: g.type ?? 'original',
|
||
};
|
||
}
|
||
|
||
// --- DATA ---
|
||
|
||
const providers = ref<string[]>([]);
|
||
const slots = ref<any[]>([]);
|
||
const newReleases = ref<any[]>([]);
|
||
const liveCasino = ref<any[]>([]);
|
||
|
||
// Static Hall of Fame fallback — shown when no real bets exist yet
|
||
const bestHits = [
|
||
{ id: 1, user: 'CryptoKing', game: 'Dice', multiplier: '9,900x', amount: '5.2 BTX', image: null, rank: 1 },
|
||
{ id: 2, user: 'LuckyLuke', game: 'Mines', multiplier: '4,200x', amount: '2.1 BTX', image: null, rank: 2 },
|
||
{ id: 3, user: 'Anna_99', game: 'Crash', multiplier: '1,500x', amount: '0.8 BTX', image: null, rank: 3 },
|
||
{ id: 4, user: 'Satoshi', game: 'Plinko', multiplier: '800x', amount: '0.4 BTX', image: null, rank: 4 },
|
||
{ id: 5, user: 'Whale', game: 'Dice', multiplier: '500x', amount: '0.2 BTX', image: null, rank: 5 },
|
||
];
|
||
|
||
// Real Hall of Fame data from API
|
||
const topWins = ref<any[]>([]);
|
||
const hallOfFame = computed(() => topWins.value.length ? topWins.value : bestHits);
|
||
|
||
const faqs = [
|
||
{ q: 'How do I deposit crypto?', a: 'Go to your wallet, select the cryptocurrency you wish to deposit, and copy the address. Send your funds to that address.' },
|
||
{ q: 'Is Betix fair?', a: 'Yes, all our games use Provably Fair technology or are provided by certified, regulated game providers with RNG testing.' },
|
||
{ q: 'What is the welcome bonus?', a: 'New players get a 100% match bonus up to 1 BTC plus 50 free spins on their first deposit.' },
|
||
{ q: 'How fast are withdrawals?', a: 'Withdrawals are processed instantly. Depending on the blockchain network, it usually takes a few minutes.' },
|
||
];
|
||
|
||
// Live Wins Feed
|
||
const liveWins = ref([
|
||
{ id: 1, user: 'CryptoKing', game: 'Gates of Olympus', amount: '0.05 BTC', isWin: true },
|
||
{ id: 2, user: 'LuckyLuke', game: 'Sweet Bonanza', amount: '1.2 ETH', isWin: true },
|
||
{ id: 3, user: 'Anna_99', game: 'Razor Shark', amount: '500 USDT', isWin: true },
|
||
{ id: 4, user: 'Satoshi', game: 'Mental', amount: '0.01 BTC', isWin: true },
|
||
{ id: 5, user: 'Whale_Hunter', game: 'San Quentin', amount: '2.5 ETH', isWin: true },
|
||
{ id: 6, user: 'Dolo', game: 'Wanted', amount: '1500 XRP', isWin: true },
|
||
]);
|
||
|
||
// BetiX Originals (loaded from API)
|
||
const originals = ref<any[]>([]);
|
||
const originalsLoading = ref<boolean>(!props.initialGames?.length);
|
||
const originalsError = ref<string | null>(null);
|
||
|
||
// Iframe game session
|
||
const gameSession = ref<{ url: string; token: string; game: string } | null>(null);
|
||
const gameLoading = ref(false);
|
||
|
||
// --- STATE ---
|
||
|
||
const activeFaq = ref<number | null>(null);
|
||
const hasRealWins = ref(false);
|
||
|
||
// --- METHODS ---
|
||
|
||
const toggleFaq = (index: number) => {
|
||
activeFaq.value = activeFaq.value === index ? null : index;
|
||
};
|
||
|
||
const maskName = (name: string) => {
|
||
if (name.length <= 3) return name;
|
||
return name.substring(0, 2) + '***' + name.substring(name.length - 1);
|
||
};
|
||
|
||
const slugify = (val: string) => String(val)
|
||
.toLowerCase()
|
||
.replace(/[^a-z0-9]+/g, '-')
|
||
.replace(/^-+|-+$/g, '');
|
||
|
||
const play = (game: any) => {
|
||
const user = authUser.value;
|
||
|
||
if (!user) {
|
||
window.dispatchEvent(new Event('require-login'));
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const slug = game?.slug ?? game?.id ?? (game?.name ? slugify(game.name) : null);
|
||
const provider = (game?.provider ?? 'betix').toLowerCase().replace(/\s+/g, '-');
|
||
if (slug) {
|
||
router.visit(`/games/play/${encodeURIComponent(provider)}/${encodeURIComponent(String(slug))}`);
|
||
return;
|
||
}
|
||
alert('Spiel-URL nicht verfügbar.');
|
||
} catch (e) {
|
||
console.error('Play navigation failed:', e);
|
||
}
|
||
}
|
||
|
||
const providersLoading = ref<boolean>(true);
|
||
const gamesLoading = ref<boolean>(true);
|
||
const allGames = ref<any[]>((props.initialGames ?? []).map(mapGame));
|
||
|
||
const searchQuery = ref('');
|
||
const selectedProvider = ref('All');
|
||
const selectedRtpRange = ref('all'); // 'all' | 'low' | 'mid' | 'high'
|
||
|
||
const rtpRanges = [
|
||
{ value: 'all', label: 'All RTP' },
|
||
{ value: 'low', label: 'RTP < 95%' },
|
||
{ value: 'mid', label: 'RTP 95–96%' },
|
||
{ value: 'high', label: 'RTP 97%+' },
|
||
];
|
||
|
||
function matchesRtp(game: any): boolean {
|
||
if (selectedRtpRange.value === 'all') return true;
|
||
const rtp = Number(game.rtp ?? 96);
|
||
if (selectedRtpRange.value === 'low') return rtp < 95;
|
||
if (selectedRtpRange.value === 'mid') return rtp >= 95 && rtp < 97;
|
||
if (selectedRtpRange.value === 'high') return rtp >= 97;
|
||
return true;
|
||
}
|
||
|
||
function matchesSearch(game: any): boolean {
|
||
return searchQuery.value === '' || game.name.toLowerCase().includes(searchQuery.value.toLowerCase());
|
||
}
|
||
|
||
function matchesProvider(game: any): boolean {
|
||
return selectedProvider.value === 'All' || game.provider === selectedProvider.value;
|
||
}
|
||
|
||
const filteredOriginals = computed(() =>
|
||
allGames.value.filter(g =>
|
||
(g.provider === 'BetiX' || g.type === 'original') &&
|
||
matchesSearch(g) && matchesProvider(g) && matchesRtp(g)
|
||
)
|
||
);
|
||
|
||
// Shows all available games in the slider — fetched automatically via loadData()
|
||
const popularSlots = computed(() =>
|
||
allGames.value.filter(g => matchesProvider(g) && matchesRtp(g)).slice(0, 12)
|
||
);
|
||
|
||
const filteredSlots = computed(() =>
|
||
allGames.value.filter(g =>
|
||
(g.type === 'slot' || !g.type) &&
|
||
matchesSearch(g) && matchesProvider(g) && matchesRtp(g)
|
||
).slice(0, 12)
|
||
);
|
||
|
||
const filteredLive = computed(() =>
|
||
allGames.value.filter(g =>
|
||
(g.type === 'live' || g.type === 'table') &&
|
||
matchesSearch(g) && matchesProvider(g) && matchesRtp(g)
|
||
).slice(0, 8)
|
||
);
|
||
|
||
// Favorites
|
||
const favorites = ref<Set<string>>(new Set());
|
||
|
||
async function loadFavorites() {
|
||
try {
|
||
const res = await fetch('/api/favorites');
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
favorites.value = new Set((data.data ?? []).map((f: any) => f.game_slug));
|
||
}
|
||
} catch {}
|
||
}
|
||
|
||
async function toggleFavorite(game: any) {
|
||
const user = authUser.value;
|
||
if (!user) { window.dispatchEvent(new Event('require-login')); return; }
|
||
|
||
const slug = game.slug ?? game.id ?? slugify(game.name ?? '');
|
||
const isFav = favorites.value.has(slug);
|
||
|
||
// Optimistic update
|
||
const next = new Set(favorites.value);
|
||
if (isFav) next.delete(slug); else next.add(slug);
|
||
favorites.value = next;
|
||
|
||
try {
|
||
if (isFav) {
|
||
await fetch(`/api/favorites/${encodeURIComponent(slug)}`, { method: 'DELETE', headers: { 'X-CSRF-TOKEN': (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement)?.content ?? '' } });
|
||
} else {
|
||
await fetch('/api/favorites', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement)?.content ?? '' }, body: JSON.stringify({ slug, name: game.name, image: game.image, provider: game.provider }) });
|
||
}
|
||
} catch {
|
||
// Revert on error
|
||
favorites.value = new Set(isFav ? [...favorites.value, slug] : [...favorites.value].filter(s => s !== slug));
|
||
}
|
||
}
|
||
|
||
// Recently Played
|
||
const recentlyPlayed = ref<any[]>([]);
|
||
|
||
let liveWinsErrors = 0;
|
||
let liveWinsInterval: ReturnType<typeof setInterval> | null = null;
|
||
|
||
async function loadLiveWins() {
|
||
try {
|
||
const res = await fetch('/api/wins/live', { signal: AbortSignal.timeout(4000) });
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
if (Array.isArray(data) && data.length > 0) {
|
||
liveWins.value = data.slice(0, 6);
|
||
hasRealWins.value = true;
|
||
}
|
||
liveWinsErrors = 0;
|
||
} else {
|
||
liveWinsErrors++;
|
||
}
|
||
} catch {
|
||
liveWinsErrors++;
|
||
}
|
||
// Stop polling after 3 consecutive failures — server is busy/offline
|
||
if (liveWinsErrors >= 3 && liveWinsInterval !== null) {
|
||
clearInterval(liveWinsInterval);
|
||
liveWinsInterval = null;
|
||
}
|
||
}
|
||
|
||
async function loadTopWins() {
|
||
try {
|
||
const res = await fetch('/api/wins/top');
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
if (Array.isArray(data) && data.length > 0) {
|
||
topWins.value = data;
|
||
}
|
||
}
|
||
} catch {}
|
||
}
|
||
|
||
async function loadRecentlyPlayed() {
|
||
try {
|
||
const res = await fetch('/api/recently-played');
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
recentlyPlayed.value = data.data ?? [];
|
||
}
|
||
} catch {}
|
||
}
|
||
|
||
async function loadData() {
|
||
// If we already have server-side games, don't show a loading state — just refresh in background
|
||
if (!allGames.value.length) {
|
||
providersLoading.value = true;
|
||
gamesLoading.value = true;
|
||
}
|
||
|
||
try {
|
||
// 1. BetiX Originals (always available)
|
||
let list: any[] = (props.initialGames ?? []).map(mapGame);
|
||
|
||
// 2. Try to merge in external games from backend API
|
||
try {
|
||
const res = await fetch('/api/games');
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const external = (Array.isArray(data) ? data : (data?.games || data?.items || [])) as any[];
|
||
const externalMapped = external.map((g, i) => mapGame(g, list.length + i));
|
||
// Only add slugs not already present
|
||
const existingSlugs = new Set(list.map(g => g.slug));
|
||
list = [...list, ...externalMapped.filter(g => !existingSlugs.has(g.slug))];
|
||
}
|
||
} catch {}
|
||
|
||
// If API calls failed but we already have server-side games showing, just skip silently
|
||
if (!list.length) return;
|
||
|
||
allGames.value = list;
|
||
|
||
// Initial slices
|
||
originals.value = allGames.value.filter((g: any) => g.provider === 'BetiX' || g.type === 'original');
|
||
slots.value = allGames.value.filter((g: any) => g.type === 'slot').slice(0, 12);
|
||
liveCasino.value = allGames.value.filter((g: any) => g.type === 'live' || g.type === 'table').slice(0, 8);
|
||
newReleases.value = allGames.value.slice(0, 4);
|
||
|
||
// Providers
|
||
const pSet = new Set<string>(allGames.value.map((g: any) => g.provider).filter(Boolean));
|
||
providers.value = Array.from(pSet).sort();
|
||
|
||
} catch (e) {
|
||
console.error('Failed to load real mode lobby data:', e);
|
||
originalsError.value = t('dashboard.error_loading');
|
||
} finally {
|
||
providersLoading.value = false;
|
||
gamesLoading.value = false;
|
||
originalsLoading.value = false;
|
||
}
|
||
}
|
||
|
||
// Alias so the error-state reload button works
|
||
const loadOriginals = loadData;
|
||
|
||
let nextId = liveWins.value.length + 1;
|
||
|
||
onMounted(() => {
|
||
loadData();
|
||
loadFavorites();
|
||
loadRecentlyPlayed();
|
||
loadLiveWins();
|
||
loadTopWins();
|
||
|
||
// Poll live wins every 15s — stops automatically after 3 consecutive failures
|
||
liveWinsInterval = setInterval(loadLiveWins, 15000);
|
||
|
||
// Simulated live feed fallback — only runs when no real wins exist yet
|
||
setInterval(() => {
|
||
if (hasRealWins.value) return;
|
||
const games = ['Dice', 'Mines', 'Crash', 'Plinko'];
|
||
const users = ['Andri_X', 'CryptoKing', 'Neon_Ripper', 'Satoshi', 'Whale_Hunter', 'Dolo'];
|
||
const amounts = ['0.01 BTX', '0.5 BTX', '2.0 BTX', '0.15 BTX', '0.8 BTX'];
|
||
|
||
const newWin = {
|
||
id: nextId++,
|
||
user: users[Math.floor(Math.random() * users.length)],
|
||
game: games[Math.floor(Math.random() * games.length)],
|
||
amount: amounts[Math.floor(Math.random() * amounts.length)],
|
||
isWin: true
|
||
};
|
||
|
||
liveWins.value.unshift(newWin);
|
||
if (liveWins.value.length > 6) liveWins.value.pop();
|
||
}, 2500);
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<UserLayout>
|
||
<Head title="Welcome" />
|
||
|
||
<div class="dashboard-content">
|
||
|
||
<!-- Hero Section -->
|
||
<div class="hero-banner">
|
||
<div class="hero-content">
|
||
<div class="badge">WELCOME OFFER</div>
|
||
<h1 class="hero-title">100% BONUS <br> <span class="highlight">UP TO 1 BTC</span></h1>
|
||
<p class="hero-desc">+ 50 Free Spins on your first deposit. No wager limits.</p>
|
||
<div class="hero-actions">
|
||
<Button class="neon-button h-12 px-8 text-base font-bold">CLAIM NOW</Button>
|
||
<button class="text-link">Read Terms</button>
|
||
</div>
|
||
</div>
|
||
<div class="hero-image">
|
||
<div class="floating-coin">
|
||
<div class="coin-inner">₿</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Provider Filter Bar -->
|
||
<div class="providers-bar">
|
||
<div class="provider-list">
|
||
<button
|
||
class="provider-item"
|
||
:class="{ active: selectedProvider === 'All' }"
|
||
@click="selectedProvider = 'All'"
|
||
>All</button>
|
||
<button
|
||
v-for="p in providers"
|
||
:key="p"
|
||
class="provider-item"
|
||
:class="{ active: selectedProvider === p }"
|
||
@click="selectedProvider = p"
|
||
>{{ p }}</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- BetiX Originals -->
|
||
<section class="game-section" id="originals">
|
||
<div class="section-header">
|
||
<h2>
|
||
BetiX <span class="handwritten">originals</span>
|
||
</h2>
|
||
<button class="view-all" @click="selectedProvider = 'All'">Show All</button>
|
||
</div>
|
||
|
||
<div v-if="originalsLoading" style="color:#888; font-weight:800; padding: 10px 4px;">{{ $t('dashboard.loading') }}</div>
|
||
<div v-else-if="originalsError" style="color:#ff8a8a; font-weight:800; padding: 10px 4px;">
|
||
{{ originalsError }}
|
||
<div style="margin-top:8px;">
|
||
<Button class="neon-button h-9 px-4" @click="loadOriginals">{{ $t('dashboard.reload') }}</Button>
|
||
</div>
|
||
</div>
|
||
<div v-else-if="filteredOriginals.length === 0" style="color:#777; font-weight:800; padding: 10px 4px;">{{ $t('dashboard.no_games') }}</div>
|
||
<div v-else class="game-grid">
|
||
<div v-for="game in filteredOriginals" :key="game.id" class="game-card group" @click="play(game)">
|
||
<div class="card-image">
|
||
<img :src="game.image || 'https://placehold.co/600x400?text=BetiX'" :alt="game.name" loading="lazy">
|
||
<div class="card-overlay">
|
||
<Button class="play-btn" @click.stop.prevent="play(game)"><PlayCircle class="w-8 h-8 text-white" /></Button>
|
||
<span class="provider">{{ game.provider || 'BetiX' }}</span>
|
||
</div>
|
||
<div v-if="game.tag" class="tag">{{ game.tag }}</div>
|
||
<div v-if="game.rtp" class="rtp-badge">RTP {{ game.rtp }}%</div>
|
||
<button class="fav-btn" :class="{ active: favorites.has(game.slug) }" @click.stop="toggleFavorite(game)" title="Favorite">
|
||
<Heart class="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
<div class="card-info">
|
||
<div class="game-name">{{ game.name }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Popular Slots — horizontal slider -->
|
||
<section class="game-section" id="slots">
|
||
<div class="section-header">
|
||
<h2><Flame class="w-5 h-5 text-orange-500 inline mb-1" /> {{ $t('dashboard.popular_slots') }}</h2>
|
||
<button class="view-all" @click="selectedProvider = 'All'">{{ $t('dashboard.show_all') }}</button>
|
||
</div>
|
||
<div v-if="popularSlots.length === 0 && !gamesLoading" style="color:#777; font-weight:800; padding: 10px 4px;">{{ $t('dashboard.no_slots') }}</div>
|
||
<div class="slider-track">
|
||
<div v-for="slot in popularSlots" :key="slot.id" class="game-card slider-card group" @click="play(slot)">
|
||
<div class="card-image">
|
||
<img :src="slot.image || 'https://placehold.co/300x200?text=' + encodeURIComponent(slot.name)" :alt="slot.name" loading="lazy">
|
||
<div class="card-overlay">
|
||
<Button class="play-btn" @click.stop.prevent="play(slot)"><PlayCircle class="w-8 h-8 text-white" /></Button>
|
||
<span class="provider">{{ slot.provider }}</span>
|
||
</div>
|
||
<div v-if="slot.tag" class="tag">{{ slot.tag }}</div>
|
||
<div class="rtp-badge">RTP {{ slot.rtp }}%</div>
|
||
<button class="fav-btn" :class="{ active: favorites.has(slot.slug) }" @click.stop="toggleFavorite(slot)" title="Favorite">
|
||
<Heart class="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
<div class="card-info">
|
||
<div class="game-name">{{ slot.name }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Live Wins (Full Width) -->
|
||
<section class="wins-section">
|
||
<div class="section-header">
|
||
<h2><Trophy class="w-5 h-5 text-yellow-500 inline mb-1" /> {{ $t('dashboard.live_wins') }}</h2>
|
||
<div class="live-badge">
|
||
<span class="live-dot"></span>
|
||
{{ hasRealWins ? 'LIVE' : $t('dashboard.live_feed') }}
|
||
</div>
|
||
</div>
|
||
<div class="wins-grid">
|
||
<transition-group name="wins-list">
|
||
<div v-for="win in liveWins" :key="win.id" class="win-card">
|
||
<div class="win-icon">
|
||
<img :src="`https://api.dicebear.com/7.x/avataaars/svg?seed=${win.user}`" alt="Avatar" class="w-8 h-8 rounded-full bg-[#222]" />
|
||
</div>
|
||
<div class="win-details">
|
||
<div class="win-user">{{ maskName(win.user) }}</div>
|
||
<div class="win-game">{{ $t('dashboard.won_in') }} {{ win.game }}</div>
|
||
</div>
|
||
<div class="win-value">{{ win.amount }}</div>
|
||
</div>
|
||
</transition-group>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- New Releases -->
|
||
<section class="game-section" id="new-releases">
|
||
<div class="section-header">
|
||
<h2><Star class="w-5 h-5 text-yellow-400 inline mb-1" /> {{ $t('dashboard.new_releases') }}</h2>
|
||
<button class="view-all" @click="selectedProvider = 'All'">{{ $t('dashboard.show_all') }}</button>
|
||
</div>
|
||
<div class="game-grid">
|
||
<div v-for="slot in newReleases" :key="slot.id" class="game-card group" @click="play(slot)">
|
||
<div class="card-image">
|
||
<img :src="slot.image" :alt="slot.name" loading="lazy">
|
||
<div class="card-overlay">
|
||
<Button class="play-btn" @click.stop.prevent="play(slot)"><PlayCircle class="w-8 h-8 text-white" /></Button>
|
||
<span class="provider">{{ slot.provider }}</span>
|
||
</div>
|
||
<div v-if="slot.tag" class="tag">{{ slot.tag }}</div>
|
||
</div>
|
||
<div class="card-info">
|
||
<div class="game-name">{{ slot.name }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Platform Features Strip -->
|
||
<div class="features-strip">
|
||
<div class="feature-item">
|
||
<div class="feature-icon"><Shield :size="28" /></div>
|
||
<div class="feature-label">Provably Fair</div>
|
||
<div class="feature-desc">Every result verifiable on-chain</div>
|
||
</div>
|
||
<div class="feature-item">
|
||
<div class="feature-icon"><Zap :size="28" /></div>
|
||
<div class="feature-label">Instant Payouts</div>
|
||
<div class="feature-desc">No waiting, no limits</div>
|
||
</div>
|
||
<div class="feature-item">
|
||
<div class="feature-icon"><Gamepad2 :size="28" /></div>
|
||
<div class="feature-label">Exclusive Originals</div>
|
||
<div class="feature-desc">Games only on BetiX</div>
|
||
</div>
|
||
<div class="feature-item">
|
||
<div class="feature-icon"><Star :size="28" /></div>
|
||
<div class="feature-label">VIP Rewards</div>
|
||
<div class="feature-desc">Climb ranks, earn more</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Live Casino -->
|
||
<section class="game-section" id="live">
|
||
<div class="section-header">
|
||
<h2><Zap class="w-5 h-5 text-blue-400 inline mb-1" /> {{ $t('dashboard.live_casino') }}</h2>
|
||
<button class="view-all" @click="selectedProvider = 'All'">{{ $t('dashboard.show_all') }}</button>
|
||
</div>
|
||
<div v-if="filteredLive.length === 0 && !gamesLoading" style="color:#777; font-weight:800; padding: 10px 4px;">{{ $t('dashboard.no_live') }}</div>
|
||
<div class="game-grid">
|
||
<div v-for="slot in filteredLive" :key="slot.id" class="game-card group" @click="play(slot)">
|
||
<div class="card-image">
|
||
<img :src="slot.image" :alt="slot.name" loading="lazy">
|
||
<div class="card-overlay">
|
||
<Button class="play-btn" @click.stop.prevent="play(slot)"><PlayCircle class="w-8 h-8 text-white" /></Button>
|
||
<span class="provider">{{ slot.provider }}</span>
|
||
</div>
|
||
<div v-if="slot.tag" class="tag">{{ slot.tag }}</div>
|
||
</div>
|
||
<div class="card-info">
|
||
<div class="game-name">{{ slot.name }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- All Time Best Hits (Hall of Fame) -->
|
||
<section class="hits-section">
|
||
<div class="section-header">
|
||
<h2><Trophy class="w-5 h-5 text-purple-500 inline mb-1" /> {{ $t('dashboard.hall_of_fame') }}</h2>
|
||
</div>
|
||
<div class="hits-card">
|
||
<div v-if="hallOfFame.length === 0" style="color:#555; padding: 24px; text-align:center;">No wins recorded yet.</div>
|
||
<div v-else class="hits-container">
|
||
<!-- Rank 1 Hero -->
|
||
<div class="hit-hero">
|
||
<div class="hit-hero-image">
|
||
<img
|
||
:src="hallOfFame[0].image || 'https://placehold.co/600x300?text=' + encodeURIComponent(hallOfFame[0].game)"
|
||
:alt="hallOfFame[0].game"
|
||
>
|
||
<div class="hit-hero-overlay">
|
||
<div class="rank-badge rank-1">#1</div>
|
||
<div class="hit-hero-content">
|
||
<div class="hit-hero-multi">{{ hallOfFame[0].multiplier }}</div>
|
||
<div class="hit-hero-amount">{{ hallOfFame[0].amount }}</div>
|
||
<div class="hit-hero-user">by {{ maskName(hallOfFame[0].user) }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rank 2-5 List -->
|
||
<div class="hits-list">
|
||
<div v-for="hit in hallOfFame.slice(1)" :key="hit.id" class="hit-item">
|
||
<div class="hit-rank">{{ hit.rank }}</div>
|
||
<div class="hit-thumb">
|
||
<img :src="hit.image || 'https://placehold.co/80x60?text=' + encodeURIComponent(hit.game)" :alt="hit.game">
|
||
</div>
|
||
<div class="hit-details">
|
||
<div class="hit-multi">{{ hit.multiplier }}</div>
|
||
<div class="hit-game-name">{{ hit.game }}</div>
|
||
</div>
|
||
<div class="hit-amount-small">{{ hit.amount }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Recently Played -->
|
||
<section v-if="recentlyPlayed.length > 0" class="game-section">
|
||
<div class="section-header">
|
||
<h2>🕒 {{ $t('dashboard.recently_played') }}</h2>
|
||
</div>
|
||
<div class="recent-grid">
|
||
<div
|
||
v-for="g in recentlyPlayed"
|
||
:key="g.game_name"
|
||
class="recent-card"
|
||
@click="play({ name: g.game_name, slug: g.slug })"
|
||
>
|
||
<div class="recent-name">{{ g.game_name }}</div>
|
||
<div class="recent-date">{{ new Date(g.last_played_at).toLocaleDateString('de-DE') }}</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- FAQ -->
|
||
<section class="faq-section">
|
||
<div class="section-header">
|
||
<h2><HelpCircle class="w-5 h-5 text-gray-400 inline mb-1" /> FAQ</h2>
|
||
</div>
|
||
<div class="faq-list">
|
||
<div v-for="(faq, index) in faqs" :key="index" class="faq-item" :class="{ active: activeFaq === index }">
|
||
<div class="faq-question" @click="toggleFaq(index)">
|
||
{{ faq.q }}
|
||
<ChevronDown class="w-4 h-4 transition-transform duration-300" :class="{ 'rotate-180': activeFaq === index }" />
|
||
</div>
|
||
<div class="faq-answer" :class="{ open: activeFaq === index }">
|
||
<div class="faq-content">
|
||
{{ faq.a }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<!-- Game Loading Overlay -->
|
||
<Teleport to="body">
|
||
<div v-if="gameLoading" class="game-launch-overlay">
|
||
<div class="game-launch-spinner"></div>
|
||
</div>
|
||
<div v-if="gameSession" class="game-overlay" @keydown.esc="gameSession = null" tabindex="-1">
|
||
<div class="game-overlay-inner">
|
||
<div class="game-overlay-header">
|
||
<span class="game-overlay-title">{{ gameSession.game }}</span>
|
||
<button class="game-overlay-close" @click="gameSession = null" title="Close">✕</button>
|
||
</div>
|
||
<iframe
|
||
:src="gameSession.url"
|
||
class="game-iframe"
|
||
allowfullscreen
|
||
allow="autoplay"
|
||
frameborder="0"
|
||
></iframe>
|
||
</div>
|
||
</div>
|
||
</Teleport>
|
||
|
||
</UserLayout>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.dashboard-content {
|
||
padding: 30px;
|
||
max-width: 1600px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* Hero Banner */
|
||
.hero-banner {
|
||
background: linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 100%);
|
||
border: 1px solid #151515;
|
||
border-radius: 24px;
|
||
padding: 60px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
position: relative;
|
||
overflow: hidden;
|
||
margin-bottom: 40px;
|
||
box-shadow: 0 20px 50px rgba(0,0,0,0.5);
|
||
}
|
||
.hero-banner::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -50%;
|
||
left: -20%;
|
||
width: 80%;
|
||
height: 200%;
|
||
background: radial-gradient(circle, rgba(223, 0, 106, 0.15) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
}
|
||
.hero-content { z-index: 2; max-width: 500px; }
|
||
.badge { background: rgba(223, 0, 106, 0.1); color: var(--primary); padding: 6px 12px; border-radius: 50px; font-size: 12px; font-weight: 800; display: inline-block; margin-bottom: 20px; border: 1px solid rgba(223, 0, 106, 0.3); }
|
||
.hero-title { font-size: 3.5rem; font-weight: 900; line-height: 1.1; margin-bottom: 15px; color: white; }
|
||
.highlight { color: #00f2ff; text-shadow: 0 0 20px rgba(0,242,255,0.4); }
|
||
.hero-desc { color: #888; font-size: 1.1rem; margin-bottom: 30px; }
|
||
.hero-actions { display: flex; gap: 20px; align-items: center; }
|
||
.text-link { color: #666; font-size: 14px; font-weight: 600; text-decoration: underline; cursor: pointer; transition: color 0.2s; }
|
||
.text-link:hover { color: white; }
|
||
|
||
/* Floating Coin */
|
||
.floating-coin { width: 200px; height: 200px; background: linear-gradient(45deg, #ffb700, #ff8800); border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 50px rgba(255, 136, 0, 0.4), inset 0 0 20px rgba(255,255,255,0.5); animation: float 6s ease-in-out infinite; position: relative; z-index: 2; }
|
||
.coin-inner { font-size: 100px; font-weight: 900; color: rgba(255,255,255,0.9); text-shadow: 0 2px 10px rgba(0,0,0,0.2); }
|
||
@keyframes float { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-20px) rotate(5deg); } }
|
||
|
||
/* Providers Bar */
|
||
.providers-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
margin-bottom: 40px;
|
||
background: #0a0a0a;
|
||
border: 1px solid #151515;
|
||
padding: 10px 20px;
|
||
border-radius: 12px;
|
||
}
|
||
.provider-list {
|
||
flex: 1;
|
||
display: flex;
|
||
gap: 10px;
|
||
overflow-x: auto;
|
||
padding-bottom: 5px;
|
||
scrollbar-width: none;
|
||
}
|
||
.provider-list::-webkit-scrollbar { display: none; }
|
||
.provider-item {
|
||
background: #111;
|
||
border: 1px solid #222;
|
||
color: #888;
|
||
padding: 8px 16px;
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.provider-item:hover { color: white; border-color: #444; }
|
||
.provider-item.active { background: #fff; color: black; border-color: #fff; }
|
||
.filter-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: #151515;
|
||
border: 1px solid #222;
|
||
color: white;
|
||
padding: 8px 16px;
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Horizontal Slider */
|
||
.slider-track {
|
||
display: flex;
|
||
gap: 16px;
|
||
overflow-x: auto;
|
||
padding-bottom: 10px;
|
||
scroll-snap-type: x mandatory;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: #222 transparent;
|
||
}
|
||
.slider-track::-webkit-scrollbar { height: 4px; }
|
||
.slider-track::-webkit-scrollbar-track { background: transparent; }
|
||
.slider-track::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 2px; }
|
||
.slider-card {
|
||
flex: 0 0 180px;
|
||
scroll-snap-align: start;
|
||
}
|
||
|
||
/* Live badge */
|
||
.live-badge {
|
||
display: flex; align-items: center; gap: 6px;
|
||
font-size: 11px; font-weight: 800; text-transform: uppercase;
|
||
color: var(--primary); letter-spacing: 1px;
|
||
}
|
||
.live-dot {
|
||
width: 8px; height: 8px; border-radius: 50%;
|
||
background: var(--primary);
|
||
animation: pulse 1.4s ease-in-out infinite;
|
||
}
|
||
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(0.75); } }
|
||
|
||
/* Features Strip */
|
||
.features-strip {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 16px;
|
||
margin-bottom: 50px;
|
||
background: #070709;
|
||
border: 1px solid #151515;
|
||
border-radius: 20px;
|
||
padding: 32px 24px;
|
||
}
|
||
.feature-item {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
text-align: center; gap: 10px;
|
||
}
|
||
.feature-icon {
|
||
width: 56px; height: 56px; border-radius: 16px;
|
||
background: rgba(223,0,106,.08); border: 1px solid rgba(223,0,106,.15);
|
||
display: flex; align-items: center; justify-content: center;
|
||
color: var(--primary);
|
||
}
|
||
.feature-label { font-size: 13px; font-weight: 800; color: #fff; }
|
||
.feature-desc { font-size: 11px; color: #555; font-weight: 600; line-height: 1.4; }
|
||
|
||
/* Game Grid */
|
||
.game-section { margin-bottom: 50px; }
|
||
.section-header { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 20px; }
|
||
.section-header h2 { font-size: 18px; font-weight: 800; color: white; text-transform: uppercase; letter-spacing: 1px; margin: 0; }
|
||
.view-all { font-size: 12px; color: var(--primary); font-weight: 700; text-transform: uppercase; background: none; border: none; cursor: pointer; padding: 0; text-decoration: none; }
|
||
.view-all:hover { opacity: 0.75; }
|
||
|
||
/* Handwritten style for 'originals' word */
|
||
.handwritten {
|
||
font-family: 'Caveat', 'Pacifico', 'Brush Script MT', cursive;
|
||
font-weight: 900;
|
||
font-style: italic;
|
||
letter-spacing: 1px;
|
||
color: #fff;
|
||
}
|
||
|
||
.game-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; }
|
||
.game-card {
|
||
background: #0a0a0a;
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
border: 1px solid #151515;
|
||
transition: transform 0.3s cubic-bezier(0.2, 0, 0, 1), box-shadow 0.3s, border-color 0.3s;
|
||
cursor: pointer;
|
||
position: relative;
|
||
}
|
||
.game-card:hover {
|
||
transform: translateY(-6px) scale(1.01);
|
||
box-shadow: 0 20px 50px rgba(0,0,0,0.7), 0 0 0 1px rgba(223, 0, 106, 0.25), 0 0 24px rgba(223, 0, 106, 0.12);
|
||
border-color: rgba(223, 0, 106, 0.3);
|
||
z-index: 2;
|
||
}
|
||
.card-image { position: relative; aspect-ratio: 4/3; overflow: hidden; }
|
||
.card-image img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s cubic-bezier(0.2, 0, 0, 1); }
|
||
.game-card:hover img { transform: scale(1.08); filter: brightness(0.35) saturate(0.6); }
|
||
.card-overlay {
|
||
position: absolute; inset: 0;
|
||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||
opacity: 0; transition: opacity 0.3s;
|
||
background: rgba(5, 5, 5, 0.45);
|
||
backdrop-filter: blur(3px) saturate(1.2);
|
||
}
|
||
.game-card:hover .card-overlay { opacity: 1; }
|
||
.play-btn {
|
||
width: 60px; height: 60px; border-radius: 50%;
|
||
background: rgba(255,255,255,0.08);
|
||
backdrop-filter: blur(8px);
|
||
border: 1.5px solid rgba(255,255,255,0.2);
|
||
color: white; display: flex; align-items: center; justify-content: center;
|
||
margin-bottom: 10px; transform: scale(0.75);
|
||
transition: transform 0.25s cubic-bezier(0.2, 0, 0, 1), background 0.2s, border-color 0.2s;
|
||
padding: 0;
|
||
box-shadow: 0 0 20px rgba(223, 0, 106, 0.2);
|
||
}
|
||
.game-card:hover .play-btn { transform: scale(1); }
|
||
.play-btn:hover {
|
||
background: rgba(223, 0, 106, 0.3);
|
||
border-color: rgba(223, 0, 106, 0.6);
|
||
box-shadow: 0 0 30px rgba(223, 0, 106, 0.4);
|
||
}
|
||
.provider {
|
||
font-size: 10px; font-weight: 800; color: rgba(255,255,255,0.9);
|
||
text-transform: uppercase; letter-spacing: 1.5px;
|
||
background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
|
||
padding: 3px 8px; border-radius: 20px; border: 1px solid rgba(255,255,255,0.1);
|
||
}
|
||
.tag {
|
||
position: absolute; top: 10px; left: 10px;
|
||
padding: 3px 8px; border-radius: 5px;
|
||
font-size: 9px; font-weight: 900; text-transform: uppercase;
|
||
z-index: 2; background: var(--primary); color: white;
|
||
box-shadow: 0 2px 10px rgba(223, 0, 106, 0.4);
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.rtp-badge {
|
||
position: absolute; bottom: 10px; right: 10px;
|
||
background: rgba(0,0,0,0.75); backdrop-filter: blur(4px);
|
||
color: #bbb; padding: 2px 7px; border-radius: 5px;
|
||
font-size: 9px; font-weight: 700;
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
}
|
||
.card-info { padding: 12px; background: #0a0a0a; border-top: 1px solid #151515; }
|
||
.game-name { font-size: 13px; font-weight: 700; color: white; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
|
||
/* Wins Section */
|
||
.wins-section { margin-bottom: 50px; }
|
||
.wins-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 15px; }
|
||
.win-card {
|
||
background: rgba(10, 10, 10, 0.8);
|
||
backdrop-filter: blur(6px);
|
||
border: 1px solid #1a1a1a;
|
||
border-radius: 12px; padding: 12px 15px;
|
||
display: flex; align-items: center; gap: 12px;
|
||
transition: all 0.25s cubic-bezier(0.2, 0, 0, 1);
|
||
}
|
||
.win-card:hover {
|
||
border-color: rgba(223, 0, 106, 0.25);
|
||
background: rgba(18, 5, 12, 0.9);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 24px rgba(0,0,0,0.5), 0 0 12px rgba(223,0,106,0.06);
|
||
}
|
||
.win-icon { flex-shrink: 0; }
|
||
.win-details { flex: 1; overflow: hidden; }
|
||
.win-user { font-size: 13px; font-weight: 700; color: #fff; }
|
||
.win-game { font-size: 11px; color: #666; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.win-value { font-size: 13px; font-weight: 800; color: #00ff9d; text-align: right; }
|
||
|
||
|
||
/* Hits Section (Hall of Fame) */
|
||
.hits-section { margin-bottom: 50px; }
|
||
.hits-card {
|
||
background: #0a0a0a;
|
||
border: 1px solid #151515;
|
||
border-radius: 20px;
|
||
padding: 20px;
|
||
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
||
}
|
||
.hits-container {
|
||
display: grid;
|
||
grid-template-columns: 1.5fr 1fr;
|
||
gap: 20px;
|
||
}
|
||
.hit-hero {
|
||
position: relative;
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
border: 1px solid #ffb700;
|
||
box-shadow: 0 0 30px rgba(255, 183, 0, 0.15);
|
||
height: 220px; /* Reduced height */
|
||
}
|
||
.hit-hero-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: relative;
|
||
}
|
||
.hit-hero-image img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
transition: transform 0.5s;
|
||
}
|
||
.hit-hero:hover img { transform: scale(1.05); }
|
||
.hit-hero-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(to top, rgba(0,0,0,0.95), transparent);
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-end;
|
||
padding: 20px;
|
||
}
|
||
.rank-badge {
|
||
position: absolute; top: 15px; left: 15px;
|
||
background: linear-gradient(135deg, #ffb700, #ff8800);
|
||
color: black;
|
||
font-size: 14px; font-weight: 900;
|
||
padding: 6px 12px; border-radius: 6px;
|
||
box-shadow: 0 0 20px rgba(255, 183, 0, 0.6);
|
||
}
|
||
.hit-hero-multi { font-size: 36px; font-weight: 900; color: #ffb700; text-shadow: 0 0 20px rgba(255, 183, 0, 0.4); line-height: 1; margin-bottom: 8px; }
|
||
.hit-hero-amount { font-size: 18px; font-weight: 800; color: #00ff9d; margin-bottom: 4px; }
|
||
.hit-hero-user { font-size: 12px; color: #ccc; font-weight: 600; }
|
||
|
||
.hits-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px; /* Reduced gap */
|
||
}
|
||
.hit-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
background: #111;
|
||
border: 1px solid #222;
|
||
padding: 8px 12px; /* Reduced padding */
|
||
border-radius: 10px;
|
||
transition: all 0.3s;
|
||
}
|
||
.hit-item:hover {
|
||
border-color: #444;
|
||
background: #151515;
|
||
transform: translateX(5px);
|
||
}
|
||
.hit-rank {
|
||
font-size: 14px; font-weight: 900; color: #444; width: 25px; text-align: center;
|
||
}
|
||
.hit-thumb {
|
||
width: 40px; height: 30px; border-radius: 6px; overflow: hidden;
|
||
}
|
||
.hit-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
||
.hit-details { flex: 1; }
|
||
.hit-multi { font-size: 13px; font-weight: 800; color: #ffb700; }
|
||
.hit-game-name { font-size: 10px; color: #666; font-weight: 600; }
|
||
.hit-amount-small { font-size: 12px; font-weight: 800; color: #00ff9d; }
|
||
|
||
/* FAQ */
|
||
.faq-section { margin-bottom: 50px; max-width: 800px; margin-left: auto; margin-right: auto; }
|
||
.faq-item { background: #0a0a0a; border: 1px solid #151515; border-radius: 12px; margin-bottom: 10px; overflow: hidden; }
|
||
.faq-item.active { border-color: #333; }
|
||
.faq-question { padding: 15px 20px; font-size: 14px; font-weight: 700; color: #ccc; cursor: pointer; display: flex; justify-content: space-between; align-items: center; }
|
||
.faq-question:hover { color: white; }
|
||
.faq-answer {
|
||
max-height: 0;
|
||
overflow: hidden;
|
||
transition: max-height 0.3s ease-out;
|
||
}
|
||
.faq-answer.open {
|
||
max-height: 200px; /* Adjust based on content */
|
||
}
|
||
.faq-content {
|
||
padding: 0 20px 20px; font-size: 13px; color: #888; line-height: 1.5;
|
||
}
|
||
|
||
/* Neon Button */
|
||
.neon-button { background: linear-gradient(90deg, var(--primary), #a3004d); border: none; position: relative; overflow: hidden; transition: all 0.3s ease; color: white; }
|
||
.neon-button:hover { transform: translateY(-2px); box-shadow: 0 0 30px rgba(223, 0, 106, 0.6); filter: brightness(1.1); }
|
||
|
||
@media (max-width: 1000px) {
|
||
.dashboard-content { padding: 12px; }
|
||
.hero-banner {
|
||
flex-direction: column;
|
||
padding: 24px;
|
||
min-height: auto;
|
||
gap: 20px;
|
||
text-align: center;
|
||
}
|
||
.hero-content { padding: 0; }
|
||
.hero-title { font-size: 28px; }
|
||
.hero-desc { font-size: 14px; }
|
||
.hero-image { display: none; }
|
||
.hero-actions { justify-content: center; }
|
||
|
||
.providers-bar {
|
||
padding: 10px;
|
||
margin-bottom: 20px;
|
||
position: sticky;
|
||
top: 64px;
|
||
z-index: 800;
|
||
background: rgba(5,5,5,0.9);
|
||
backdrop-filter: blur(10px);
|
||
border-radius: 0;
|
||
margin: -12px -12px 20px -12px;
|
||
}
|
||
.provider-list { gap: 8px; }
|
||
.provider-item { padding: 6px 12px; font-size: 11px; }
|
||
.features-strip { grid-template-columns: repeat(2, 1fr); }
|
||
|
||
.game-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 12px;
|
||
}
|
||
.section-header h2 { font-size: 16px; }
|
||
.game-name { font-size: 12px; }
|
||
|
||
.wins-grid { grid-template-columns: 1fr; }
|
||
.hits-container { grid-template-columns: 1fr; }
|
||
.hit-hero { height: 250px; }
|
||
|
||
.footer-grid { grid-template-columns: 1fr; text-align: center; gap: 30px; }
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.hero-title { font-size: 24px; }
|
||
.game-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
|
||
.card-info { padding: 8px; }
|
||
.features-strip { grid-template-columns: repeat(2, 1fr); padding: 20px 16px; gap: 12px; }
|
||
.slider-card { flex: 0 0 150px; }
|
||
.recent-grid { gap: 8px; }
|
||
.recent-card { min-width: calc(50% - 4px); }
|
||
}
|
||
|
||
|
||
/* Favorite Button */
|
||
.fav-btn {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
background: rgba(0,0,0,0.6);
|
||
border: 1px solid #333;
|
||
border-radius: 8px;
|
||
padding: 5px;
|
||
cursor: pointer;
|
||
color: #666;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: color 0.2s, background 0.2s, border-color 0.2s;
|
||
z-index: 10;
|
||
}
|
||
.fav-btn:hover { color: #ff4d80; border-color: #ff4d80; background: rgba(255,77,128,0.12); }
|
||
.fav-btn.active { color: #ff4d80; border-color: #ff4d80; background: rgba(255,77,128,0.15); }
|
||
|
||
/* Recently Played */
|
||
.recent-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
.recent-card {
|
||
background: #0d0d0d;
|
||
border: 1px solid #1a1a1a;
|
||
border-radius: 12px;
|
||
padding: 14px 20px;
|
||
cursor: pointer;
|
||
transition: border-color 0.2s, transform 0.15s;
|
||
min-width: 140px;
|
||
}
|
||
.recent-card:hover {
|
||
border-color: rgba(223, 0, 106, 0.5);
|
||
transform: translateY(-2px);
|
||
}
|
||
.recent-name {
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
color: #fff;
|
||
margin-bottom: 4px;
|
||
}
|
||
.recent-date {
|
||
font-size: 11px;
|
||
color: #555;
|
||
}
|
||
|
||
/* Game Launch Loading Overlay */
|
||
.game-launch-overlay {
|
||
position: fixed; inset: 0; z-index: 9000;
|
||
background: rgba(0,0,0,0.7);
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.game-launch-spinner {
|
||
width: 48px; height: 48px; border-radius: 50%;
|
||
border: 3px solid #222;
|
||
border-top-color: var(--primary);
|
||
animation: spin 0.7s linear infinite;
|
||
}
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
|
||
/* Iframe Game Overlay */
|
||
.game-overlay {
|
||
position: fixed; inset: 0; z-index: 9001;
|
||
background: rgba(0,0,0,0.92);
|
||
display: flex; align-items: center; justify-content: center;
|
||
padding: 16px;
|
||
}
|
||
.game-overlay-inner {
|
||
width: 100%; max-width: 1200px; height: 90vh;
|
||
display: flex; flex-direction: column;
|
||
background: #0a0a0a;
|
||
border-radius: 16px;
|
||
border: 1px solid #222;
|
||
overflow: hidden;
|
||
box-shadow: 0 0 80px rgba(0,0,0,0.8);
|
||
}
|
||
.game-overlay-header {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
padding: 12px 20px;
|
||
background: #111;
|
||
border-bottom: 1px solid #1a1a1a;
|
||
flex-shrink: 0;
|
||
}
|
||
.game-overlay-title {
|
||
font-size: 15px; font-weight: 800; color: #fff; text-transform: uppercase; letter-spacing: 1px;
|
||
}
|
||
.game-overlay-close {
|
||
width: 32px; height: 32px;
|
||
background: #1a1a1a; border: 1px solid #333; border-radius: 8px;
|
||
color: #888; font-size: 16px; cursor: pointer;
|
||
display: flex; align-items: center; justify-content: center;
|
||
transition: color 0.2s, border-color 0.2s;
|
||
}
|
||
.game-overlay-close:hover { color: #fff; border-color: var(--primary); }
|
||
.game-iframe {
|
||
flex: 1; width: 100%; border: none;
|
||
}
|
||
@media (max-width: 600px) {
|
||
.game-overlay { padding: 0; }
|
||
.game-overlay-inner { border-radius: 0; height: 100vh; max-width: 100%; }
|
||
}
|
||
</style>
|