Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (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-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

View File

@@ -0,0 +1,610 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { computed } from 'vue';
import UserLayout from '@/layouts/user/userlayout.vue';
interface Achievement {
key: string;
title: string;
description: string;
icon: string;
unlocked: boolean;
unlocked_at: string | null;
}
const props = defineProps<{
achievements: Achievement[];
total: number;
unlocked: number;
profileUser?: { username: string; avatar: string | null };
}>();
// Tier & color config per achievement
const TIER: Record<string, { color: string; light: string; glow: string; shadow: string; tier: string }> = {
first_bet: { tier: 'bronze', color: '#a0522d', light: '#e8883a', glow: 'rgba(205,127,50,0.6)', shadow: '#5c2f10' },
first_win: { tier: 'gold', color: '#b8860b', light: '#ffd700', glow: 'rgba(255,200,0,0.7)', shadow: '#7a5600' },
big_winner: { tier: 'gold', color: '#b8860b', light: '#ffd700', glow: 'rgba(255,200,0,0.7)', shadow: '#7a5600' },
high_roller: { tier: 'silver', color: '#777', light: '#ddd', glow: 'rgba(200,200,200,0.5)', shadow: '#333' },
frequent_player: { tier: 'silver', color: '#777', light: '#ddd', glow: 'rgba(200,200,200,0.5)', shadow: '#333' },
hundred_bets: { tier: 'gold', color: '#b8860b', light: '#ffd700', glow: 'rgba(255,200,0,0.7)', shadow: '#7a5600' },
vault_user: { tier: 'bronze', color: '#a0522d', light: '#e8883a', glow: 'rgba(205,127,50,0.6)', shadow: '#5c2f10' },
vip_level2: { tier: 'silver', color: '#777', light: '#ddd', glow: 'rgba(200,200,200,0.5)', shadow: '#333' },
vip_level5: { tier: 'gold', color: '#b8860b', light: '#ffd700', glow: 'rgba(255,200,0,0.7)', shadow: '#7a5600' },
guild_member: { tier: 'special',color: '#4a0080', light: '#c455ff', glow: 'rgba(180,50,255,0.6)', shadow: '#2a0050' },
promo_user: { tier: 'bronze', color: '#a0522d', light: '#e8883a', glow: 'rgba(205,127,50,0.6)', shadow: '#5c2f10' },
};
function getTier(key: string) {
return TIER[key] ?? { tier: 'bronze', color: '#a0522d', light: '#e8883a', glow: 'rgba(205,127,50,0.6)', shadow: '#5c2f10' };
}
// Split into rows of 4
const rows = computed(() => {
const out = [];
for (let i = 0; i < props.achievements.length; i += 4) {
out.push(props.achievements.slice(i, i + 4));
}
return out;
});
function formatDate(iso: string | null): string {
if (!iso) return '';
return new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' });
}
const progressPct = computed(() =>
props.total > 0 ? Math.round((props.unlocked / props.total) * 100) : 0
);
</script>
<template>
<UserLayout>
<Head :title="profileUser ? `${profileUser.username}'s Trophy Room` : 'Trophy Room'" />
<div class="trophy-page">
<!-- Page Header -->
<div class="page-header">
<div class="header-left">
<div v-if="profileUser" class="viewing-badge">
<img v-if="profileUser.avatar" :src="profileUser.avatar" class="viewer-avatar" alt="" />
<span>{{ profileUser.username }}'s Collection</span>
</div>
<h1>Trophy Room</h1>
<p class="header-sub">{{ unlocked }} of {{ total }} achievements unlocked</p>
</div>
<div class="header-progress">
<div class="progress-ring-wrap">
<svg class="progress-ring" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="32" fill="none" stroke="#1a1a1a" stroke-width="6"/>
<circle
cx="40" cy="40" r="32"
fill="none"
stroke="url(#ringGrad)"
stroke-width="6"
stroke-linecap="round"
:stroke-dasharray="`${progressPct * 2.01} 201`"
transform="rotate(-90 40 40)"
/>
<defs>
<linearGradient id="ringGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#df006a"/>
<stop offset="100%" stop-color="#ffb700"/>
</linearGradient>
</defs>
</svg>
<div class="ring-label">{{ progressPct }}%</div>
</div>
</div>
</div>
<!-- Trophy Cabinet -->
<div class="cabinet">
<!-- Cabinet top frame -->
<div class="cabinet-header">
<span class="cabinet-label">🏅 Achievement Showcase</span>
<div class="cabinet-bolts">
<span class="bolt"></span>
<span class="bolt"></span>
</div>
</div>
<!-- Glass panel shine -->
<div class="glass-shine"></div>
<!-- Shelves -->
<div class="cabinet-body">
<div v-for="(row, rowIdx) in rows" :key="rowIdx" class="shelf-row">
<!-- Shelf surface -->
<div class="shelf-surface"></div>
<!-- Shelf underside shadow -->
<div class="shelf-shadow"></div>
<!-- Trophies on this shelf -->
<div class="shelf-trophies">
<div
v-for="ach in row"
:key="ach.key"
class="trophy-slot"
:class="{ unlocked: ach.unlocked, locked: !ach.unlocked }"
:title="ach.description"
>
<!-- 3D Trophy -->
<div class="trophy-3d" :data-tier="getTier(ach.key).tier">
<!-- Glow aura (unlocked only) -->
<div v-if="ach.unlocked" class="trophy-aura" :style="{ background: getTier(ach.key).glow }"></div>
<!-- Star on top -->
<div v-if="ach.unlocked" class="trophy-star">★</div>
<!-- Cup body with handles -->
<div class="cup-wrap">
<div
class="cup-body"
:style="ach.unlocked ? {
background: `linear-gradient(135deg, ${getTier(ach.key).light} 0%, ${getTier(ach.key).color} 50%, ${getTier(ach.key).shadow} 100%)`,
boxShadow: `inset -4px -4px 8px ${getTier(ach.key).shadow}, inset 4px 4px 8px ${getTier(ach.key).light}55`
} : {}"
>
<!-- Shine highlight -->
<div class="cup-shine"></div>
<!-- Icon inside cup -->
<div class="cup-icon">{{ ach.icon }}</div>
</div>
<!-- Handles -->
<div
class="handle handle-left"
:style="ach.unlocked ? { borderColor: getTier(ach.key).color } : {}"
></div>
<div
class="handle handle-right"
:style="ach.unlocked ? { borderColor: getTier(ach.key).color } : {}"
></div>
</div>
<!-- Stem -->
<div
class="trophy-stem"
:style="ach.unlocked ? {
background: `linear-gradient(90deg, ${getTier(ach.key).shadow}, ${getTier(ach.key).color}, ${getTier(ach.key).shadow})`
} : {}"
></div>
<!-- Base -->
<div
class="trophy-base"
:style="ach.unlocked ? {
background: `linear-gradient(180deg, ${getTier(ach.key).color} 0%, ${getTier(ach.key).shadow} 100%)`,
boxShadow: `0 4px 12px ${getTier(ach.key).glow}`
} : {}"
></div>
<!-- Locked overlay -->
<div v-if="!ach.unlocked" class="locked-overlay">
<div class="lock-icon">🔒</div>
</div>
</div>
<!-- Name plate below trophy -->
<div class="nameplate" :style="ach.unlocked ? { borderColor: getTier(ach.key).color + '66', color: getTier(ach.key).light } : {}">
{{ ach.title }}
</div>
<div v-if="ach.unlocked_at" class="unlock-date">
{{ formatDate(ach.unlocked_at) }}
</div>
</div>
</div>
</div>
</div>
<!-- Cabinet bottom frame -->
<div class="cabinet-footer"></div>
</div>
<!-- Tier legend -->
<div class="tier-legend">
<div class="legend-item">
<span class="legend-dot" style="background: linear-gradient(135deg, #ffd700, #b8860b);"></span>
Gold
</div>
<div class="legend-item">
<span class="legend-dot" style="background: linear-gradient(135deg, #ddd, #777);"></span>
Silver
</div>
<div class="legend-item">
<span class="legend-dot" style="background: linear-gradient(135deg, #e8883a, #a0522d);"></span>
Bronze
</div>
<div class="legend-item">
<span class="legend-dot" style="background: linear-gradient(135deg, #c455ff, #4a0080);"></span>
Special
</div>
</div>
</div>
</UserLayout>
</template>
<style scoped>
.trophy-page {
padding: 30px;
max-width: 1000px;
margin: 0 auto;
}
/* ── Page Header ─────────────────────────────── */
.page-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 40px;
gap: 20px;
}
.header-left h1 {
font-size: 2.2rem;
font-weight: 900;
color: #fff;
letter-spacing: -1px;
line-height: 1;
margin-bottom: 6px;
}
.header-sub {
font-size: 13px;
color: #666;
font-weight: 600;
}
.viewing-badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(223,0,106,0.08);
border: 1px solid rgba(223,0,106,0.2);
border-radius: 999px;
padding: 4px 12px 4px 6px;
font-size: 12px;
font-weight: 700;
color: var(--primary, #df006a);
margin-bottom: 10px;
}
.viewer-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
object-fit: cover;
}
.progress-ring-wrap {
position: relative;
width: 80px;
height: 80px;
flex-shrink: 0;
}
.progress-ring { width: 80px; height: 80px; }
.ring-label {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 900;
color: #fff;
}
/* ── Cabinet ─────────────────────────────────── */
.cabinet {
position: relative;
background: linear-gradient(180deg, #0e0a06 0%, #0a0a0a 100%);
border: 2px solid #2a1f0e;
border-radius: 20px;
overflow: hidden;
box-shadow:
0 0 0 1px #1a1208,
0 20px 60px rgba(0,0,0,0.8),
inset 0 1px 0 rgba(255,200,100,0.06);
margin-bottom: 28px;
}
.cabinet-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
background: linear-gradient(90deg, #1a120a, #120e06, #1a120a);
border-bottom: 1px solid #2a1a08;
}
.cabinet-label {
font-size: 11px;
font-weight: 800;
letter-spacing: 3px;
text-transform: uppercase;
color: #7a5a2a;
}
.cabinet-bolts {
display: flex;
gap: 8px;
}
.bolt {
width: 10px;
height: 10px;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #5a4020, #2a1a08);
border: 1px solid #3a2510;
box-shadow: inset 1px 1px 2px rgba(255,200,100,0.15);
}
.glass-shine {
position: absolute;
top: 44px;
left: 0; right: 0;
height: 80px;
background: linear-gradient(180deg, rgba(255,255,255,0.03) 0%, transparent 100%);
pointer-events: none;
z-index: 10;
}
.cabinet-body {
padding: 24px 20px 0;
}
.cabinet-footer {
height: 20px;
background: linear-gradient(90deg, #1a120a, #120e06, #1a120a);
border-top: 1px solid #2a1a08;
margin-top: 8px;
}
/* ── Shelf ───────────────────────────────────── */
.shelf-row {
position: relative;
margin-bottom: 0;
padding-bottom: 28px;
}
.shelf-surface {
position: absolute;
bottom: 0;
left: -20px;
right: -20px;
height: 14px;
background: linear-gradient(180deg, #3a2810 0%, #2a1e0c 50%, #1e1408 100%);
border-top: 1px solid #5a3a18;
box-shadow: 0 2px 0 #0a0806, inset 0 1px 0 rgba(255,200,100,0.1);
z-index: 5;
}
.shelf-shadow {
position: absolute;
bottom: -10px;
left: 0; right: 0;
height: 10px;
background: linear-gradient(180deg, rgba(0,0,0,0.5), transparent);
z-index: 4;
}
.shelf-trophies {
display: flex;
gap: 8px;
justify-content: flex-start;
padding: 16px 8px 6px;
position: relative;
z-index: 6;
}
/* ── Trophy Slot ─────────────────────────────── */
.trophy-slot {
flex: 1;
max-width: 200px;
min-width: 100px;
display: flex;
flex-direction: column;
align-items: center;
cursor: default;
transition: transform 0.2s ease;
}
.trophy-slot:hover {
transform: translateY(-4px);
}
.trophy-slot.locked .trophy-3d {
filter: grayscale(0.9) brightness(0.4);
}
/* ── 3D Trophy ───────────────────────────────── */
.trophy-3d {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 90px;
perspective: 400px;
user-select: none;
}
.trophy-aura {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 70px;
height: 70px;
border-radius: 50%;
opacity: 0.25;
filter: blur(14px);
pointer-events: none;
animation: aura-pulse 2.4s ease-in-out infinite;
}
@keyframes aura-pulse {
0%, 100% { opacity: 0.2; transform: translate(-50%, -50%) scale(1); }
50% { opacity: 0.4; transform: translate(-50%, -50%) scale(1.2); }
}
.trophy-star {
font-size: 14px;
color: #ffd700;
text-shadow: 0 0 8px #ffd70088;
margin-bottom: 2px;
animation: star-spin 6s linear infinite;
display: inline-block;
}
@keyframes star-spin {
0% { transform: rotate(0deg) scale(1); }
50% { transform: rotate(180deg) scale(1.2); }
100% { transform: rotate(360deg) scale(1); }
}
/* Cup */
.cup-wrap {
position: relative;
width: 60px;
height: 52px;
display: flex;
align-items: center;
justify-content: center;
}
.cup-body {
width: 46px;
height: 52px;
border-radius: 8px 8px 18px 18px;
background: linear-gradient(135deg, #555 0%, #333 50%, #111 100%);
position: relative;
clip-path: polygon(8% 0%, 92% 0%, 100% 30%, 88% 100%, 12% 100%, 0% 30%);
overflow: hidden;
box-shadow: inset -4px -4px 8px rgba(0,0,0,0.4), inset 4px 4px 8px rgba(255,255,255,0.08);
transition: background 0.3s, box-shadow 0.3s;
}
.cup-shine {
position: absolute;
top: 4px;
left: 6px;
width: 12px;
height: 24px;
border-radius: 6px;
background: rgba(255,255,255,0.18);
transform: rotate(-15deg);
pointer-events: none;
}
.cup-icon {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.5));
padding-top: 6px;
}
/* Handles */
.handle {
position: absolute;
width: 14px;
height: 22px;
border-radius: 50%;
border: 5px solid #333;
top: 8px;
transition: border-color 0.3s;
}
.handle-left {
left: -4px;
border-right: none;
border-radius: 50% 0 0 50%;
}
.handle-right {
right: -4px;
border-left: none;
border-radius: 0 50% 50% 0;
}
/* Stem */
.trophy-stem {
width: 10px;
height: 22px;
background: linear-gradient(90deg, #222, #444, #222);
border-radius: 2px;
transition: background 0.3s;
}
/* Base */
.trophy-base {
width: 52px;
height: 12px;
border-radius: 4px;
background: linear-gradient(180deg, #444 0%, #222 100%);
box-shadow: 0 3px 8px rgba(0,0,0,0.5);
transition: background 0.3s, box-shadow 0.3s;
}
/* Locked overlay */
.locked-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.lock-icon {
font-size: 22px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.8));
opacity: 0.7;
}
/* ── Nameplate ───────────────────────────────── */
.nameplate {
margin-top: 10px;
font-size: 10px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #666;
text-align: center;
background: #0d0d0d;
border: 1px solid #222;
border-radius: 4px;
padding: 3px 8px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
transition: border-color 0.3s, color 0.3s;
line-height: 1.4;
}
.unlock-date {
font-size: 9px;
color: #444;
font-weight: 600;
text-align: center;
margin-top: 3px;
letter-spacing: 0.3px;
}
/* ── Tier Legend ─────────────────────────────── */
.tier-legend {
display: flex;
gap: 20px;
justify-content: center;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-weight: 700;
color: #666;
text-transform: uppercase;
letter-spacing: 1px;
}
.legend-dot {
width: 14px;
height: 14px;
border-radius: 50%;
flex-shrink: 0;
}
/* ── Responsive ─────────────────────────────── */
@media (max-width: 700px) {
.trophy-page { padding: 16px; }
.shelf-trophies { gap: 4px; }
.trophy-3d { width: 72px; }
.cup-body { width: 38px; height: 42px; }
.trophy-base { width: 42px; }
.nameplate { font-size: 9px; padding: 2px 5px; }
.cup-icon { font-size: 16px; }
.page-header { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 480px) {
.shelf-trophies { flex-wrap: wrap; justify-content: center; }
.trophy-slot { min-width: 80px; }
}
</style>