Files
pokedex/src/pages/PokemonSpecies.vue
2026-02-07 16:54:28 -06:00

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>