<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>究極のポケモン図鑑 MAX + 色違い</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700;900&display=swap');
body { font-family: 'Noto Sans JP', sans-serif; overflow-x: hidden; }
@keyframes fadeInZoom {
from { opacity: 0; transform: scale(0.95) translateY(10px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.animate-zoom-in { animation: fadeInZoom 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; }
@keyframes mega-glow {
0% { filter: drop-shadow(0 0 5px #fff) brightness(1); }
50% { filter: drop-shadow(0 0 20px #a855f7) brightness(1.5); }
100% { filter: drop-shadow(0 0 5px #fff) brightness(1); }
}
.mega-active { animation: mega-glow 2s infinite ease-in-out; }
@keyframes shiny-sparkle {
0%, 100% { opacity: 0; transform: scale(0) rotate(0deg); }
50% { opacity: 1; transform: scale(1.2) rotate(180deg); }
}
.shiny-sparkle-icon { animation: shiny-sparkle 1.5s infinite; }
@keyframes gold-pulse {
0% { box-shadow: 0 0 0 0 rgba(234, 179, 8, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(234, 179, 8, 0); }
100% { box-shadow: 0 0 0 0 rgba(234, 179, 8, 0); }
}
.shiny-card { animation: gold-pulse 2s infinite; border-color: #eab308 !important; }
@keyframes attack-right {
0% { transform: translateX(0); }
50% { transform: translateX(50px); }
100% { transform: translateX(0); }
}
.animate-attack-right { animation: attack-right 0.3s ease-in-out; }
@keyframes attack-left {
0% { transform: translateX(0); }
50% { transform: translateX(-50px); }
100% { transform: translateX(0); }
}
.animate-attack-left { animation: attack-left 0.3s ease-in-out; }
.hp-bar-transition { transition: width 0.5s ease-out; }
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: #f1f1f1; }
::-webkit-scrollbar-thumb { background: #ef4444; border-radius: 10px; }
.pokemon-card { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
.pokemon-card:hover { transform: translateY(-5px); }
.custom-modal { display: none; }
.custom-modal.active { display: flex; }
</style>
</head>
<body class="bg-slate-100 text-slate-800 min-h-screen pb-20">
<!-- タイトルがめん -->
<div id="start-screen" class="fixed inset-0 z-[100] bg-red-600 flex flex-col items-center justify-center text-white p-6">
<div class="relative z-10 flex flex-col items-center gap-8 animate-zoom-in text-center">
<div class="flex items-center gap-4">
<div class="w-16 h-16 bg-white rounded-full border-8 border-slate-900 flex items-center justify-center relative shadow-2xl">
<div class="w-full h-2 bg-slate-900 absolute top-1/2 -translate-y-1/2"></div>
<div class="w-6 h-6 bg-white rounded-full border-4 border-slate-900 relative z-10"></div>
</div>
<h1 class="text-5xl sm:text-6xl font-black italic tracking-tighter uppercase drop-shadow-lg">PokeBattle SHINY</h1>
</div>
<div class="flex flex-col gap-4 w-64">
<button id="btn-continue" class="bg-white text-red-600 hover:bg-slate-100 py-4 px-8 rounded-2xl font-black text-xl shadow-xl transition-all active:scale-95 disabled:opacity-50">
つづきから
</button>
<button id="btn-new-game" class="bg-slate-900 text-white hover:bg-slate-800 py-4 px-8 rounded-2xl font-black text-xl shadow-xl transition-all active:scale-95">
はじめから
</button>
</div>
<p class="text-sm font-bold text-red-200 mt-4">いろちがい しゅつげんりつ アップちゅう!</p>
</div>
</div>
<audio id="bgm-player" loop>
<source src="https://petitacode.web2050.jp/users/original/kizu/%E6%98%9F%E6%98%8E%E3%81%8B%E3%82%8A%E3%81%AE%E5%90%88%E5%9B%B3.mp3" type="audio/mpeg">
</audio>
<div id="game-container" class="opacity-0 transition-opacity duration-700">
<header class="bg-red-600 text-white p-4 shadow-lg sticky top-0 z-30">
<div class="max-w-6xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-3">
<i data-lucide="zap" class="w-6 h-6 text-yellow-300"></i>
<h1 class="text-xl font-black italic tracking-tighter uppercase">POKEBATTLE</h1>
</div>
<div class="flex items-center gap-2 w-full sm:w-auto">
<div class="relative flex-1 sm:w-64 text-slate-800">
<input type="text" id="search-input" placeholder="なまえや ばんごうで けんさく..." class="w-full pl-10 pr-4 py-2 rounded-xl focus:outline-none shadow-md border-none text-sm" />
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 w-4 h-4"></i>
</div>
<button id="filter-mega" class="bg-purple-600 text-white p-2 rounded-xl shadow-md hover:bg-purple-700 transition-colors flex items-center gap-2 px-3">
<i data-lucide="zap" class="w-4 h-4"></i>
<span class="text-xs font-bold hidden md:inline">めが</span>
</button>
<button id="filter-shiny" class="bg-yellow-500 text-white p-2 rounded-xl shadow-md hover:bg-yellow-600 transition-colors flex items-center gap-2 px-3">
<i data-lucide="star" class="w-4 h-4"></i>
<span class="text-xs font-bold hidden md:inline">いろちがい</span>
</button>
<button id="view-collection" class="bg-slate-900 text-white p-2 rounded-xl shadow-md hover:bg-slate-800 transition-colors relative">
<i data-lucide="briefcase" class="w-5 h-5"></i>
<span id="collection-count" class="absolute -top-2 -right-2 bg-yellow-400 text-slate-900 text-[10px] font-black min-w-[20px] h-5 px-1 rounded-full flex items-center justify-center border-2 border-slate-900">0</span>
</button>
</div>
</div>
</header>
<main class="max-w-6xl mx-auto p-4 sm:p-8">
<div id="view-title-container" class="mb-6 flex justify-between items-center">
<div class="flex items-center gap-4">
<button id="btn-back-to-grid" class="hidden bg-slate-200 hover:bg-slate-300 text-slate-700 p-2 rounded-full transition-all active:scale-95">
<i data-lucide="arrow-left" class="w-5 h-5"></i>
</button>
<h2 id="view-title-text" class="text-lg font-black text-slate-400 uppercase tracking-widest italic">ポケモンずかん</h2>
</div>
<span id="status-text" class="text-xs font-bold text-slate-400">よみこみちゅう...</span>
</div>
<div id="pokemon-grid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6"></div>
<div id="loading" class="flex flex-col items-center justify-center py-20 gap-4">
<div class="animate-spin rounded-full h-12 w-12 border-4 border-slate-200 border-t-red-500"></div>
<p class="text-slate-400 font-bold animate-pulse">データを読み込み中...</p>
</div>
<div id="no-results" class="hidden flex flex-col items-center py-20 opacity-50 text-center w-full">
<i data-lucide="search-x" class="w-16 h-16 mb-4 mx-auto"></i>
<p class="text-xl font-medium">みつかりませんでした</p>
</div>
</main>
</div>
<!-- つうちメッセージ -->
<div id="message-modal" class="fixed inset-0 z-[200] bg-slate-900/60 backdrop-blur-sm items-center justify-center p-4 custom-modal">
<div class="bg-white rounded-3xl p-8 max-w-sm w-full shadow-2xl animate-zoom-in text-center">
<div id="msg-icon-container" class="w-16 h-16 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-4 text-red-600">
<i data-lucide="info" class="w-8 h-8"></i>
</div>
<p id="message-text" class="text-lg font-bold text-slate-800 mb-6"></p>
<button id="close-msg-btn" class="w-full bg-slate-900 text-white py-3 rounded-xl font-black shadow-lg">とじる</button>
</div>
</div>
<!-- ポケモンしょうさい -->
<div id="pokemon-modal" class="fixed inset-0 bg-slate-900/80 backdrop-blur-md flex items-center justify-center p-4 z-50 hidden">
<div class="bg-white rounded-3xl shadow-2xl w-full max-w-lg overflow-hidden relative animate-zoom-in max-h-[90vh] overflow-y-auto">
<button id="close-modal" class="absolute top-4 right-4 text-white hover:bg-black/20 bg-black/10 rounded-full p-2 z-20">
<i data-lucide="x"></i>
</button>
<div id="modal-content" class="hidden">
<div id="modal-header" class="pt-12 pb-20 px-8 relative bg-slate-400">
<div class="relative z-10 text-white">
<div class="flex items-center gap-2 mb-2">
<span id="modal-id" class="text-sm font-black bg-black/20 px-3 py-1 rounded-full inline-block"></span>
<span id="mega-badge" class="hidden text-[10px] font-black bg-purple-500 text-white px-2 py-1 rounded-full uppercase italic animate-pulse border border-white">めが</span>
<span id="shiny-badge" class="hidden text-[10px] font-black bg-yellow-400 text-slate-900 px-2 py-1 rounded-full uppercase italic border border-white">いろちがい</span>
</div>
<h2 id="modal-name-jp" class="text-4xl font-black mb-1 text-white"></h2>
<div id="modal-types" class="flex gap-2 mt-4"></div>
</div>
</div>
<div class="bg-white px-8 pb-10 pt-16 relative rounded-t-[2.5rem] -mt-10">
<div class="absolute -top-32 left-1/2 -translate-x-1/2 w-48 h-48">
<img id="modal-image" src="" class="w-full h-full object-contain" />
</div>
<div class="flex flex-col gap-3 mb-8">
<div class="flex gap-2">
<button id="start-battle-btn" class="flex-1 bg-orange-500 hover:bg-orange-600 text-white font-black py-4 px-6 rounded-2xl shadow-lg flex items-center justify-center gap-3 transition-transform active:scale-95">
<i data-lucide="swords"></i> バトル
</button>
<button id="catch-btn" class="flex-1 bg-red-500 hover:bg-red-600 text-white font-black py-4 px-6 rounded-2xl shadow-lg flex items-center justify-center gap-3 transition-transform active:scale-95">
<i data-lucide="target"></i> つかまえる
</button>
</div>
<p id="already-caught-msg" class="hidden text-green-600 font-bold text-center bg-green-50 py-2 rounded-xl">
<i data-lucide="check-circle" class="inline w-4 h-4"></i> つかまえた!
</p>
</div>
<div id="modal-stats" class="space-y-3"></div>
</div>
</div>
</div>
</div>
<!-- バトルがめん -->
<div id="battle-scene" class="fixed inset-0 bg-gradient-to-b from-blue-900 to-indigo-900 z-[70] hidden overflow-hidden flex flex-col">
<div class="flex-1 relative flex items-center justify-end pr-10 sm:pr-20">
<div class="relative">
<div id="enemy-shiny-sparkles" class="absolute inset-0 pointer-events-none hidden">
<i data-lucide="sparkles" class="absolute -top-4 -right-4 text-yellow-300 w-12 h-12 shiny-sparkle-icon"></i>
<i data-lucide="star" class="absolute -bottom-4 -left-4 text-yellow-300 w-8 h-8 shiny-sparkle-icon" style="animation-delay: 0.5s"></i>
</div>
<img id="enemy-img" src="" class="w-48 h-48 sm:w-64 sm:h-64 object-contain relative z-10" />
<div class="absolute top-0 -left-64 bg-white/90 backdrop-blur p-4 rounded-br-3xl rounded-tl-3xl shadow-xl w-56 border-l-8 border-red-500">
<p id="enemy-name" class="font-black text-slate-800 truncate"></p>
<div class="flex items-center gap-2 mt-2">
<div class="flex-1 h-3 bg-slate-200 rounded-full overflow-hidden">
<div id="enemy-hp-bar" class="h-full bg-green-500 hp-bar-transition" style="width: 100%"></div>
</div>
</div>
</div>
</div>
</div>
<div class="flex-1 relative flex items-center justify-start pl-10 sm:pl-20">
<div class="relative">
<div id="player-shiny-sparkles" class="absolute inset-0 pointer-events-none hidden">
<i data-lucide="sparkles" class="absolute -top-4 -right-4 text-yellow-300 w-12 h-12 shiny-sparkle-icon"></i>
</div>
<img id="player-img" src="" class="w-48 h-48 sm:w-64 sm:h-64 object-contain relative z-10 scale-x-[-1]" />
<div class="absolute -top-10 -right-64 bg-white/90 backdrop-blur p-4 rounded-bl-3xl rounded-tr-3xl shadow-xl w-56 border-r-8 border-blue-500">
<p id="player-name" class="font-black text-slate-800 truncate"></p>
<div class="flex items-center gap-2 mt-2">
<div class="flex-1 h-3 bg-slate-200 rounded-full overflow-hidden">
<div id="player-hp-bar" class="h-full bg-green-500 hp-bar-transition" style="width: 100%"></div>
</div>
</div>
<p class="text-right text-[10px] font-bold mt-1 text-slate-400"><span id="player-hp-text">100</span> / 100</p>
</div>
</div>
</div>
<div class="h-1/3 bg-slate-900 border-t-4 border-slate-800 p-4 flex flex-col sm:flex-row gap-4">
<div id="battle-log" class="flex-1 bg-black/40 rounded-xl p-4 text-white text-xs font-medium overflow-y-auto">
<p class="text-emerald-400">バトル かいし!</p>
</div>
<div id="battle-actions" class="w-full sm:w-80 grid grid-cols-2 gap-2"></div>
</div>
</div>
<div id="select-modal" class="fixed inset-0 bg-slate-900/90 backdrop-blur-sm flex items-center justify-center p-4 z-[60] hidden">
<div class="bg-white rounded-3xl p-6 w-full max-w-md shadow-2xl">
<h3 class="text-xl font-black mb-4 italic">だす ポケモンを えらぶ</h3>
<div id="my-pokemon-list" class="grid grid-cols-2 gap-4 max-h-80 overflow-y-auto mb-6 p-2"></div>
<button id="close-select-modal" class="w-full py-3 bg-slate-200 rounded-xl font-bold">やめる</button>
</div>
</div>
<script>
// --- 変数の宣言 (へんすうの せんげん) ---
let allPokemons = [];
let currentDisplayList = [];
let caughtPokemons = JSON.parse(localStorage.getItem('my_pokemons')) || [];
let jpNameCache = {};
let moveCache = {};
let itemsToShow = 60;
let isMusicPlaying = false;
let battleState = null;
let currentPokemonData = null;
const SHINY_CHANCE = 0.08;
const typeColors = {
normal: 'bg-[#949495]', fire: 'bg-[#ff4422]', water: 'bg-[#3399ff]',
electric: 'bg-[#ffcc33]', grass: 'bg-[#77cc55]', ice: 'bg-[#66ccff]',
fighting: 'bg-[#bb5544]', poison: 'bg-[#aa5599]', ground: 'bg-[#ddbb55]',
flying: 'bg-[#8899ff]', psychic: 'bg-[#ff5599]', bug: 'bg-[#aabb22]',
rock: 'bg-[#bbaa66]', ghost: 'bg-[#6666bb]', dragon: 'bg-[#7766ee]',
dark: 'bg-[#775544]', steel: 'bg-[#aaaabb]', fairy: 'bg-[#ee99ee]',
};
const typeNamesJP = {
normal: 'ノーマル', fire: 'ほのお', water: 'みず', electric: 'でんき', grass: 'くさ', ice: 'こおり', fighting: 'かくとう', poison: 'どく',
ground: 'じめん', flying: 'ひこう', psychic: 'エスパー', bug: 'むし', rock: 'いわ', ghost: 'ゴースト', dragon: 'ドラゴン', dark: 'あく',
steel: 'はがね', fairy: 'フェアリー'
};
const statNamesJP = {
hp: 'HP',
attack: 'こうげき',
defense: 'ぼうぎょ',
'special-attack': 'とくこう',
'special-defense': 'とくぼう',
speed: 'すばやさ'
};
// --- ユーティリティ ---
// カタカナに直すため、以前の toHiragana 変換処理を削除、または呼び出しを停止します
const bgmPlayer = document.getElementById('bgm-player');
function showMessage(text, icon = 'info') {
const modal = document.getElementById('message-modal');
const textEl = document.getElementById('message-text');
const iconContainer = document.getElementById('msg-icon-container');
textEl.textContent = text;
iconContainer.innerHTML = `<i data-lucide="${icon}" class="w-8 h-8"></i>`;
lucide.createIcons();
modal.classList.add('active');
}
document.getElementById('close-msg-btn').onclick = () => {
document.getElementById('message-modal').classList.remove('active');
};
function updateCollectionCount() {
document.getElementById('collection-count').textContent = caughtPokemons.length;
}
// --- メイン処理 (めいんの しょり) ---
async function init() {
lucide.createIcons();
updateCollectionCount();
const continueBtn = document.getElementById('btn-continue');
if (caughtPokemons.length === 0) {
continueBtn.disabled = true;
continueBtn.classList.add('opacity-30');
}
document.getElementById('btn-new-game').onclick = () => {
if (caughtPokemons.length > 0) {
showMessage("あたらしく はじめると、きろくが きえてしまいます。いいですか?", "alert-triangle");
const originalClose = document.getElementById('close-msg-btn').onclick;
document.getElementById('close-msg-btn').onclick = () => {
localStorage.removeItem('my_pokemons');
caughtPokemons = [];
updateCollectionCount();
startGame();
document.getElementById('message-modal').classList.remove('active');
document.getElementById('close-msg-btn').onclick = originalClose;
};
return;
}
startGame();
};
document.getElementById('btn-continue').onclick = startGame;
try {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=1300`);
const data = await response.json();
allPokemons = data.results
.map((p) => {
const id = p.url.split('/').filter(Boolean).pop();
const isMega = parseInt(id) > 10000 && p.name.includes('-mega');
const isStandard = parseInt(id) <= 1025;
if (!isMega && !isStandard) return null;
const isShiny = Math.random() < SHINY_CHANCE;
return {
id: id,
name: p.name,
isMega: isMega,
isShiny: isShiny,
image: isShiny
? `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/shiny/${id}.png`
: `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${id}.png`
};
})
.filter(p => p !== null);
currentDisplayList = allPokemons;
await renderGrid(currentDisplayList.slice(0, itemsToShow));
document.getElementById('loading').classList.add('hidden');
document.getElementById('status-text').textContent = `${allPokemons.length}ひき はっけん`;
window.addEventListener('scroll', () => {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
loadMoreItems();
}
});
} catch (e) { console.error(e); }
}
function loadMoreItems() {
if (itemsToShow >= currentDisplayList.length) return;
const nextBatch = 60;
const newItems = currentDisplayList.slice(itemsToShow, itemsToShow + nextBatch);
appendItemsToGrid(newItems);
itemsToShow += nextBatch;
}
function appendItemsToGrid(list) {
const grid = document.getElementById('pokemon-grid');
list.forEach(p => grid.appendChild(createPokemonCard(p)));
}
async function fetchJapaneseName(id) {
if (jpNameCache[id]) return jpNameCache[id];
try {
let speciesId = id;
let prefix = "";
if (parseInt(id) > 10000) {
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const data = await res.json();
speciesId = data.species.url.split('/').filter(Boolean).pop();
prefix = "メガ ";
}
const res = await fetch(`https://pokeapi.co/api/v2/pokemon-species/${speciesId}/`);
const data = await res.json();
// カタカナの名前(ja)を優先的に取得
const jpName = data.names.find(n => n.language.name === 'ja')?.name ||
data.names.find(n => n.language.name === 'ja-Hrkt')?.name ||
data.name;
const finalName = prefix + jpName;
jpNameCache[id] = finalName;
return finalName;
} catch (e) { return "???"; }
}
async function fetchMoveDetails(url) {
if (moveCache[url]) return moveCache[url];
try {
const res = await fetch(url);
const data = await res.json();
const jpName = data.names.find(n => n.language.name === 'ja')?.name ||
data.names.find(n => n.language.name === 'ja-Hrkt')?.name ||
data.name;
const moveData = { name: jpName, power: data.power || 40, type: data.type.name };
moveCache[url] = moveData;
return moveData;
} catch (e) { return { name: "たいあたり", power: 40, type: "normal" }; }
}
async function showDetail(id) {
const modal = document.getElementById('pokemon-modal');
const content = document.getElementById('modal-content');
content.classList.add('hidden');
modal.classList.remove('hidden');
const listInstance = currentDisplayList.find(p => p.id === id);
const isShiny = listInstance ? listInstance.isShiny : false;
try {
const pRes = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const data = await pRes.json();
let speciesData = { varieties: [] };
try {
const sId = data.species.url.split('/').filter(Boolean).pop();
const sRes = await fetch(`https://pokeapi.co/api/v2/pokemon-species/${sId}/`);
speciesData = await sRes.json();
} catch(e) {}
currentPokemonData = data;
currentPokemonData.isShiny = isShiny;
currentPokemonData.potentialMoves = data.moves.slice(0, 10);
const jpName = await fetchJapaneseName(id);
currentPokemonData.jpName = jpName;
const megaVarieties = speciesData.varieties.filter(v => v.pokemon.name.includes('-mega'));
document.getElementById('mega-badge').classList.toggle('hidden', megaVarieties.length === 0);
document.getElementById('shiny-badge').classList.toggle('hidden', !isShiny);
const mainType = data.types[0].type.name;
document.getElementById('modal-header').className = `pt-12 pb-20 px-8 relative ${typeColors[mainType] || 'bg-slate-400'}`;
document.getElementById('modal-name-jp').textContent = jpName;
document.getElementById('modal-id').textContent = `#${String(id).padStart(3, '0')}`;
const artworkUrl = isShiny
? data.sprites.other['official-artwork'].front_shiny || data.sprites.front_shiny
: data.sprites.other['official-artwork'].front_default || data.sprites.front_default;
document.getElementById('modal-image').src = artworkUrl;
const isCaught = caughtPokemons.some(cp => cp.id === String(id) && cp.isShiny === isShiny);
document.getElementById('catch-btn').classList.toggle('hidden', isCaught);
document.getElementById('already-caught-msg').classList.toggle('hidden', !isCaught);
document.getElementById('modal-types').innerHTML = data.types.map(t =>
`<span class="bg-white/20 px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest">${typeNamesJP[t.type.name] || t.type.name}</span>`
).join('');
document.getElementById('modal-stats').innerHTML = data.stats.map(s => `
<div class="flex items-center gap-3">
<span class="text-[10px] font-bold text-slate-400 w-24 uppercase">${statNamesJP[s.stat.name] || s.stat.name}</span>
<div class="flex-1 h-2 bg-slate-100 rounded-full overflow-hidden">
<div class="h-full bg-slate-800" style="width: ${Math.min(100, s.base_stat)}%"></div>
</div>
<span class="text-[10px] font-black w-6 text-right">${s.base_stat}</span>
</div>
`).join('');
content.classList.remove('hidden');
} catch (e) { console.error(e); }
}
async function startBattle(playerPoke) {
document.getElementById('select-modal').classList.add('hidden');
document.getElementById('pokemon-modal').classList.add('hidden');
document.getElementById('battle-scene').classList.remove('hidden');
document.getElementById('battle-log').innerHTML = '';
const pMoves = [];
const movePool = playerPoke.rawMoves || [];
for (let i = 0; i < 4 && i < movePool.length; i++) {
const m = await fetchMoveDetails(movePool[i].move.url);
pMoves.push(m);
}
if (pMoves.length === 0) pMoves.push({ name: "たいあたり", power: 40, type: "normal" });
const eMoves = [];
const eMovePool = currentPokemonData.moves.slice(0, 8);
for (let i = 0; i < 2 && i < eMovePool.length; i++) {
const m = await fetchMoveDetails(eMovePool[i].move.url);
eMoves.push(m);
}
if (eMoves.length === 0) eMoves.push({ name: "たいあたり", power: 40, type: "normal" });
battleState = {
active: true,
player: { ...playerPoke, hp: 100, maxHp: 100, isMega: false, moves: pMoves },
enemy: {
name: currentPokemonData.jpName,
hp: 100,
maxHp: 100,
img: document.getElementById('modal-image').src,
id: currentPokemonData.id,
isShiny: currentPokemonData.isShiny,
moves: eMoves
},
turn: 'player'
};
document.getElementById('player-img').src = battleState.player.image;
document.getElementById('player-name').textContent = (battleState.player.isShiny ? '★' : '') + battleState.player.jpName;
document.getElementById('player-shiny-sparkles').classList.toggle('hidden', !battleState.player.isShiny);
document.getElementById('enemy-img').src = battleState.enemy.img;
document.getElementById('enemy-name').textContent = (battleState.enemy.isShiny ? '★' : '') + battleState.enemy.name;
document.getElementById('enemy-shiny-sparkles').classList.toggle('hidden', !battleState.enemy.isShiny);
updateBattleUI();
logMessage(`やせいの ${battleState.enemy.name} が あらわれた!`, "text-yellow-400 font-bold");
if(battleState.enemy.isShiny) logMessage(`きらり! いろちがいだ!`, "text-yellow-200 animate-pulse");
createBattleButtons();
}
function createBattleButtons() {
const container = document.getElementById('battle-actions');
container.innerHTML = '';
battleState.player.moves.forEach(move => {
const b = document.createElement('button');
const typeColor = typeColors[move.type] || 'bg-slate-500';
b.className = `${typeColor} text-white p-3 rounded-xl font-bold shadow-md active:scale-95 transition-all text-xs flex flex-col border-b-4 border-black/20`;
b.innerHTML = `<span>${move.name}</span><span class="text-[8px] opacity-80">いりょく:${move.power}</span>`;
b.onclick = () => playerAttack(move);
container.appendChild(b);
});
}
function playerAttack(move) {
if (battleState.turn !== 'player') return;
battleState.turn = 'enemy';
document.getElementById('battle-actions').innerHTML = '';
logMessage(`${battleState.player.jpName} の ${move.name}!`);
document.getElementById('player-img').classList.add('animate-attack-right');
setTimeout(() => document.getElementById('player-img').classList.remove('animate-attack-right'), 300);
let dmg = (move.power / 4) + (Math.random() * 10);
battleState.enemy.hp = Math.max(0, battleState.enemy.hp - dmg);
setTimeout(() => {
updateBattleUI();
if (battleState.enemy.hp <= 0) {
logMessage(`${battleState.enemy.name} を たおした!`, "text-emerald-400 font-bold");
setTimeout(() => {
showMessage("しょうり! ずかんで つかまえる チャンスです!", "trophy");
document.getElementById('battle-scene').classList.add('hidden');
showDetail(battleState.enemy.id);
}, 1000);
} else {
setTimeout(enemyAttack, 1000);
}
}, 500);
}
function enemyAttack() {
const move = battleState.enemy.moves[Math.floor(Math.random() * battleState.enemy.moves.length)];
logMessage(`${battleState.enemy.name} の ${move.name}!`, "text-red-300");
document.getElementById('enemy-img').classList.add('animate-attack-left');
setTimeout(() => document.getElementById('enemy-img').classList.remove('animate-attack-left'), 300);
const dmg = (move.power / 5) + (Math.random() * 10);
battleState.player.hp = Math.max(0, battleState.player.hp - dmg);
setTimeout(() => {
updateBattleUI();
if (battleState.player.hp <= 0) {
logMessage(`${battleState.player.jpName} は ちからつきた...`, "text-red-400");
setTimeout(() => {
showMessage("まけてしまいました...", "frown");
document.getElementById('battle-scene').classList.add('hidden');
}, 1000);
} else {
battleState.turn = 'player';
createBattleButtons();
}
}, 500);
}
document.getElementById('catch-btn').onclick = async () => {
if (!currentPokemonData) return;
const success = Math.random() > 0.3;
if (success) {
const newItem = {
id: String(currentPokemonData.id),
jpName: currentPokemonData.jpName,
name: currentPokemonData.name,
image: document.getElementById('modal-image').src,
isShiny: currentPokemonData.isShiny,
rawMoves: currentPokemonData.moves.slice(0, 12)
};
if (!caughtPokemons.some(p => p.id === newItem.id && p.isShiny === newItem.isShiny)) {
caughtPokemons.push(newItem);
localStorage.setItem('my_pokemons', JSON.stringify(caughtPokemons));
updateCollectionCount();
showMessage(`${currentPokemonData.jpName} をつかまえた!`, "check-circle");
document.getElementById('pokemon-modal').classList.add('hidden');
}
} else {
showMessage("にげられてしまった...", "info");
document.getElementById('pokemon-modal').classList.add('hidden');
}
};
document.getElementById('start-battle-btn').onclick = () => {
if (caughtPokemons.length === 0) {
showMessage("まずは ポケモンを つかまえてください!");
return;
}
const list = document.getElementById('my-pokemon-list');
list.innerHTML = '';
caughtPokemons.forEach(p => {
const btn = document.createElement('button');
btn.className = `p-4 rounded-2xl hover:bg-red-50 border-2 transition-all flex flex-col items-center group ${p.isShiny ? 'border-yellow-300 bg-yellow-50' : 'border-transparent bg-slate-50'}`;
btn.onclick = () => startBattle(p);
btn.innerHTML = `
<div class="relative">
<img src="${p.image}" class="w-16 h-16 object-contain" />
${p.isShiny ? '<div class="absolute -top-1 -left-1 text-yellow-500"><i data-lucide="star" class="w-3 h-3 fill-current"></i></div>' : ''}
</div>
<span class="text-xs font-bold mt-2 truncate w-full text-center">${p.jpName}</span>
`;
list.appendChild(btn);
});
lucide.createIcons();
document.getElementById('select-modal').classList.remove('hidden');
};
document.getElementById('filter-shiny').onclick = () => {
const btn = document.getElementById('filter-shiny');
const isActive = btn.classList.contains('bg-yellow-700');
if (!isActive) {
btn.classList.replace('bg-yellow-500', 'bg-yellow-700');
currentDisplayList = allPokemons.filter(p => p.isShiny);
document.getElementById('view-title-text').textContent = "いろちがい そうぐうちゅう";
} else {
btn.classList.replace('bg-yellow-700', 'bg-yellow-500');
currentDisplayList = allPokemons;
document.getElementById('view-title-text').textContent = "ポケモンずかん";
}
itemsToShow = 60;
renderGrid(currentDisplayList.slice(0, itemsToShow));
document.getElementById('btn-back-to-grid').classList.toggle('hidden', !isActive);
};
document.getElementById('filter-mega').onclick = () => {
const btn = document.getElementById('filter-mega');
const isActive = btn.classList.contains('bg-purple-900');
if (!isActive) {
btn.classList.replace('bg-purple-600', 'bg-purple-900');
currentDisplayList = allPokemons.filter(p => p.isMega);
document.getElementById('view-title-text').textContent = "めがしんか ポケモン";
} else {
btn.classList.replace('bg-purple-900', 'bg-purple-600');
currentDisplayList = allPokemons;
document.getElementById('view-title-text').textContent = "ポケモンずかん";
}
itemsToShow = 60;
renderGrid(currentDisplayList.slice(0, itemsToShow));
document.getElementById('btn-back-to-grid').classList.toggle('hidden', !isActive);
};
document.getElementById('view-collection').onclick = () => {
currentDisplayList = caughtPokemons;
itemsToShow = 9999;
renderGrid(currentDisplayList);
document.getElementById('view-title-text').textContent = "これくしょん";
document.getElementById('btn-back-to-grid').classList.remove('hidden');
};
document.getElementById('search-input').oninput = (e) => {
const val = e.target.value.toLowerCase().trim();
currentDisplayList = allPokemons.filter(p =>
(p.jpName && p.jpName.includes(val)) || p.id === val || p.name.toLowerCase().includes(val)
);
itemsToShow = 60;
renderGrid(currentDisplayList.slice(0, itemsToShow));
document.getElementById('no-results').classList.toggle('hidden', currentDisplayList.length > 0);
};
document.getElementById('btn-back-to-grid').onclick = () => {
currentDisplayList = allPokemons;
itemsToShow = 60;
renderGrid(currentDisplayList.slice(0, itemsToShow));
document.getElementById('view-title-text').textContent = "ポケモンずかん";
document.getElementById('btn-back-to-grid').classList.add('hidden');
document.getElementById('filter-mega').classList.replace('bg-purple-900', 'bg-purple-600');
document.getElementById('filter-shiny').classList.replace('bg-yellow-700', 'bg-yellow-500');
};
document.getElementById('close-modal').onclick = () => document.getElementById('pokemon-modal').classList.add('hidden');
document.getElementById('close-select-modal').onclick = () => document.getElementById('select-modal').classList.add('hidden');
function startGame() {
document.getElementById('start-screen').classList.add('hidden');
document.getElementById('game-container').classList.remove('opacity-0');
document.getElementById('game-container').classList.add('opacity-100');
if (!isMusicPlaying) {
bgmPlayer.play().catch(() => {});
isMusicPlaying = true;
}
}
async function renderGrid(list) {
const grid = document.getElementById('pokemon-grid');
grid.innerHTML = '';
list.forEach(p => grid.appendChild(createPokemonCard(p)));
lucide.createIcons();
}
function createPokemonCard(p) {
const isCaught = caughtPokemons.some(cp => cp.id === String(p.id) && cp.isShiny === p.isShiny);
const card = document.createElement('div');
let bgStyles = p.isMega ? 'ring-2 ring-purple-500 bg-purple-50' : 'bg-white';
if (p.isShiny) bgStyles = 'ring-2 ring-yellow-400 bg-yellow-50 shiny-card';
card.className = `pokemon-card ${bgStyles} rounded-3xl p-4 shadow-sm hover:shadow-xl transition-all cursor-pointer border border-slate-200 flex flex-col items-center group relative animate-zoom-in`;
card.onclick = () => showDetail(p.id);
card.innerHTML = `
<div class="absolute top-3 left-4 flex items-center gap-1">
<span class="text-[10px] font-black text-slate-300">#${String(p.id).padStart(3, '0')}</span>
${isCaught ? '<div class="w-2 h-2 bg-red-500 rounded-full border border-white"></div>' : ''}
</div>
<div class="absolute top-2 right-2 flex gap-1">
${p.isShiny ? '<i data-lucide="star" class="w-4 h-4 text-yellow-500 fill-current"></i>' : ''}
${p.isMega ? '<div class="bg-purple-500 text-[7px] text-white px-1.5 py-0.5 rounded-full font-black italic shadow-sm uppercase">めが</div>' : ''}
</div>
<img src="${p.image}" class="w-24 h-24 object-contain mb-2 group-hover:scale-110 transition-transform" />
<h2 id="name-label-${p.id}-${p.isShiny}" class="font-bold text-slate-700 text-xs truncate w-full text-center">
${p.jpName || 'よみこみちゅう...'}
</h2>
`;
if (!p.jpName) {
fetchJapaneseName(p.id).then(name => {
const label = card.querySelector(`#name-label-${p.id}-${p.isShiny}`);
if (label) label.textContent = name;
p.jpName = name;
});
}
return card;
}
function logMessage(msg, colorClass = "text-white") {
const log = document.getElementById('battle-log');
const p = document.createElement('p');
p.className = `mb-1 ${colorClass}`;
p.textContent = msg;
log.appendChild(p);
log.scrollTop = log.scrollHeight;
}
function updateBattleUI() {
if (!battleState) return;
const pPercent = (battleState.player.hp / battleState.player.maxHp) * 100;
const ePercent = (battleState.enemy.hp / battleState.enemy.maxHp) * 100;
document.getElementById('player-hp-bar').style.width = `${pPercent}%`;
document.getElementById('enemy-hp-bar').style.width = `${ePercent}%`;
document.getElementById('player-hp-text').textContent = Math.ceil(battleState.player.hp);
}
window.onload = init;
</script>
</body>
</html>
ポケモンを捕まえたりしたり戦ったりしたりするゲーム