611 lines
20 KiB
Vue
611 lines
20 KiB
Vue
<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>
|