Refactor OverviewView and NoLoginView to integrate Character3D component

- Replaced avatar display logic in OverviewView with a 3D character representation based on user gender and age.
- Updated NoLoginView to utilize Character3D for displaying mascots, enhancing visual consistency.
- Removed outdated avatar positioning logic and related computed properties for improved code clarity and maintainability.
- Adjusted CSS styles for better layout and responsiveness of character displays.
This commit is contained in:
Torsten Schulz (local)
2026-01-22 11:06:38 +01:00
parent 2be5505c55
commit 33aa2ddd45
3 changed files with 361 additions and 80 deletions

View File

@@ -0,0 +1,330 @@
<template>
<div class="character-3d-container" :class="[`gender-${actualGender}`, `age-${ageGroup}`]">
<div class="character-3d" :style="characterStyle">
<!-- Kopf -->
<div class="head">
<div class="face">
<div class="eye left"></div>
<div class="eye right"></div>
<div class="mouth"></div>
</div>
<!-- Haare -->
<div class="hair" :class="actualGender"></div>
</div>
<!-- Körper -->
<div class="body" :class="actualGender">
<div class="chest"></div>
</div>
<!-- Arme -->
<div class="arm left"></div>
<div class="arm right"></div>
<!-- Beine -->
<div class="leg left"></div>
<div class="leg right"></div>
</div>
</div>
</template>
<script>
export default {
name: 'Character3D',
props: {
gender: {
type: String,
default: null,
validator: (value) => value === null || ['male', 'female'].includes(value)
},
age: {
type: Number,
default: null,
validator: (value) => value === null || (value >= 0 && value <= 120)
}
},
data() {
return {
randomGender: null,
randomAge: null
};
},
computed: {
actualGender() {
if (this.gender) {
return this.gender;
}
// Zufällige Auswahl beim ersten Mount, dann persistent
if (this.randomGender === null) {
this.randomGender = Math.random() < 0.5 ? 'male' : 'female';
}
return this.randomGender;
},
actualAge() {
if (this.age !== null && this.age !== undefined) {
return this.age;
}
// Zufällige Auswahl beim ersten Mount, dann persistent
if (this.randomAge === null) {
// Zufälliges Alter zwischen 18 und 65 Jahren
this.randomAge = Math.floor(Math.random() * 47) + 18;
}
return this.randomAge;
},
ageGroup() {
const age = this.actualAge;
if (age <= 3) return 'toddler';
if (age <= 12) return 'child';
if (age <= 17) return 'teen';
if (age <= 30) return 'young-adult';
if (age <= 50) return 'adult';
if (age <= 70) return 'senior';
return 'elderly';
},
characterStyle() {
const age = this.actualAge;
let scale = 1;
// Skalierung basierend auf Alter
if (age <= 3) {
scale = 0.4; // Kleinkind
} else if (age <= 12) {
scale = 0.6; // Kind
} else if (age <= 17) {
scale = 0.85; // Teenager
} else if (age <= 30) {
scale = 1.0; // Junger Erwachsener
} else if (age <= 50) {
scale = 0.95; // Erwachsener
} else if (age <= 70) {
scale = 0.9; // Senior
} else {
scale = 0.85; // Älterer Senior
}
return {
transform: `scale(${scale})`
};
}
}
};
</script>
<style scoped>
.character-3d-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
perspective: 1000px;
perspective-origin: center center;
}
.character-3d {
position: relative;
width: 80%;
height: 80%;
transform-style: preserve-3d;
animation: float 3s ease-in-out infinite;
transform-origin: center bottom;
}
/* Altersbasierte Anpassungen */
.age-toddler .head {
width: 35%;
height: 30%;
}
.age-child .head {
width: 32%;
height: 27%;
}
.age-teen .head {
width: 30%;
height: 25%;
}
.age-elderly .head {
width: 28%;
height: 23%;
}
.age-elderly .hair {
background: linear-gradient(135deg, #d3d3d3 0%, #a9a9a9 100%) !important;
}
@keyframes float {
0%, 100% {
transform: translateY(0) rotateY(0deg);
}
50% {
transform: translateY(-10px) rotateY(5deg);
}
}
/* Kopf */
.head {
position: absolute;
width: 30%;
height: 25%;
left: 50%;
top: 0;
transform: translateX(-50%);
background: linear-gradient(135deg, #ffdbac 0%, #f4c2a1 100%);
border-radius: 50% 50% 45% 45%;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transform-style: preserve-3d;
}
.face {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.eye {
position: absolute;
width: 8%;
height: 8%;
background: #000;
border-radius: 50%;
top: 35%;
}
.eye.left {
left: 30%;
}
.eye.right {
right: 30%;
}
.mouth {
position: absolute;
width: 20%;
height: 8%;
left: 50%;
top: 60%;
transform: translateX(-50%);
border: 2px solid #000;
border-top: none;
border-radius: 0 0 50% 50%;
}
/* Haare */
.hair {
position: absolute;
top: -15%;
left: 50%;
transform: translateX(-50%);
width: 120%;
height: 60%;
border-radius: 50% 50% 0 0;
}
.hair.male {
background: linear-gradient(135deg, #2c1810 0%, #1a0f08 100%);
clip-path: polygon(20% 0%, 80% 0%, 100% 50%, 0% 50%);
}
.hair.female {
background: linear-gradient(135deg, #ffd700 0%, #ffb347 100%);
border-radius: 50% 50% 30% 30%;
box-shadow: 0 -2px 10px rgba(255, 215, 0, 0.3);
}
/* Körper */
.body {
position: absolute;
width: 35%;
height: 40%;
left: 50%;
top: 25%;
transform: translateX(-50%);
border-radius: 20% 20% 10% 10%;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.body.male {
background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);
}
.body.female {
background: linear-gradient(135deg, #e24a90 0%, #c73a7a 100%);
}
.chest {
position: absolute;
width: 60%;
height: 40%;
left: 50%;
top: 20%;
transform: translateX(-50%);
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
}
/* Arme */
.arm {
position: absolute;
width: 12%;
height: 35%;
background: linear-gradient(135deg, #ffdbac 0%, #f4c2a1 100%);
border-radius: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.arm.left {
left: 15%;
top: 28%;
transform: rotate(-20deg);
}
.arm.right {
right: 15%;
top: 28%;
transform: rotate(20deg);
}
/* Beine */
.leg {
position: absolute;
width: 15%;
height: 30%;
border-radius: 10px 10px 5px 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.leg.left {
left: 35%;
top: 65%;
}
.leg.right {
right: 35%;
top: 65%;
}
.gender-male .leg {
background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%);
}
.gender-female .leg {
background: linear-gradient(135deg, #8b4caf 0%, #6b3a8f 100%);
}
/* 3D-Effekt mit Schatten */
.character-3d::before {
content: '';
position: absolute;
width: 60%;
height: 10%;
left: 50%;
bottom: -5%;
transform: translateX(-50%);
background: radial-gradient(ellipse, rgba(0, 0, 0, 0.3) 0%, transparent 70%);
border-radius: 50%;
z-index: -1;
}
</style>