pet_data/app/components/pixel/PixelAvatar.vue

341 lines
13 KiB
Vue

<template>
<div class="w-full h-full relative image-pixelated flex items-center justify-center" :class="{ 'grayscale': isDead }">
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" shapeRendering="crispEdges" class="w-full h-full">
<!-- EGG STAGE -->
<g v-if="isEgg">
<!-- Egg Base -->
<path d="M12 4 H20 V6 H22 V10 H24 V22 H22 V26 H20 V28 H12 V26 H10 V22 H8 V10 H10 V6 H12 Z" :fill="eggBaseColor" />
<!-- Egg Shading -->
<path d="M12 26 H20 V28 H12 Z" fill="rgba(0,0,0,0.2)" />
<path d="M22 10 H24 V22 H22 Z" fill="rgba(0,0,0,0.1)" />
<!-- Patterns -->
<!-- CAT Patterns -->
<g v-if="species === 'cat'">
<g v-if="eggPattern === 'spots'" fill="rgba(255,255,255,0.3)">
<rect x="14" y="8" width="2" height="2" />
<rect x="18" y="14" width="2" height="2" />
<rect x="12" y="18" width="2" height="2" />
<rect x="16" y="22" width="2" height="2" />
</g>
<g v-else-if="eggPattern === 'stripes'" fill="rgba(255,255,255,0.2)">
<rect x="10" y="10" width="12" height="2" />
<rect x="8" y="16" width="16" height="2" />
<rect x="10" y="22" width="12" height="2" />
</g>
<g v-else-if="eggPattern === 'zigzag'" fill="rgba(255,255,255,0.2)">
<path d="M10 14 H12 V12 H14 V14 H16 V12 H18 V14 H20 V12 H22 V14 H22 V16 H20 V18 H18 V16 H16 V18 H14 V16 H12 V18 H10 V16 Z" />
</g>
</g>
<!-- DOG Patterns -->
<g v-else>
<!-- Bone Pattern -->
<g v-if="eggPattern === 'bone'" fill="rgba(255,255,255,0.3)">
<rect x="14" y="14" width="4" height="2" />
<rect x="13" y="13" width="1" height="1" />
<rect x="13" y="16" width="1" height="1" />
<rect x="18" y="13" width="1" height="1" />
<rect x="18" y="16" width="1" height="1" />
</g>
<!-- Paw Pattern -->
<g v-else-if="eggPattern === 'paw'" fill="rgba(255,255,255,0.2)">
<rect x="15" y="18" width="2" height="2" /> <!-- Pad -->
<rect x="14" y="16" width="1" height="1" />
<rect x="16" y="15" width="1" height="1" />
<rect x="18" y="16" width="1" height="1" />
</g>
<!-- Patch Pattern -->
<g v-else-if="eggPattern === 'patch'" fill="rgba(255,255,255,0.2)">
<rect x="18" y="8" width="4" height="4" />
<rect x="10" y="20" width="6" height="4" />
</g>
</g>
<!-- Cracks (if close to hatching) -->
<g v-if="isHatching" fill="#2b193f">
<rect x="14" y="6" width="1" height="4" />
<rect x="13" y="9" width="1" height="2" />
<rect x="15" y="8" width="1" height="3" />
</g>
</g>
<!-- DEITY STAGE -->
<g v-else-if="deityId">
<!-- Mazu (妈祖) -->
<g v-if="deityId === 'mazu'">
<rect x="10" y="4" width="12" height="12" fill="#ffe0bd" /> <!-- Face -->
<rect x="8" y="4" width="2" height="12" fill="#1a1a1a" /> <!-- Hair L -->
<rect x="22" y="4" width="2" height="12" fill="#1a1a1a" /> <!-- Hair R -->
<rect x="8" y="2" width="16" height="4" fill="#d4af37" /> <!-- Crown Base -->
<rect x="14" y="0" width="4" height="2" fill="#d4af37" /> <!-- Crown Top -->
<rect x="12" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
<rect x="18" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
<path d="M8 16 H24 V28 H8 Z" fill="#ffa500" /> <!-- Robe -->
<rect x="14" y="16" width="4" height="12" fill="#d4af37" opacity="0.5" /> <!-- Robe Detail -->
</g>
<!-- Earth God (土地公) -->
<g v-else-if="deityId === 'earth_god'">
<rect x="10" y="6" width="12" height="10" fill="#f0c0a8" /> <!-- Face -->
<rect x="8" y="4" width="16" height="4" fill="#1a1a1a" /> <!-- Hat Base -->
<rect x="10" y="2" width="12" height="2" fill="#1a1a1a" /> <!-- Hat Top -->
<rect x="12" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
<rect x="18" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
<rect x="10" y="14" width="12" height="6" fill="#f0f0f0" /> <!-- Beard -->
<path d="M8 16 H24 V28 H8 Z" fill="#d75b5b" /> <!-- Robe -->
<rect x="14" y="20" width="4" height="2" fill="#8e5c2e" /> <!-- Belt -->
</g>
<!-- Matchmaker (月老) -->
<g v-else-if="deityId === 'matchmaker'">
<rect x="10" y="6" width="12" height="10" fill="#ffe0bd" /> <!-- Face -->
<rect x="10" y="4" width="12" height="4" fill="#f0f0f0" /> <!-- Hair -->
<rect x="12" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
<rect x="18" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
<rect x="10" y="14" width="12" height="4" fill="#f0f0f0" /> <!-- Beard -->
<path d="M8 16 H24 V28 H8 Z" fill="#d95763" /> <!-- Robe -->
<rect x="20" y="18" width="4" height="4" fill="#ff0000" /> <!-- Red Thread Ball -->
</g>
<!-- Wenchang (文昌) -->
<g v-else>
<rect x="10" y="6" width="12" height="10" fill="#ffe0bd" /> <!-- Face -->
<rect x="8" y="2" width="16" height="6" fill="#1a1a1a" /> <!-- Hat -->
<rect x="12" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
<rect x="18" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
<rect x="12" y="14" width="8" height="2" fill="#1a1a1a" /> <!-- Mustache -->
<path d="M8 16 H24 V28 H8 Z" fill="#9fd75b" /> <!-- Robe -->
<rect x="20" y="18" width="2" height="8" fill="#8d6e63" /> <!-- Brush -->
</g>
</g>
<!-- PORTRAIT STAGE (Baby, Child, Adult) -->
<g v-else>
<!-- Background Glow (Optional) -->
<circle cx="16" cy="16" r="14" :fill="auraColor" opacity="0.2" />
<!-- CAT EARS -->
<g v-if="species === 'cat'" :fill="furColor">
<!-- Left Ear -->
<path d="M6 4 H10 V8 H6 Z" />
<path d="M7 5 H9 V7 H7 Z" fill="#ffb7b2" /> <!-- Inner Ear -->
<!-- Right Ear -->
<path d="M22 4 H26 V8 H22 Z" />
<path d="M23 5 H25 V7 H23 Z" fill="#ffb7b2" /> <!-- Inner Ear -->
</g>
<!-- DOG EARS (Floppy) -->
<g v-else :fill="furColor">
<!-- Left Ear -->
<path d="M4 8 H8 V14 H6 V16 H4 Z" />
<!-- Right Ear -->
<path d="M24 8 H28 V16 H26 V14 H24 Z" />
</g>
<!-- Head Base -->
<rect x="8" y="8" width="16" height="14" :fill="furColor" />
<rect x="6" y="10" width="2" height="10" :fill="furColor" /> <!-- Cheeks L -->
<rect x="24" y="10" width="2" height="10" :fill="furColor" /> <!-- Cheeks R -->
<!-- CAT STRIPES -->
<g v-if="species === 'cat'">
<!-- Tiger Stripes (Forehead) -->
<g fill="#4a3b5e" opacity="0.6">
<rect x="15" y="8" width="2" height="3" />
<rect x="12" y="9" width="1" height="2" />
<rect x="19" y="9" width="1" height="2" />
</g>
<!-- Cheeks Stripes -->
<g fill="#4a3b5e" opacity="0.4">
<rect x="6" y="14" width="2" height="1" />
<rect x="6" y="16" width="2" height="1" />
<rect x="24" y="14" width="2" height="1" />
<rect x="24" y="16" width="2" height="1" />
</g>
</g>
<!-- DOG PATCH (Eye Patch) -->
<g v-else>
<rect x="18" y="12" width="5" height="5" fill="#4a3b5e" opacity="0.3" />
</g>
<!-- Face Features -->
<!-- Eyes -->
<!-- Eyes -->
<g :fill="eyeColor">
<g v-if="mood === 'dead'">
<!-- X Eyes -->
<path d="M10 13 L13 16 M13 13 L10 16" stroke="#1b1026" stroke-width="1" />
<path d="M19 13 L22 16 M22 13 L19 16" stroke="#1b1026" stroke-width="1" />
</g>
<g v-else>
<rect x="10" y="13" width="3" height="3" />
<rect x="19" y="13" width="3" height="3" />
<!-- Highlights -->
<rect x="12" y="13" width="1" height="1" fill="white" />
<rect x="21" y="13" width="1" height="1" fill="white" />
</g>
</g>
<!-- Glasses (High INT) -->
<g v-if="stats.int > 20" stroke="#f6b26b" stroke-width="1" fill="none">
<rect x="9.5" y="12.5" width="4" height="4" />
<rect x="18.5" y="12.5" width="4" height="4" />
<line x1="13.5" y1="14.5" x2="18.5" y2="14.5" />
</g>
<!-- Nose & Mouth -->
<rect x="15" y="17" width="2" height="1" :fill="species === 'dog' ? '#1a1a1a' : '#ffb7b2'" /> <!-- Nose (Black for dog, Pink for cat) -->
<!-- Mouth Expressions -->
<g fill="#2b193f">
<g v-if="mood === 'happy'">
<rect x="14" y="19" width="1" height="1" />
<rect x="15" y="20" width="2" height="1" />
<rect x="17" y="19" width="1" height="1" />
</g>
<g v-else-if="mood === 'sad'">
<rect x="14" y="20" width="1" height="1" />
<rect x="15" y="19" width="2" height="1" />
<rect x="17" y="20" width="1" height="1" />
</g>
<g v-else-if="mood === 'dead'">
<!-- Dead Mouth (Flat line) -->
<rect x="14" y="20" width="4" height="1" />
</g>
<g v-else>
<rect x="14" y="19" width="4" height="1" />
</g>
</g>
<!-- Body (Shoulders/Upper Chest) - Portrait Style -->
<path d="M8 22 H24 V32 H8 Z" :fill="outfitColor" />
<path d="M6 24 H8 V32 H6 Z" :fill="outfitColor" filter="brightness(0.9)" />
<path d="M24 24 H26 V32 H24 Z" :fill="outfitColor" filter="brightness(0.9)" />
<!-- Collar/Accessory -->
<g v-if="stats.str > 20">
<!-- Red Scarf/Bandana -->
<path d="M10 22 H22 V24 H20 V25 H12 V24 H10 Z" fill="#d95763" />
</g>
<g v-else-if="stats.dex > 20">
<!-- Green Bowtie -->
<path d="M14 23 L12 22 V26 L14 25 Z" fill="#99e550" />
<path d="M18 23 L20 22 V26 L18 25 Z" fill="#99e550" />
<rect x="15" y="23" width="2" height="2" fill="#76c442" />
</g>
<g v-else>
<!-- Simple Collar -->
<rect x="11" y="22" width="10" height="2" fill="#e0d8f0" opacity="0.5" />
</g>
</g>
</svg>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
stage?: string;
species?: string;
stats?: {
str: number;
int: number;
dex: number;
happiness: number;
generation?: number;
age?: number;
};
skinColor?: string; // Fallback
outfitColor?: string; // Fallback
deityId?: string;
isDead?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
stage: 'egg',
species: 'cat',
stats: () => ({ str: 0, int: 0, dex: 0, happiness: 50, generation: 1, age: 0 }),
skinColor: '#ffdbac',
outfitColor: '#78909c',
isDead: false
});
const isEgg = computed(() => {
if (props.deityId) return false;
return !props.stage || props.stage.toLowerCase() === 'egg';
});
const isHatching = computed(() => {
return isEgg.value && (props.stats.age || 0) > 60;
});
// --- EGG LOGIC ---
const eggBaseColor = computed(() => {
const { str, int, dex } = props.stats;
if (str > int && str > dex) return '#ffcccb'; // Reddish
if (int > str && int > dex) return '#cce5ff'; // Bluish
if (dex > str && dex > int) return '#ccffcc'; // Greenish
return '#f0f0f0'; // White/Grey
});
const eggPattern = computed(() => {
const gen = props.stats.generation || 1;
if (props.species === 'cat') {
const patterns = ['spots', 'stripes', 'zigzag'];
return patterns[gen % patterns.length];
} else {
const patterns = ['bone', 'paw', 'patch'];
return patterns[gen % patterns.length];
}
});
// --- PORTRAIT LOGIC ---
const furColor = computed(() => {
const colors = ['#ffdbac', '#e0e0e0', '#d2b48c', '#ffdead'];
const gen = props.stats.generation || 1;
return colors[gen % colors.length];
});
const eyeColor = computed(() => {
const { str, int, dex } = props.stats;
if (int > 30) return '#2ce8f4'; // Cyan eyes for high INT
if (str > 30) return '#d95763'; // Red eyes for high STR
return '#1b1026'; // Black eyes default
});
const auraColor = computed(() => {
const { str, int, dex } = props.stats;
if (str > int && str > dex) return '#d95763';
if (int > str && int > dex) return '#2ce8f4';
if (dex > str && dex > int) return '#99e550';
return '#ffffff';
});
const mood = computed(() => {
if (props.isDead) return 'dead';
const h = props.stats.happiness;
if (h >= 70) return 'happy';
if (h <= 30) return 'sad';
return 'neutral';
});
const outfitColor = computed(() => {
return props.outfitColor;
});
</script>
<style scoped>
.image-pixelated {
image-rendering: pixelated;
}
</style>