Neuaufbau des Repositories
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

This commit is contained in:
2026-04-10 21:14:11 +02:00
parent 3f61033d14
commit 79bea8cf56
309 changed files with 31416 additions and 0 deletions
@@ -0,0 +1,102 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
interface Star {
x: number;
y: number;
size: number;
opacity: number;
speed: number;
}
const starCanvasRef = ref<HTMLCanvasElement | null>(null);
let backgroundStars: Star[] = [];
let animationFrameId: number;
function initBackgroundStars() {
if (typeof window === 'undefined' || !starCanvasRef.value) return;
const canvas = starCanvasRef.value;
// Explizite Zuweisung der Fenstergröße an die Canvas-Attribute
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
backgroundStars = [];
const count = Math.min(window.innerWidth / 3, 400); // Etwas mehr Sterne für bessere Sichtbarkeit
for (let i = 0; i < count; i++) {
backgroundStars.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 1.5 + 0.5,
opacity: Math.random(),
speed: Math.random() * 0.15 + 0.05,
});
}
}
function drawBackgroundStars() {
if (typeof window === 'undefined') return;
const canvas = starCanvasRef.value;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < backgroundStars.length; i++) {
const s = backgroundStars[i];
s.opacity += (Math.random() - 0.5) * 0.03;
if (s.opacity <= 0.1) s.opacity = 0.1;
if (s.opacity >= 0.8) s.opacity = 0.8;
ctx.fillStyle = `rgba(255, 255, 255, ${s.opacity})`;
ctx.beginPath();
ctx.arc(s.x, s.y, s.size, 0, Math.PI * 2);
ctx.fill();
s.y -= s.speed;
if (s.y < -10) {
s.y = canvas.height + 10;
s.x = Math.random() * canvas.width;
}
}
animationFrameId = requestAnimationFrame(drawBackgroundStars);
}
const handleResize = () => {
initBackgroundStars();
};
onMounted(() => {
initBackgroundStars();
drawBackgroundStars();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (typeof window !== 'undefined') {
window.removeEventListener('resize', handleResize);
}
if (animationFrameId) cancelAnimationFrame(animationFrameId);
});
</script>
<template>
<div
class="pointer-events-none fixed inset-0 h-full w-full"
style="z-index: 0"
>
<div class="absolute inset-0 bg-[#020617]"></div>
<canvas
ref="starCanvasRef"
class="absolute inset-0 block h-full w-full"
></canvas>
<div
class="absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,#020617_100%)] opacity-60"
></div>
</div>
</template>
@@ -0,0 +1,279 @@
<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>
@@ -0,0 +1,216 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
const heroCanvasRef = ref<HTMLCanvasElement | null>(null);
let particles: Particle[] = [];
const mouse = { x: -1000, y: -1000, radius: 120 };
let animationFrameIdHero: number;
const handleGlobalMouseMove = (e: MouseEvent) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
};
class Particle {
baseX: number;
baseY: number;
x: number;
y: number;
size: number;
isWhite: boolean;
blinkTimer: number;
opacity: number;
ease: number;
constructor(
x: number,
y: number,
private canvasWidth: number,
private canvasHeight: number,
) {
this.baseX = x;
this.baseY = y;
this.x = Math.random() * canvasWidth;
this.y = Math.random() * canvasHeight;
this.size = 1.6;
this.isWhite = false;
this.blinkTimer = 0;
this.opacity = 1;
this.ease = 0.06;
}
draw(ctx: CanvasRenderingContext2D) {
if (this.isWhite) {
this.opacity = Math.sin(this.blinkTimer) * 0.5 + 0.5;
ctx.fillStyle = `rgba(255, 255, 255, ${Math.max(0.3, this.opacity)})`;
this.blinkTimer += 0.1;
if (this.blinkTimer > Math.PI) {
this.isWhite = false;
this.opacity = 1;
}
} else {
const ratio = this.x / this.canvasWidth;
const r = 168 - (168 - 59) * ratio;
const g = 85 + (130 - 85) * ratio;
ctx.fillStyle = `rgb(${r},${g},255)`;
}
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
update() {
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < mouse.radius) {
const force = (mouse.radius - distance) / mouse.radius;
this.x -= (dx / distance) * force * 7;
this.y -= (dy / distance) * force * 7;
} else {
this.x += (this.baseX - this.x) * this.ease;
this.y += (this.baseY - this.y) * this.ease;
}
}
}
function initHero() {
if (!heroCanvasRef.value) return;
const canvas = heroCanvasRef.value;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const tCanvas = document.createElement('canvas');
const tCtx = tCanvas.getContext('2d');
if (!tCtx) return;
tCanvas.width = canvas.width;
tCanvas.height = canvas.height;
// Wir verschieben den Text im Partikel-Canvas etwas nach unten,
// um Platz für das "Welcome" darüber zu schaffen
const fontSize = Math.min(canvas.width / 9, 90);
tCtx.fillStyle = 'white';
tCtx.font = `bold ${fontSize}px Arial`;
tCtx.textAlign = 'center';
tCtx.fillText(
'BRATANBONUS.NET',
tCanvas.width / 2,
tCanvas.height / 2 + 40,
);
const pixels = tCtx.getImageData(0, 0, tCanvas.width, tCanvas.height);
particles = [];
const step = 5;
for (let y = 0; y < pixels.height; y += step) {
for (let x = 0; x < pixels.width; x += step) {
if (pixels.data[y * 4 * pixels.width + x * 4 + 3] > 128) {
particles.push(new Particle(x, y, canvas.width, canvas.height));
}
}
}
}
function animateHero() {
if (!heroCanvasRef.value) return;
const ctx = heroCanvasRef.value.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, heroCanvasRef.value.width, heroCanvasRef.value.height);
if (Math.random() > 0.93 && particles.length > 0) {
const p = particles[Math.floor(Math.random() * particles.length)];
if (!p && !p.isWhite) {
p.isWhite = true;
p.blinkTimer = 0;
}
}
particles.forEach((p) => {
p.update();
p.draw(ctx);
});
animationFrameIdHero = requestAnimationFrame(animateHero);
}
const handleResize = () => {
initHero();
};
onMounted(() => {
window.addEventListener('mousemove', handleGlobalMouseMove);
window.addEventListener('resize', handleResize);
setTimeout(() => {
initHero();
animateHero();
}, 100);
});
onUnmounted(() => {
window.removeEventListener('mousemove', handleGlobalMouseMove);
window.removeEventListener('resize', handleResize);
if (animationFrameIdHero) cancelAnimationFrame(animationFrameIdHero);
});
</script>
<template>
<section
class="relative flex h-screen flex-col items-center justify-center overflow-hidden bg-transparent"
>
<div
class="pointer-events-none absolute top-[32%] left-1/2 z-10 w-full -translate-x-1/2 text-center"
>
<h2
class="text-2xl font-thin tracking-[1em] uppercase opacity-30 select-none md:text-4xl"
>
Welcome
</h2>
</div>
<canvas
ref="heroCanvasRef"
class="pointer-events-none absolute inset-0 z-20 block h-screen w-full bg-transparent"
></canvas>
<a
href="#bonuses"
class="group absolute bottom-10 left-1/2 z-30 flex -translate-x-1/2 flex-col items-center gap-2 opacity-50 transition-all duration-300 hover:opacity-100"
>
<span
class="text-[10px] font-bold tracking-[0.3em] text-white/40 uppercase group-hover:text-white/80"
>Scroll</span
>
<div
class="flex h-10 w-6 justify-center rounded-full border-2 border-white/20 p-1"
>
<div
class="animate-scroll-dot h-2 w-1 rounded-full bg-purple-500"
></div>
</div>
<i
class="fas fa-chevron-down mt-1 animate-bounce text-sm text-white/30"
></i>
</a>
</section>
</template>
<style scoped>
@keyframes scroll-dot {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(15px);
opacity: 0;
}
}
.animate-scroll-dot {
animation: scroll-dot 2s infinite;
}
/* Verhindert Text-Markierung während man mit der Maus über den Canvas fährt */
.select-none {
user-select: none;
}
</style>
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3';
</script>
<template>
<section class="relative z-10 py-12 px-6">
<div class="container mx-auto max-w-6xl">
<div
class="relative overflow-hidden rounded-3xl border border-white/10 bg-[#0f172a]/50 p-8 shadow-2xl backdrop-blur-sm sm:p-12"
>
<div
class="absolute -top-24 -right-24 h-48 w-48 rounded-full bg-purple-500/20 blur-[80px]"
></div>
<div
class="absolute -bottom-24 -left-24 h-48 w-48 rounded-full bg-blue-500/20 blur-[80px]"
></div>
<div class="relative flex flex-col items-center justify-between gap-8 text-center md:flex-row md:text-left">
<div class="max-w-2xl">
<h2 class="mb-4 text-3xl font-black italic tracking-tight text-white md:text-4xl">
<span class="bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">NEU:</span> DAS LEADERBOARD
</h2>
<p class="text-lg text-gray-300">
Miss dich mit der Community, sammle Punkte und klettere an die Spitze.
Wer wird der ultimative Bratan?
</p>
</div>
<div class="flex-shrink-0">
<Link
href="/leaderboard"
class="group relative inline-flex items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-purple-500 to-blue-500 p-1 font-bold text-white transition-all duration-300 hover:scale-105 hover:shadow-[0_0_2rem_-0.5rem_#a855f7]"
>
<span class="relative rounded-full bg-[#0f172a] px-8 py-4 transition-all duration-300 group-hover:bg-transparent">
Zum Leaderboard <i class="fa-solid fa-arrow-right ml-2 transition-transform group-hover:translate-x-1"></i>
</span>
</Link>
</div>
</div>
</div>
</div>
</section>
</template>
@@ -0,0 +1,200 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import axios from 'axios';
const isTwitchLive = ref(false);
const isKickLive = ref(false);
const checkLiveStatus = async () => {
try {
const { data } = await axios.get('/api/live-status');
isTwitchLive.value = data.twitch;
isKickLive.value = data.kick;
} catch (e) {
console.error('Status-Check fehlgeschlagen', e);
}
};
const trackSocial = async (platform: string) => {
try {
await axios.post('/api/track-social', { platform });
} catch (e) {
console.error('Tracking failed', e);
}
};
onMounted(() => {
checkLiveStatus();
// Alle 5 Minuten im Hintergrund aktualisieren
setInterval(checkLiveStatus, 300000);
});
</script>
<template>
<section
id="find-me"
class="social-section relative z-30 bg-gray-950/20 py-32"
>
<div class="container mx-auto px-6">
<h2
class="mb-24 text-center text-5xl font-black tracking-tighter text-white uppercase italic"
>
Join the <span class="text-purple-500">Squad</span>
</h2>
<div
class="mx-auto grid max-w-7xl grid-cols-1 gap-8 md:grid-cols-3"
>
<a
href="https://www.twitch.tv/bratander1ste"
target="_blank"
@click="trackSocial('twitch')"
class="social-card group twitch flex cursor-pointer flex-col items-center gap-6 rounded-[30px] border border-white/10 bg-white/5 p-8 transition-all duration-500 ease-out hover:border-[#9146FF] hover:bg-white/10"
:class="{ 'live-active-twitch': isTwitchLive }"
>
<div v-if="isTwitchLive" class="live-indicator">
<span class="pulse-dot"></span> LIVE
</div>
<i
class="fab fa-twitch text-6xl text-[#9146FF] drop-shadow-[0_0_15px_rgba(145,70,255,0.4)] transition-transform duration-500 group-hover:scale-110"
></i>
<div class="text-center">
<h4
class="mb-2 text-2xl font-black tracking-tight text-white"
>
TWITCH
</h4>
<p class="text-sm font-medium text-gray-400">
Action jeden Abend live
</p>
</div>
</a>
<a
href="#"
@click="trackSocial('instagram')"
class="social-card group instagram flex cursor-pointer flex-col items-center gap-6 rounded-[30px] border border-white/10 bg-white/5 p-8 transition-all duration-500 ease-out hover:border-[#E1306C] hover:bg-white/10"
>
<i
class="fab fa-instagram text-6xl text-pink-500 drop-shadow-[0_0_15px_rgba(236,72,153,0.4)] transition-transform duration-500 group-hover:scale-110"
></i>
<div class="text-center">
<h4
class="mb-2 text-2xl font-black tracking-tight text-white"
>
INSTAGRAM
</h4>
<p class="text-sm font-medium text-gray-400">
News & Giveaways
</p>
</div>
</a>
<a
href="https://kick.com/Bratander1ste"
target="_blank"
@click="trackSocial('kick')"
class="social-card group kick flex cursor-pointer flex-col items-center gap-6 rounded-[30px] border border-white/10 bg-white/5 p-8 transition-all duration-500 ease-out hover:border-[#53FC18] hover:bg-white/10"
:class="{ 'live-active-kick': isKickLive }"
>
<div v-if="isKickLive" class="live-indicator">
<span class="pulse-dot"></span> LIVE
</div>
<i
class="fas fa-bolt text-6xl text-[#53FC18] drop-shadow-[0_0_15px_rgba(83,252,24,0.4)] transition-transform duration-500 group-hover:scale-110"
></i>
<div class="text-center">
<h4
class="mb-2 text-2xl font-black tracking-tight text-[#53FC18]"
>
KICK
</h4>
<p class="text-sm font-medium text-gray-400">
The home of high stakes
</p>
</div>
</a>
</div>
</div>
</section>
</template>
<style scoped>
.social-section {
perspective: 2000px;
}
.social-card {
transform-style: preserve-3d;
position: relative;
}
/* Initiale 3D Lage */
.social-card.twitch {
transform: rotateY(-10deg) rotateX(5deg);
}
.social-card.kick {
transform: rotateY(10deg) rotateX(5deg);
}
.social-card:hover {
transform: rotateY(0deg) rotateX(0deg) translateZ(20px) !important;
}
/* LIVE INDICATOR */
.live-indicator {
position: absolute;
top: 20px;
right: 20px;
background: rgba(255, 0, 0, 0.15);
border: 1px solid #ff0000;
color: #ff4d4d;
padding: 4px 12px;
border-radius: 99px;
font-size: 11px;
font-weight: 900;
display: flex;
align-items: center;
gap: 6px;
box-shadow: 0 0 15px rgba(255, 0, 0, 0.4);
z-index: 50;
}
.pulse-dot {
width: 8px;
height: 8px;
background: #ff0000;
border-radius: 50%;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(0.9);
opacity: 1;
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
}
70% {
transform: scale(1);
opacity: 0.8;
box-shadow: 0 0 0 10px rgba(255, 0, 0, 0);
}
100% {
transform: scale(0.9);
opacity: 1;
}
}
/* LIVE HIGHLIGHTS */
.live-active-twitch {
border-color: rgba(145, 70, 255, 0.8) !important;
box-shadow: 0 0 40px rgba(145, 70, 255, 0.2);
background: rgba(145, 70, 255, 0.05) !important;
}
.live-active-kick {
border-color: rgba(83, 252, 24, 0.8) !important;
box-shadow: 0 0 40px rgba(83, 252, 24, 0.2);
background: rgba(83, 252, 24, 0.05) !important;
}
</style>