pet_data/app/components/pixel/InventoryOverlay.vue

210 lines
10 KiB
Vue
Raw Normal View History

2025-11-26 06:53:44 +00:00
<template>
<div class="flex flex-col h-full gap-2">
<!-- 1. Rarity Legend -->
<div class="flex flex-wrap gap-2 px-2 py-1 bg-[#150c1f] border border-[#4a3b5e] text-[10px]">
<span class="text-[#8f80a0] mr-2">Rarity:</span>
<div v-for="(color, rarity) in RARITY_COLORS" :key="rarity" class="flex items-center gap-1 border border-[#2b193f] px-1 bg-[#0f0816]">
<span :style="{ color: color }">{{ rarity }}</span>
<span class="text-[#4a3b5e]">(10%)</span>
</div>
</div>
<!-- 2. Equipment Slots Grid -->
<div class="grid grid-cols-2 md:grid-cols-3 gap-2">
<div v-for="slot in Object.values(EquipSlot)" :key="slot" class="border border-[#4a3b5e] bg-[#0f0816] p-2 flex flex-col gap-2 relative">
<!-- Slot Header -->
<div class="flex items-center gap-2 mb-1 justify-center border-b border-[#2b193f] pb-1">
<component :is="SLOT_ICONS[slot]" :size="14" class="text-[#8f80a0]" />
<span class="text-[#2ce8f4] text-xs font-bold uppercase tracking-wider">{{ slot }}</span>
</div>
<!-- Actual Slot -->
<div
class="bg-[#150c1f] border border-[#4a3b5e] p-2 min-h-[40px] flex flex-col items-center justify-center cursor-pointer hover:border-[#9fd75b] group"
@click="getEquippedItem(slot, false) && setSelectedItemId(getEquippedItem(slot, false)?.id)"
>
<span class="text-[9px] text-[#8f80a0] mb-0.5">ACTUAL</span>
<span v-if="getEquippedItem(slot, false)" class="text-xs text-center" :style="{ color: RARITY_COLORS[getEquippedItem(slot, false)!.rarity] }">{{ getEquippedItem(slot, false)!.name }}</span>
<span v-else class="text-[10px] text-[#4a3b5e]">Empty</span>
</div>
<!-- Appearance Slot -->
<div
class="bg-[#150c1f] border border-[#4a3b5e] p-2 min-h-[40px] flex flex-col items-center justify-center cursor-pointer hover:border-[#d584fb] group"
@click="getEquippedItem(slot, true) && setSelectedItemId(getEquippedItem(slot, true)?.id)"
>
<span class="text-[9px] text-[#8f80a0] mb-0.5">COSMETIC</span>
<span v-if="getEquippedItem(slot, true)" class="text-xs text-center" :style="{ color: RARITY_COLORS[getEquippedItem(slot, true)!.rarity] }">{{ getEquippedItem(slot, true)!.name }}</span>
<span v-else class="text-[10px] text-[#4a3b5e]">Empty</span>
</div>
</div>
</div>
<!-- 3. Backpack Section -->
<div class="flex-grow flex flex-col md:flex-row gap-2 overflow-hidden mt-2">
<!-- Item Grid -->
<PixelFrame class="flex-grow flex flex-col bg-[#1b1026]" :title="`Backpack (${items.filter(i => !i.isEquipped).length})`">
<div class="flex-grow overflow-y-auto p-1 custom-scrollbar">
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2">
<button
v-for="item in items.filter(i => !i.isEquipped)"
:key="item.id"
@click="setSelectedItemId(item.id)"
class="relative p-2 flex flex-col items-center justify-center gap-1 min-h-[80px] border-2 transition-all group bg-[#2b193f]"
:class="selectedItemId === item.id ? 'border-white bg-[#3d2459]' : 'border-[#4a3b5e] hover:border-[#8f80a0]'"
>
<div class="relative">
<!-- Generic Icons based on type -->
<template v-if="item.type === ItemType.Equipment">
<Sword v-if="item.slot === EquipSlot.Weapon" :color="RARITY_COLORS[item.rarity]" />
<Shield v-else-if="item.slot === EquipSlot.Armor" :color="RARITY_COLORS[item.rarity]" />
<Crown v-else-if="item.slot === EquipSlot.Hat" :color="RARITY_COLORS[item.rarity]" />
<Gem v-else-if="item.slot === EquipSlot.Accessory" :color="RARITY_COLORS[item.rarity]" />
<Sparkles v-else-if="item.slot === EquipSlot.Charm" :color="RARITY_COLORS[item.rarity]" />
<Star v-else :color="RARITY_COLORS[item.rarity]" />
</template>
<template v-else>
<Zap v-if="item.name.includes('Potion')" :color="RARITY_COLORS[item.rarity]" />
<Heart v-else :color="RARITY_COLORS[item.rarity]" />
</template>
<span v-if="item.quantity && item.quantity > 1" class="absolute -bottom-2 -right-2 text-[10px] bg-black text-white px-1 border border-[#4a3b5e]">{{ item.quantity }}</span>
</div>
<span class="text-[10px] text-center leading-tight line-clamp-2" :style="{ color: RARITY_COLORS[item.rarity] }">
{{ item.name }}
</span>
</button>
</div>
</div>
</PixelFrame>
<!-- Selected Item Detail -->
<div class="w-full md:w-1/3 min-h-[200px] flex-shrink-0">
<PixelFrame v-if="selectedItem" class="h-full bg-[#150c1f] flex flex-col" highlight>
<!-- Item Header -->
<div class="flex gap-3 mb-2 border-b border-[#4a3b5e] pb-2">
<div class="w-12 h-12 bg-[#0f0816] border border-[#4a3b5e] flex items-center justify-center">
<Shirt v-if="selectedItem.type === ItemType.Equipment" :size="24" :color="RARITY_COLORS[selectedItem.rarity]" />
<Zap v-else :size="24" :color="RARITY_COLORS[selectedItem.rarity]" />
</div>
<div class="flex flex-col">
<span class="font-bold text-sm tracking-wide" :style="{ color: RARITY_COLORS[selectedItem.rarity] }">{{ selectedItem.name }}</span>
<div class="flex gap-2 text-[10px] text-[#8f80a0]">
<span>{{ selectedItem.rarity }}</span>
<span></span>
<span>{{ selectedItem.type }}</span>
</div>
</div>
</div>
<!-- Description -->
<div class="mb-2">
<p class="text-xs text-[#e0d8f0] italic mb-2">"{{ selectedItem.description }}"</p>
<!-- Stats Block -->
<div v-if="selectedItem.statsDescription" class="bg-[#0f0816] border border-[#4a3b5e] p-2 mb-2">
<span class="text-[10px] text-[#99e550] block mb-1">EFFECTS:</span>
<span class="text-xs text-[#2ce8f4]">{{ selectedItem.statsDescription }}</span>
</div>
<div v-if="selectedItem.effects && selectedItem.effects.length > 0" class="flex flex-col gap-1">
<span v-for="(eff, i) in selectedItem.effects" :key="i" class="text-[10px] text-[#9fd75b]">+ {{ eff }}</span>
</div>
</div>
<!-- Actions -->
<div class="mt-auto flex flex-col gap-2">
<div v-if="selectedItem.type === ItemType.Equipment" class="grid grid-cols-2 gap-2">
<PixelButton
class="text-[10px] py-1"
:disabled="selectedItem.isEquipped && !selectedItem.isAppearance"
@click="$emit('equip', selectedItem.id, false)"
>
{{ selectedItem.isEquipped && !selectedItem.isAppearance ? 'EQUIPPED' : 'EQUIP' }}
</PixelButton>
<PixelButton
variant="secondary"
class="text-[10px] py-1"
:disabled="selectedItem.isEquipped && selectedItem.isAppearance"
@click="$emit('equip', selectedItem.id, true)"
>
COSMETIC
</PixelButton>
</div>
<PixelButton v-if="selectedItem.type === ItemType.Consumable" @click="$emit('use', selectedItem.id)">USE ITEM</PixelButton>
<div class="flex justify-between mt-2 pt-2 border-t border-[#4a3b5e]">
<button
v-if="selectedItem.isEquipped"
@click="$emit('unequip', selectedItem.slot!, selectedItem.isAppearance!)"
class="text-[#f6b26b] text-xs hover:underline"
>
Unequip
</button>
<button
@click="$emit('delete', selectedItem.id)"
class="text-[#d95763] text-xs hover:text-red-400 flex items-center gap-1 ml-auto"
>
<Trash2 :size="10" /> Delete
</button>
</div>
</div>
</PixelFrame>
<PixelFrame v-else class="h-full bg-[#150c1f] flex items-center justify-center text-[#4a3b5e]">
<div class="text-center">
<HelpCircle :size="32" class="mx-auto mb-2 opacity-50" />
<span class="text-xs">Select an item<br/>to view details</span>
</div>
</PixelFrame>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { Sword, Shield, Crown, Gem, Sparkles, Star, Shirt, HelpCircle, Trash2, Zap, Heart } from 'lucide-vue-next';
import PixelFrame from './PixelFrame.vue';
import PixelButton from './PixelButton.vue';
import { ItemType, EquipSlot, Rarity } from '~/types/pixel';
import type { Item } from '~/types/pixel';
interface Props {
items: Item[];
}
const props = defineProps<Props>();
defineEmits(['equip', 'unequip', 'use', 'delete']);
const selectedItemId = ref<string | null>(null);
const selectedItem = computed(() => props.items.find(i => i.id === selectedItemId.value));
const RARITY_COLORS: Record<Rarity, string> = {
[Rarity.Common]: '#9ca3af', // Gray
[Rarity.Excellent]: '#9fd75b', // Green
[Rarity.Rare]: '#2ce8f4', // Blue
[Rarity.Epic]: '#d584fb', // Purple
[Rarity.Legendary]: '#ffa500', // Orange
};
const SLOT_ICONS: Record<EquipSlot, any> = {
[EquipSlot.Weapon]: Sword,
[EquipSlot.Armor]: Shield,
[EquipSlot.Hat]: Crown,
[EquipSlot.Accessory]: Gem,
[EquipSlot.Charm]: Sparkles,
[EquipSlot.Special]: Star,
};
const getEquippedItem = (slot: EquipSlot, isAppearance: boolean) => {
return props.items.find(i => i.isEquipped && i.slot === slot && !!i.isAppearance === isAppearance);
};
</script>