247 lines
10 KiB
Vue
247 lines
10 KiB
Vue
<script setup>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { appState } from '../store'
|
|
import PageLayout from '../components/layout/PageLayout.vue'
|
|
import { useScreenActions } from '../composables/useScreenActions'
|
|
|
|
const { setButtonActions } = useScreenActions()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const pokemon = ref(null)
|
|
const speciesData = ref(null)
|
|
const loading = ref(true)
|
|
|
|
const genderRateText = computed(() => {
|
|
if (!speciesData.value) return 'Unknown'
|
|
const rate = speciesData.value.gender_rate
|
|
if (rate === -1) return 'Genderless'
|
|
const femalePercent = (rate / 8) * 100
|
|
const malePercent = 100 - femalePercent
|
|
return `♀ ${femalePercent}% / ♂ ${malePercent}%`
|
|
})
|
|
|
|
const captureRateBars = computed(() => {
|
|
if (!speciesData.value) return 0
|
|
return Math.ceil((speciesData.value.capture_rate / 255) * 20)
|
|
})
|
|
|
|
function formatName(name) {
|
|
return name
|
|
.split('-')
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
.join(' ')
|
|
}
|
|
|
|
function getPokemonData() {
|
|
const currentId = parseInt(route.params.id)
|
|
loading.value = true
|
|
|
|
fetch(`https://pokeapi.co/api/v2/pokemon/${currentId}`)
|
|
.then((response) => {
|
|
if (!response.ok) {
|
|
router.push({ name: '404', params: { catchAll: 'not-found' } })
|
|
return null
|
|
}
|
|
return response.json()
|
|
})
|
|
.then((json) => {
|
|
if (json) pokemon.value = json
|
|
})
|
|
|
|
fetch(`https://pokeapi.co/api/v2/pokemon-species/${currentId}`)
|
|
.then((response) => {
|
|
if (!response.ok) return null
|
|
return response.json()
|
|
})
|
|
.then((json) => {
|
|
if (json) speciesData.value = json
|
|
loading.value = false
|
|
})
|
|
}
|
|
|
|
getPokemonData()
|
|
|
|
onMounted(() => {
|
|
appState.setPageDisplay(
|
|
'M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8zm4-8c0-2.2-1.8-4-4-4s-4 1.8-4 4 1.8 4 4 4 4-1.8 4-4z',
|
|
'SPECIES'
|
|
)
|
|
|
|
setButtonActions([
|
|
{ buttonNumber: 1, label: 'BACK', action: () => router.push(`/pokemon/${route.params.id}`) }
|
|
])
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<PageLayout
|
|
v-if="pokemon && !loading"
|
|
:title="pokemon.name"
|
|
:badge="pokemon.types.map(t => t.type.name)"
|
|
subtitle="Species Data"
|
|
statusText="Species Information"
|
|
>
|
|
<div v-if="!speciesData" class="flex items-center justify-center h-full">
|
|
<div class="text-center space-y-2">
|
|
<div class="text-zinc-600 text-xs">⚠</div>
|
|
<p class="text-zinc-500 text-xs">No species data available</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="flex flex-col h-full overflow-hidden p-3 gap-2">
|
|
<div class="flex-1 overflow-y-auto custom-scrollbar space-y-2">
|
|
|
|
<div v-if="speciesData.is_baby || speciesData.is_legendary || speciesData.is_mythical" class="flex gap-1.5">
|
|
<span v-if="speciesData.is_baby" class="text-[8px] bg-pink-900/30 text-pink-300 px-2 py-1 rounded uppercase border border-pink-800">
|
|
Baby
|
|
</span>
|
|
<span v-if="speciesData.is_legendary" class="text-[8px] bg-yellow-900/30 text-yellow-300 px-2 py-1 rounded uppercase border border-yellow-800">
|
|
Legendary
|
|
</span>
|
|
<span v-if="speciesData.is_mythical" class="text-[8px] bg-purple-900/30 text-purple-300 px-2 py-1 rounded uppercase border border-purple-800">
|
|
Mythical
|
|
</span>
|
|
</div>
|
|
|
|
<div class="border border-zinc-800 bg-zinc-950/50 rounded overflow-hidden">
|
|
<div class="bg-zinc-900 px-2 py-1 border-b border-zinc-800">
|
|
<h3 class="text-[9px] font-bold text-zinc-300 uppercase tracking-wide">Base Stats</h3>
|
|
</div>
|
|
<div class="p-2 space-y-2">
|
|
<div class="space-y-1">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Capture Rate</span>
|
|
<span class="text-[9px] text-zinc-400 font-mono">{{ speciesData.capture_rate }}/255</span>
|
|
</div>
|
|
<div class="flex gap-0.5 h-1">
|
|
<div v-for="i in 20" :key="i"
|
|
class="flex-1 rounded-sm"
|
|
:class="i <= captureRateBars ? 'bg-green-500' : 'bg-zinc-800'">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-baseline justify-between py-1">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Base Happiness</span>
|
|
<span class="text-[10px] text-zinc-300 font-mono">{{ speciesData.base_happiness }}</span>
|
|
</div>
|
|
|
|
<div class="flex items-baseline justify-between py-1">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Gender Ratio</span>
|
|
<span class="text-[9px] text-zinc-300 font-mono">{{ genderRateText }}</span>
|
|
</div>
|
|
|
|
<div class="flex items-baseline justify-between py-1">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Hatch Counter</span>
|
|
<span class="text-[10px] text-zinc-300 font-mono">{{ speciesData.hatch_counter }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border border-zinc-800 bg-zinc-950/50 rounded overflow-hidden">
|
|
<div class="bg-zinc-900 px-2 py-1 border-b border-zinc-800">
|
|
<h3 class="text-[9px] font-bold text-zinc-300 uppercase tracking-wide">Classification</h3>
|
|
</div>
|
|
<div class="p-2 space-y-1.5">
|
|
<div class="flex items-baseline justify-between py-0.5">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Generation</span>
|
|
<span class="text-[9px] text-zinc-300">{{ formatName(speciesData.generation.name) }}</span>
|
|
</div>
|
|
|
|
<div class="flex items-baseline justify-between py-0.5">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Growth Rate</span>
|
|
<span class="text-[9px] text-zinc-300 capitalize">{{ speciesData.growth_rate.name }}</span>
|
|
</div>
|
|
|
|
<div v-if="speciesData.habitat" class="flex items-baseline justify-between py-0.5">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Habitat</span>
|
|
<span class="text-[9px] text-zinc-300 capitalize">{{ speciesData.habitat.name }}</span>
|
|
</div>
|
|
|
|
<div v-if="speciesData.egg_groups && speciesData.egg_groups.length > 0" class="space-y-1 pt-1">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Egg Groups</span>
|
|
<div class="flex gap-1 flex-wrap">
|
|
<span
|
|
v-for="group in speciesData.egg_groups"
|
|
:key="group.name"
|
|
class="text-[8px] bg-zinc-800 text-zinc-400 px-1.5 py-0.5 rounded capitalize"
|
|
>
|
|
{{ group.name }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="speciesData.evolves_from_species" class="border border-zinc-800 bg-zinc-950/50 rounded overflow-hidden">
|
|
<div class="bg-zinc-900 px-2 py-1 border-b border-zinc-800">
|
|
<h3 class="text-[9px] font-bold text-zinc-300 uppercase tracking-wide">Evolution</h3>
|
|
</div>
|
|
<div class="p-2">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-[9px] text-zinc-500 uppercase">Evolves From</span>
|
|
<span class="text-[10px] text-zinc-300 capitalize">{{ formatName(speciesData.evolves_from_species.name) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="speciesData.pokedex_numbers && speciesData.pokedex_numbers.length > 0" class="border border-zinc-800 bg-zinc-950/50 rounded overflow-hidden">
|
|
<div class="bg-zinc-900 px-2 py-1 border-b border-zinc-800">
|
|
<h3 class="text-[9px] font-bold text-zinc-300 uppercase tracking-wide">Pokedex Entries</h3>
|
|
</div>
|
|
<div class="p-2 space-y-1">
|
|
<div
|
|
v-for="entry in speciesData.pokedex_numbers"
|
|
:key="entry.pokedex.name"
|
|
class="flex items-baseline justify-between py-0.5"
|
|
>
|
|
<span class="text-[9px] text-zinc-500 capitalize">{{ formatName(entry.pokedex.name) }}</span>
|
|
<span class="text-[9px] text-zinc-400 font-mono">#{{ String(entry.entry_number).padStart(3, '0') }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="speciesData.has_gender_differences || speciesData.forms_switchable" class="border border-zinc-800 bg-zinc-950/50 rounded overflow-hidden">
|
|
<div class="bg-zinc-900 px-2 py-1 border-b border-zinc-800">
|
|
<h3 class="text-[9px] font-bold text-zinc-300 uppercase tracking-wide">Additional Info</h3>
|
|
</div>
|
|
<div class="p-2 space-y-1">
|
|
<div v-if="speciesData.has_gender_differences" class="text-[9px] text-zinc-400">
|
|
✓ Has gender differences
|
|
</div>
|
|
<div v-if="speciesData.forms_switchable" class="text-[9px] text-zinc-400">
|
|
✓ Forms are switchable
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</PageLayout>
|
|
|
|
<PageLayout
|
|
v-else
|
|
title="LOADING"
|
|
subtitle="Please wait..."
|
|
>
|
|
<div class="flex items-center justify-center h-full">
|
|
<div class="flex flex-col items-center gap-4">
|
|
<div class="relative w-16 h-16">
|
|
<div class="absolute inset-0 border-2 border-zinc-800 rounded-full"></div>
|
|
<div class="absolute inset-0 border-t-2 border-red-500 rounded-full animate-spin"></div>
|
|
<div class="absolute inset-4 bg-zinc-900 rounded-full flex items-center justify-center shadow-[inset_0_0_10px_rgba(0,0,0,0.5)]">
|
|
<div class="w-2 h-2 bg-red-500 rounded-full animate-ping"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-center">
|
|
<div class="text-xs font-bold tracking-[0.2em] text-zinc-500 mb-1">LOADING DATA</div>
|
|
<div class="text-[10px] text-zinc-700 animate-pulse">Fetching species information...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</PageLayout>
|
|
</template>
|