🔬 AI発明ギャラリー kizu ポケモン図鑑
🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
HTML / CSS / JS
<!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>
✨ ピックアップ

ポケモン図鑑

by kizu

ポケモンを捕まえたりしたり戦ったりしたりするゲーム

🤖 使用したAI
Gemini
👁 39 回閲覧
📅 投稿:2026年5月16日 🔄 更新:2026年8月13日
応援スタンプを押してみよう!