239 lines
11 KiB
Vue
239 lines
11 KiB
Vue
<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, Package, X } from 'lucide-vue-next';
|
|
import PixelFrame from './PixelFrame.vue';
|
|
import PixelButton from './PixelButton.vue';
|
|
|
|
import { ITEM_TYPE, ITEM_RARITY, EQUIPMENT_SLOTS } from '../../../../data/items.js';
|
|
|
|
// Use any types since we're using data directly
|
|
type Item = any;
|
|
|
|
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));
|
|
|
|
// Map data constants to local helpers for template compatibility
|
|
const ItemType = {
|
|
Equipment: ITEM_TYPE.EQUIPMENT,
|
|
Consumable: ITEM_TYPE.CONSUMABLE,
|
|
Talisman: ITEM_TYPE.TALISMAN,
|
|
Special: ITEM_TYPE.SPECIAL,
|
|
Appearance: ITEM_TYPE.APPEARANCE
|
|
};
|
|
|
|
const Rarity = {
|
|
Common: 'common',
|
|
Excellent: 'uncommon',
|
|
Rare: 'rare',
|
|
Epic: 'epic',
|
|
Legendary: 'legendary'
|
|
};
|
|
|
|
const EquipSlot = {
|
|
Weapon: 'weapon',
|
|
Armor: 'armor',
|
|
Hat: 'hat',
|
|
Accessory: 'accessory',
|
|
Charm: 'talisman',
|
|
Special: 'special'
|
|
};
|
|
|
|
const RARITY_COLORS: Record<string, 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<string, any> = {
|
|
[EquipSlot.Weapon]: Sword,
|
|
[EquipSlot.Armor]: Shield,
|
|
[EquipSlot.Hat]: Crown,
|
|
[EquipSlot.Accessory]: Gem,
|
|
[EquipSlot.Charm]: Sparkles,
|
|
[EquipSlot.Special]: Star,
|
|
};
|
|
|
|
const getEquippedItem = (slot: string, isAppearance: boolean) => {
|
|
return props.items.find(i => i.isEquipped && i.slot === slot && !!i.isAppearance === isAppearance);
|
|
};
|
|
</script>
|