Add 3D character rendering to Character3D component
- Integrated Three.js for 3D character visualization based on user gender and age. - Simplified the character structure by removing outdated HTML elements and replacing them with a dynamic 3D model loader. - Implemented model loading with fallback options and added animation capabilities for enhanced visual appeal. - Updated CSS for the character container to ensure proper rendering and responsiveness.
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
|
"three": "^0.169.0",
|
||||||
"vue": "~3.4.31",
|
"vue": "~3.4.31",
|
||||||
"vue-i18n": "^10.0.0-beta.2",
|
"vue-i18n": "^10.0.0-beta.2",
|
||||||
"vue-multiselect": "^3.1.0",
|
"vue-multiselect": "^3.1.0",
|
||||||
|
|||||||
40
frontend/public/models/3d/falukant/characters/README.md
Normal file
40
frontend/public/models/3d/falukant/characters/README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# 3D-Charakter-Modelle
|
||||||
|
|
||||||
|
## Verzeichnisstruktur
|
||||||
|
|
||||||
|
Dieses Verzeichnis enthält die 3D-Modelle für Falukant-Charaktere.
|
||||||
|
|
||||||
|
## Dateinamen-Konvention
|
||||||
|
|
||||||
|
### Basis-Modelle (Fallback)
|
||||||
|
- `male.glb` - Basis-Modell männlich
|
||||||
|
- `female.glb` - Basis-Modell weiblich
|
||||||
|
|
||||||
|
### Altersspezifische Modelle
|
||||||
|
- `male_toddler.glb` - Männlich, Kleinkind (0-3 Jahre)
|
||||||
|
- `male_child.glb` - Männlich, Kind (4-7 Jahre)
|
||||||
|
- `male_preteen.glb` - Männlich, Vor-Teenager (8-12 Jahre)
|
||||||
|
- `male_teen.glb` - Männlich, Teenager (13-17 Jahre)
|
||||||
|
- `male_adult.glb` - Männlich, Erwachsen (18+ Jahre)
|
||||||
|
- `female_toddler.glb` - Weiblich, Kleinkind (0-3 Jahre)
|
||||||
|
- `female_child.glb` - Weiblich, Kind (4-7 Jahre)
|
||||||
|
- `female_preteen.glb` - Weiblich, Vor-Teenager (8-12 Jahre)
|
||||||
|
- `female_teen.glb` - Weiblich, Teenager (13-17 Jahre)
|
||||||
|
- `female_adult.glb` - Weiblich, Erwachsen (18+ Jahre)
|
||||||
|
|
||||||
|
## Fallback-Verhalten
|
||||||
|
|
||||||
|
Wenn kein spezifisches Modell für den Altersbereich existiert, wird automatisch das Basis-Modell (`male.glb` / `female.glb`) verwendet.
|
||||||
|
|
||||||
|
## Dateigröße
|
||||||
|
|
||||||
|
- Empfohlen: < 500KB pro Modell
|
||||||
|
- Maximal: 1MB pro Modell
|
||||||
|
|
||||||
|
## Optimierung
|
||||||
|
|
||||||
|
Vor dem Hochladen:
|
||||||
|
1. In Blender öffnen
|
||||||
|
2. Decimate Modifier anwenden (falls nötig)
|
||||||
|
3. Texturen komprimieren (WebP, max 1024x1024)
|
||||||
|
4. GLB Export mit Compression aktiviert
|
||||||
BIN
frontend/public/models/3d/falukant/characters/female.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/female.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/female_adult.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/female_adult.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/female_child.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/female_child.glb
Normal file
Binary file not shown.
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/female_preteen.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/female_preteen.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/female_teen.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/female_teen.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/female_toddler.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/female_toddler.glb
Normal file
Binary file not shown.
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/male.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/male.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/male_adult.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/male_adult.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/male_child.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/male_child.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/male_preteen.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/male_preteen.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/male_teen.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/male_teen.glb
Normal file
Binary file not shown.
BIN
frontend/public/models/3d/falukant/characters/male_toddler.glb
Normal file
BIN
frontend/public/models/3d/falukant/characters/male_toddler.glb
Normal file
Binary file not shown.
@@ -1,31 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="character-3d-container" :class="[`gender-${actualGender}`, `age-${ageGroup}`]">
|
<div ref="container" class="character-3d-container"></div>
|
||||||
<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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Character3D',
|
name: 'Character3D',
|
||||||
props: {
|
props: {
|
||||||
@@ -43,7 +23,14 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
randomGender: null,
|
randomGender: null,
|
||||||
randomAge: null
|
randomAge: null,
|
||||||
|
scene: null,
|
||||||
|
camera: null,
|
||||||
|
renderer: null,
|
||||||
|
model: null,
|
||||||
|
animationId: null,
|
||||||
|
mixer: null,
|
||||||
|
clock: new THREE.Clock()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -71,37 +58,237 @@ export default {
|
|||||||
ageGroup() {
|
ageGroup() {
|
||||||
const age = this.actualAge;
|
const age = this.actualAge;
|
||||||
if (age <= 3) return 'toddler';
|
if (age <= 3) return 'toddler';
|
||||||
if (age <= 12) return 'child';
|
if (age <= 7) return 'child';
|
||||||
|
if (age <= 12) return 'preteen';
|
||||||
if (age <= 17) return 'teen';
|
if (age <= 17) return 'teen';
|
||||||
if (age <= 30) return 'young-adult';
|
return 'adult';
|
||||||
if (age <= 50) return 'adult';
|
|
||||||
if (age <= 70) return 'senior';
|
|
||||||
return 'elderly';
|
|
||||||
},
|
},
|
||||||
characterStyle() {
|
modelPath() {
|
||||||
const age = this.actualAge;
|
const gender = this.actualGender;
|
||||||
let scale = 1;
|
const ageGroup = this.ageGroup;
|
||||||
|
|
||||||
// Skalierung basierend auf Alter
|
// Versuche zuerst altersspezifisches Modell
|
||||||
if (age <= 3) {
|
const specificModel = `/models/3d/falukant/characters/${gender}_${ageGroup}.glb`;
|
||||||
scale = 0.4; // Kleinkind
|
|
||||||
} else if (age <= 12) {
|
// Fallback auf Basis-Modell
|
||||||
scale = 0.6; // Kind
|
return specificModel;
|
||||||
} else if (age <= 17) {
|
}
|
||||||
scale = 0.85; // Teenager
|
},
|
||||||
} else if (age <= 30) {
|
watch: {
|
||||||
scale = 1.0; // Junger Erwachsener
|
actualGender() {
|
||||||
} else if (age <= 50) {
|
this.loadModel();
|
||||||
scale = 0.95; // Erwachsener
|
},
|
||||||
} else if (age <= 70) {
|
ageGroup() {
|
||||||
scale = 0.9; // Senior
|
this.loadModel();
|
||||||
} else {
|
}
|
||||||
scale = 0.85; // Älterer Senior
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init3D();
|
||||||
|
this.loadModel();
|
||||||
|
this.animate();
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.cleanup();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init3D() {
|
||||||
|
const container = this.$refs.container;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
// Scene erstellen
|
||||||
|
this.scene = new THREE.Scene();
|
||||||
|
this.scene.background = new THREE.Color(0xf0f0f0);
|
||||||
|
|
||||||
|
// Camera erstellen
|
||||||
|
const aspect = container.clientWidth / container.clientHeight;
|
||||||
|
this.camera = new THREE.PerspectiveCamera(50, aspect, 0.1, 1000);
|
||||||
|
this.camera.position.set(0, 1.5, 3);
|
||||||
|
this.camera.lookAt(0, 1, 0);
|
||||||
|
|
||||||
|
// Renderer erstellen
|
||||||
|
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||||
|
this.renderer.setSize(container.clientWidth, container.clientHeight);
|
||||||
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
container.appendChild(this.renderer.domElement);
|
||||||
|
|
||||||
|
// Licht hinzufügen
|
||||||
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||||||
|
this.scene.add(ambientLight);
|
||||||
|
|
||||||
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||||
|
directionalLight.position.set(5, 10, 5);
|
||||||
|
this.scene.add(directionalLight);
|
||||||
|
|
||||||
|
// Zusätzliches Licht von hinten
|
||||||
|
const backLight = new THREE.DirectionalLight(0xffffff, 0.3);
|
||||||
|
backLight.position.set(-5, 0, -5);
|
||||||
|
this.scene.add(backLight);
|
||||||
|
|
||||||
|
// Resize Handler
|
||||||
|
window.addEventListener('resize', this.onWindowResize);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadModel() {
|
||||||
|
if (!this.scene) return;
|
||||||
|
|
||||||
|
// Altes Modell entfernen
|
||||||
|
if (this.model) {
|
||||||
|
this.scene.remove(this.model);
|
||||||
|
// Cleanup des alten Modells
|
||||||
|
this.model.traverse((object) => {
|
||||||
|
if (object.geometry) object.geometry.dispose();
|
||||||
|
if (object.material) {
|
||||||
|
if (Array.isArray(object.material)) {
|
||||||
|
object.material.forEach(m => m.dispose());
|
||||||
|
} else {
|
||||||
|
object.material.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.model = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
if (this.mixer) {
|
||||||
transform: `scale(${scale})`
|
this.mixer = null;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
const modelPath = this.modelPath;
|
||||||
|
|
||||||
|
// Versuche zuerst spezifisches Modell, dann Fallback
|
||||||
|
let gltf;
|
||||||
|
try {
|
||||||
|
gltf = await loader.loadAsync(modelPath);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Could not load ${modelPath}, trying fallback model`);
|
||||||
|
// Fallback auf Basis-Modell
|
||||||
|
const fallbackPath = `/models/3d/falukant/characters/${this.actualGender}.glb`;
|
||||||
|
gltf = await loader.loadAsync(fallbackPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.model = gltf.scene;
|
||||||
|
|
||||||
|
// Modell zentrieren und skalieren
|
||||||
|
const box = new THREE.Box3().setFromObject(this.model);
|
||||||
|
const center = box.getCenter(new THREE.Vector3());
|
||||||
|
const size = box.getSize(new THREE.Vector3());
|
||||||
|
|
||||||
|
// Skalierung basierend auf Alter
|
||||||
|
const age = this.actualAge;
|
||||||
|
let scale = 1;
|
||||||
|
if (age <= 3) {
|
||||||
|
scale = 0.4;
|
||||||
|
} else if (age <= 7) {
|
||||||
|
scale = 0.6;
|
||||||
|
} else if (age <= 12) {
|
||||||
|
scale = 0.75;
|
||||||
|
} else if (age <= 17) {
|
||||||
|
scale = 0.9;
|
||||||
|
} else {
|
||||||
|
scale = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modell skalieren, damit es in die Szene passt
|
||||||
|
const maxDimension = Math.max(size.x, size.y, size.z);
|
||||||
|
const targetHeight = 2; // Zielhöhe in 3D-Einheiten
|
||||||
|
const modelScale = (targetHeight / maxDimension) * scale;
|
||||||
|
this.model.scale.set(modelScale, modelScale, modelScale);
|
||||||
|
|
||||||
|
// Modell zentrieren
|
||||||
|
this.model.position.sub(center.multiplyScalar(modelScale));
|
||||||
|
this.model.position.y = 0; // Auf Boden setzen
|
||||||
|
|
||||||
|
this.scene.add(this.model);
|
||||||
|
|
||||||
|
// Animationen laden falls vorhanden
|
||||||
|
if (gltf.animations && gltf.animations.length > 0) {
|
||||||
|
this.mixer = new THREE.AnimationMixer(this.model);
|
||||||
|
gltf.animations.forEach((clip) => {
|
||||||
|
this.mixer.clipAction(clip).play();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanfte Rotation-Animation
|
||||||
|
if (this.model) {
|
||||||
|
this.model.rotation.y = Math.random() * Math.PI * 2;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading 3D model:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
animate() {
|
||||||
|
this.animationId = requestAnimationFrame(this.animate);
|
||||||
|
|
||||||
|
const delta = this.clock.getDelta();
|
||||||
|
|
||||||
|
// Animation-Mixer aktualisieren
|
||||||
|
if (this.mixer) {
|
||||||
|
this.mixer.update(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.model) {
|
||||||
|
// Sanfte Rotation
|
||||||
|
this.model.rotation.y += 0.005;
|
||||||
|
|
||||||
|
// Sanftes Auf und Ab
|
||||||
|
this.model.position.y = Math.sin(Date.now() * 0.001) * 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.renderer && this.scene && this.camera) {
|
||||||
|
this.renderer.render(this.scene, this.camera);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onWindowResize() {
|
||||||
|
const container = this.$refs.container;
|
||||||
|
if (!container || !this.camera || !this.renderer) return;
|
||||||
|
|
||||||
|
const width = container.clientWidth;
|
||||||
|
const height = container.clientHeight;
|
||||||
|
|
||||||
|
this.camera.aspect = width / height;
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
this.renderer.setSize(width, height);
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if (this.animationId) {
|
||||||
|
cancelAnimationFrame(this.animationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('resize', this.onWindowResize);
|
||||||
|
|
||||||
|
if (this.mixer) {
|
||||||
|
this.mixer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.model) {
|
||||||
|
this.model.traverse((object) => {
|
||||||
|
if (object.geometry) object.geometry.dispose();
|
||||||
|
if (object.material) {
|
||||||
|
if (Array.isArray(object.material)) {
|
||||||
|
object.material.forEach(m => m.dispose());
|
||||||
|
} else {
|
||||||
|
object.material.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.model = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.renderer) {
|
||||||
|
const container = this.$refs.container;
|
||||||
|
if (container && this.renderer.domElement) {
|
||||||
|
container.removeChild(this.renderer.domElement);
|
||||||
|
}
|
||||||
|
this.renderer.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.scene) {
|
||||||
|
this.scene.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -111,220 +298,7 @@ export default {
|
|||||||
.character-3d-container {
|
.character-3d-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
perspective: 1000px;
|
|
||||||
perspective-origin: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.character-3d {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 80%;
|
overflow: hidden;
|
||||||
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user