Files
bratanbonus/resources/js/components/Welcome/BonusSection.vue
T
Dolo 79bea8cf56
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
Neuaufbau des Repositories
2026-04-10 21:14:11 +02:00

280 lines
12 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
interface Badge {
label: string;
class: string;
}
interface Bonus {
id: number;
name: string;
description: string;
image_path: string;
brand_color: string;
hover_color: string;
badges: Badge[];
min_deposit: string;
max_bet: string;
wagering: string;
free_spins: string;
button_link: string;
key_features: string[];
is_sticky: boolean;
is_no_deposit: boolean;
is_featured: boolean;
}
const props = defineProps<{
bonuses?: Bonus[];
}>();
const bonusGridRef = ref<HTMLElement | null>(null);
const handleMouseMoveGrid = (e: MouseEvent) => {
if (!bonusGridRef.value) return;
const gridRect = bonusGridRef.value.getBoundingClientRect();
const mx = e.clientX - gridRect.left;
const my = e.clientY - gridRect.top;
bonusGridRef.value.style.setProperty('--mouse-x', `${mx}px`);
bonusGridRef.value.style.setProperty('--mouse-y', `${my}px`);
const cards = bonusGridRef.value.querySelectorAll('.bonus-card') as NodeListOf<HTMLElement>;
cards.forEach(card => {
card.style.setProperty('--card-left', `${card.offsetLeft}`);
card.style.setProperty('--card-top', `${card.offsetTop}`);
});
};
const trackClick = async (bonusId: number) => {
try {
await fetch('/api/track', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
},
body: JSON.stringify({
bonus_id: bonusId,
type: 'click'
})
});
} catch (e) {
console.error('Tracking failed', e);
}
};
const trackView = async (bonusId: number) => {
try {
await fetch('/api/track', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
},
body: JSON.stringify({
bonus_id: bonusId,
type: 'view'
})
});
} catch (e) {
console.error('Tracking failed', e);
}
};
onMounted(() => {
if (bonusGridRef.value) {
bonusGridRef.value.addEventListener('mousemove', handleMouseMoveGrid);
}
if (props.bonuses && props.bonuses.length > 0) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const card = entry.target as HTMLElement;
const bonusId = card.dataset.id;
if (bonusId && !card.dataset.viewed) {
trackView(Number(bonusId));
card.dataset.viewed = 'true';
}
}
});
}, { threshold: 0.5 });
setTimeout(() => {
if (bonusGridRef.value) {
const cards = bonusGridRef.value.querySelectorAll('.bonus-card');
cards.forEach(card => observer.observe(card));
}
}, 100);
}
});
onUnmounted(() => {
if (bonusGridRef.value) {
bonusGridRef.value.removeEventListener('mousemove', handleMouseMoveGrid);
}
});
</script>
<template>
<section id="bonuses" class="container mx-auto px-6 py-28 relative z-30">
<h2 class="text-5xl font-black italic text-center mb-20 uppercase tracking-tighter">Premium <span class="text-blue-500">Deals</span></h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 mb-16 items-end bg-gray-950/50 p-6 rounded-3xl border border-white/5 backdrop-blur-sm">
<div class="xl:col-span-2 relative">
<label class="text-xs text-gray-500 mb-2 block uppercase font-bold tracking-wider">Schnellsuche</label>
<div class="relative">
<i class="fas fa-search absolute left-5 top-1/2 -translate-y-1/2 text-gray-600"></i>
<input type="text" placeholder="Casino Name oder Bonusart..." class="w-full bg-white/5 border border-white/10 rounded-xl py-4 pl-14 outline-none focus:border-purple-500 transition focus:ring-1 focus:ring-purple-500 text-white placeholder-gray-500">
</div>
</div>
<div class="relative min-w-[200px] group">
<label class="text-xs text-gray-500 mb-2 block uppercase font-bold tracking-wider">Land</label>
<div class="bg-white/5 border border-white/10 p-[14px_20px] rounded-xl cursor-pointer flex justify-between items-center transition group-hover:border-white/30 text-white relative z-10">
<span>🇩🇪 Germany</span>
<i class="fas fa-chevron-down opacity-50 text-xs transition-transform"></i>
</div>
<div class="absolute top-[115%] left-0 right-0 bg-[#111827] border border-white/10 rounded-xl hidden group-hover:block z-50 overflow-hidden shadow-[0_10px_25px_rgba(0,0,0,0.5)]">
<div class="p-[12px_20px] cursor-pointer transition hover:bg-white/5">🇩🇪 Germany</div>
<div class="p-[12px_20px] cursor-pointer transition hover:bg-white/5">🇦🇹 Austria</div>
<div class="p-[12px_20px] cursor-pointer transition hover:bg-white/5">🇨🇭 Switzerland</div>
</div>
</div>
<div class="relative min-w-[200px] group">
<label class="text-xs text-gray-500 mb-2 block uppercase font-bold tracking-wider">Methode</label>
<div class="bg-white/5 border border-white/10 p-[14px_20px] rounded-xl cursor-pointer flex justify-between items-center transition group-hover:border-white/30 text-white relative z-10">
<span><i class="fas fa-wallet text-blue-400 mr-2"></i> Hybrid</span>
<i class="fas fa-chevron-down opacity-50 text-xs transition-transform"></i>
</div>
<div class="absolute top-[115%] left-0 right-0 bg-[#111827] border border-white/10 rounded-xl hidden group-hover:block z-50 overflow-hidden shadow-[0_10px_25px_rgba(0,0,0,0.5)]">
<div class="p-[12px_20px] cursor-pointer transition hover:bg-white/5"><i class="fas fa-wallet text-blue-400 mr-2"></i> Hybrid</div>
<div class="p-[12px_20px] cursor-pointer transition hover:bg-white/5"><i class="fab fa-bitcoin text-orange-400 mr-2"></i> Crypto</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10 bonus-grid relative" ref="bonusGridRef">
<div v-for="bonus in bonuses" :key="bonus.id"
:data-id="bonus.id"
:class="[
'bonus-card relative bg-[#111827] rounded-[28px] border overflow-hidden transition-transform duration-400 hover:-translate-y-2 hover:scale-[1.01] h-full z-[1] flex flex-col group',
bonus.is_featured ? 'border-yellow-500/50 shadow-lg shadow-yellow-500/20' : 'border-white/5'
]"
:style="{ '--spot-clr': bonus.hover_color || 'rgba(255,255,255,0.1)' }">
<img v-if="bonus.image_path" :src="bonus.image_path" class="absolute inset-0 w-full h-full object-cover opacity-15 z-[-1] transition-opacity duration-300 group-hover:opacity-25" :alt="bonus.name">
<div class="spotlight"></div>
<div class="p-8 relative z-10 flex flex-col h-full">
<h3 class="text-4xl font-black italic tracking-tighter mb-2" :style="{ color: bonus.brand_color || '#ffffff' }">
{{ bonus.name }}
</h3>
<p class="text-gray-300 text-sm leading-relaxed mb-6">{{ bonus.description }}</p>
<div class="flex flex-wrap gap-2 mb-6">
<span v-if="bonus.is_sticky" class="px-3 py-1 bg-amber-500/20 text-amber-300 border border-amber-500/30 rounded-full text-[10px] font-bold uppercase tracking-wider">Sticky</span>
<span v-if="bonus.is_no_deposit" class="px-3 py-1 bg-cyan-500/20 text-cyan-300 border border-cyan-500/30 rounded-full text-[10px] font-bold uppercase tracking-wider">No Deposit</span>
<span v-if="bonus.is_featured" class="px-3 py-1 bg-yellow-500/20 text-yellow-300 border border-yellow-500/30 rounded-full text-[10px] font-bold uppercase tracking-wider">Featured</span>
<span v-for="(badge, index) in bonus.badges" :key="index"
class="px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider bg-white/10 text-white border border-white/20">
{{ typeof badge === 'string' ? badge : badge.label }}
</span>
</div>
<ul v-if="bonus.key_features && bonus.key_features.length" class="mb-8 space-y-1">
<li v-for="feature in bonus.key_features" :key="feature" class="text-[11px] text-gray-400 flex items-center">
<i class="fas fa-check text-green-500 mr-2 text-[9px]"></i> {{ feature }}
</li>
</ul>
<div class="mt-auto space-y-3 text-xs text-gray-400 border-t border-white/10 pt-6 mb-8">
<div class="flex justify-between"><span>Min Deposit</span><span class="text-white font-bold">{{ bonus.min_deposit || 'N/A' }}</span></div>
<div class="flex justify-between"><span>Wagering</span><span class="text-white font-bold">{{ bonus.wagering || 'N/A' }}</span></div>
<div class="flex justify-between"><span>Max Bet</span><span class="text-white font-bold">{{ bonus.max_bet || 'N/A' }}</span></div>
<div v-if="bonus.free_spins" class="flex justify-between"><span>Free Spins</span><span class="text-white font-bold">{{ bonus.free_spins }}</span></div>
</div>
<div class="flex gap-3">
<a :href="bonus.button_link" target="_blank" @click="trackClick(bonus.id)"
class="grow bg-white text-black font-black py-4 rounded-xl hover:text-white transition uppercase tracking-tighter cursor-pointer text-center"
:style="{ '--hover-bg': bonus.brand_color || '#a855f7' }">
Deal Sichern
</a>
<button class="bg-white/5 border border-white/10 hover:bg-white/15 hover:border-white transition-all duration-300 px-5 rounded-xl text-white cursor-pointer" title="Mehr Infos"><i class="fas fa-info"></i></button>
</div>
</div>
</div>
<div v-if="!bonuses || bonuses.length === 0" class="col-span-full text-center py-20 text-gray-500">
Keine Deals gefunden.
</div>
</div>
</section>
</template>
<style scoped>
.bonus-grid {
--mouse-x: -1000px;
--mouse-y: -1000px;
}
.bonus-card {
--spot-clr: rgba(255,255,255,0.1);
}
.spotlight {
position: absolute;
width: 600px;
height: 600px;
background: radial-gradient(circle, var(--spot-clr) 0%, transparent 70%);
pointer-events: none;
mix-blend-mode: screen;
z-index: 0;
opacity: 0;
left: calc(var(--mouse-x) - (var(--card-left, 0) * 1px));
top: calc(var(--mouse-y) - (var(--card-top, 0) * 1px));
transform: translate(-50%, -50%);
transition: opacity 0.3s ease;
}
.bonus-grid:hover .spotlight {
opacity: 0.35;
}
.bonus-card::before {
content: "";
position: absolute;
inset: 0;
border-radius: 28px;
padding: 2px;
background: radial-gradient(
350px circle at calc(var(--mouse-x) - var(--card-left, 0) * 1px) calc(var(--mouse-y) - var(--card-top, 0) * 1px),
var(--spot-clr),
transparent 80%
);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
z-index: 5;
opacity: 0;
transition: opacity 0.3s;
}
.bonus-grid:hover .bonus-card::before {
opacity: 1;
}
a[style*="--hover-bg"]:hover {
background-color: var(--hover-bg) !important;
}
</style>