import "/style.css"; import Pokedex from "pokedex-promise-v2"; import annyang from "annyang"; const options = { protocol: "https", hostName: "pokeapi.co", versionPath: "/api/v2/", cacheLimit: 100 * 1000, timeout: 10 * 1000, }; const pokemon = new Pokedex(options); // --- DOM Elements --- const searchButton = document.getElementById("searchButton"); const pokemonNameInput = document.getElementById("pokemonName"); const pokemonInfoDiv = document.getElementById("pokemonInfo"); const voiceSearchButton = document.getElementById("voiceSearchButton"); const historyList = document.getElementById("historyList"); const themeToggleButton = document.getElementById("themeToggleButton"); const themeIcon = document.getElementById("themeIcon"); // --- State --- let searchHistory = []; // Now stores full data objects const MAX_HISTORY_LENGTH = 3; let lastDisplayedPokemon = null; // Track the last displayed Pokémon // --- Functions --- const updateHistoryDisplay = (justAddedName = null) => { historyList.innerHTML = ""; // Clear current list visually if (searchHistory.length === 0) { historyList.innerHTML = '
  • No history yet.
  • '; } else { searchHistory.forEach((pokemonData, index) => { const li = document.createElement("li"); li.classList.add("cursor-pointer"); // Make whole card clickable // Determine scale based on index let scale = 1.0; // Default scale for the newest item if (index === 1) scale = 0.9; else if (index === 2) scale = 0.8; // Apply scale via inline style li.style.transform = `scale(${scale})`; // Build the inner HTML for the history card li.innerHTML = `

    ${ pokemonData.name.charAt(0).toUpperCase() + pokemonData.name.slice(1) }

    ${
        pokemonData.name
      }

    Type(s): ${pokemonData.types .map((t) => t.type.name) .join(", ")}

    Height: ${pokemonData.height / 10} m

    Weight: ${pokemonData.weight / 10} kg

    `; // Add click listener to the card li.addEventListener("click", () => { pokemonNameInput.value = pokemonData.name; // Set input value searchPokemon(); // Trigger search }); // Animation for the newest item if (index === 0 && pokemonData.name === justAddedName) { li.classList.add("history-item-enter"); // Apply initial scale for animation start li.style.transform = `translateX(-50px) scale(${scale * 0.9})`; // Start smaller setTimeout(() => { li.classList.remove("history-item-enter"); // Transition back to the target scale li.style.transform = `scale(${scale})`; }, 10); // Small delay } historyList.appendChild(li); }); } }; const displayPokemonInfo = (data) => { const pokemonName = data.name; // Keep name for comparison pokemonInfoDiv.innerHTML = `

    ${ pokemonName.charAt(0).toUpperCase() + pokemonName.slice(1) }

    ${pokemonName}

    Type(s): ${data.types .map((typeInfo) => typeInfo.type.name) .join(", ")}

    Height: ${data.height / 10} m

    Weight: ${data.weight / 10} kg

    `; // --- Update History --- // Only add the previous Pokémon to history (not the current one) if (lastDisplayedPokemon && lastDisplayedPokemon.name !== data.name) { // Remove if already exists searchHistory = searchHistory.filter( (item) => item.name !== lastDisplayedPokemon.name ); // Add to beginning searchHistory.unshift(lastDisplayedPokemon); // Limit history length if (searchHistory.length > MAX_HISTORY_LENGTH) { searchHistory = searchHistory.slice(0, MAX_HISTORY_LENGTH); } updateHistoryDisplay(lastDisplayedPokemon.name); } else { updateHistoryDisplay(); } // Update lastDisplayedPokemon to current lastDisplayedPokemon = data; }; const displayError = (error) => { if (error && error.response && error.response.status === 404) { pokemonInfoDiv.innerHTML = `

    Sorry, Pokémon not found. Please check the spelling and try again.

    `; } else { pokemonInfoDiv.innerHTML = `

    An error occurred while fetching Pokémon data. Please try again later.

    `; console.error("Error fetching Pokémon:", error); } }; const searchPokemon = () => { const name = pokemonNameInput.value.trim().toLowerCase(); if (!name) { pokemonInfoDiv.innerHTML = `

    Please enter a Pokémon name.

    `; return; } pokemonInfoDiv.innerHTML = `

    Loading...

    `; pokemon.getPokemonByName(name).then(displayPokemonInfo).catch(displayError); }; // Theme toggle logic // Removed references to themeText as the text is no longer part of the DOM const setTheme = (theme) => { if (theme === "dark") { document.documentElement.classList.add("dark"); themeIcon.classList.remove("fa-moon"); themeIcon.classList.add("fa-sun"); } else { document.documentElement.classList.remove("dark"); themeIcon.classList.remove("fa-sun"); themeIcon.classList.add("fa-moon"); } localStorage.setItem("theme", theme); }; // Initialize theme based on device or saved preference const savedTheme = localStorage.getItem("theme"); const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; setTheme(savedTheme || (prefersDark ? "dark" : "light")); // Toggle theme on button click themeToggleButton.addEventListener("click", () => { const currentTheme = document.documentElement.classList.contains("dark") ? "dark" : "light"; setTheme(currentTheme === "dark" ? "light" : "dark"); }); // --- Event Listeners & Voice Search --- searchButton.addEventListener("click", searchPokemon); pokemonNameInput.addEventListener("keypress", (event) => { if (event.key === "Enter") { searchPokemon(); } }); // Show a dimmed overlay with a large microphone icon when the voice search button is clicked voiceSearchButton.addEventListener("click", () => { const voiceOverlay = document.getElementById("voiceOverlay"); voiceOverlay.classList.remove("hidden"); // Simulate listening for 3 seconds, then hide the overlay setTimeout(() => { voiceOverlay.classList.add("hidden"); }, 3000); }); const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; let recognition; if (SpeechRecognition) { recognition = new SpeechRecognition(); recognition.continuous = false; recognition.lang = "en-US"; recognition.interimResults = false; recognition.maxAlternatives = 1; voiceSearchButton.addEventListener("click", () => { const voiceOverlay = document.getElementById("voiceOverlay"); voiceOverlay.classList.remove("hidden"); // Simulate listening for 3 seconds, then hide the overlay setTimeout(() => { voiceOverlay.classList.add("hidden"); }, 3000); try { pokemonNameInput.placeholder = "Listening..."; recognition.start(); voiceSearchButton.classList.add("text-red-500"); } catch (e) { console.error("Speech recognition already started.", e); pokemonNameInput.placeholder = "Enter Pokémon name or use mic"; } }); recognition.onresult = (event) => { const speechResult = event.results[0][0].transcript.toLowerCase(); pokemonNameInput.value = speechResult; searchPokemon(); }; recognition.onspeechend = () => { recognition.stop(); pokemonNameInput.placeholder = "Enter Pokémon name or use mic"; voiceSearchButton.classList.remove("text-red-500"); }; recognition.onerror = (event) => { pokemonInfoDiv.innerHTML = `

    Voice recognition error: ${event.error}

    `; console.error("Speech recognition error:", event); pokemonNameInput.placeholder = "Enter Pokémon name or use mic"; voiceSearchButton.classList.remove("text-red-500"); }; recognition.onnomatch = (event) => { pokemonInfoDiv.innerHTML = `

    Didn't recognize that Pokémon name.

    `; pokemonNameInput.placeholder = "Enter Pokémon name or use mic"; voiceSearchButton.classList.remove("text-red-500"); }; } else if (annyang) { console.warn("Using annyang as a fallback for voice recognition."); annyang.addCommands({ "*pokemonName": (pokemonName) => { pokemonNameInput.value = pokemonName.toLowerCase(); searchPokemon(); }, }); voiceSearchButton.addEventListener("click", () => { const voiceOverlay = document.getElementById("voiceOverlay"); voiceOverlay.classList.remove("hidden"); // Simulate listening for 3 seconds, then hide the overlay setTimeout(() => { voiceOverlay.classList.add("hidden"); }, 3000); try { pokemonNameInput.placeholder = "Listening..."; annyang.start(); voiceSearchButton.classList.add("text-red-500"); } catch (e) { console.error("Annyang already started.", e); pokemonNameInput.placeholder = "Enter Pokémon name or use mic"; } }); annyang.addCallback("end", () => { pokemonNameInput.placeholder = "Enter Pokémon name or use mic"; voiceSearchButton.classList.remove("text-red-500"); }); annyang.addCallback("error", (error) => { pokemonInfoDiv.innerHTML = `

    Voice recognition error: ${error}

    `; console.error("Annyang error:", error); pokemonNameInput.placeholder = "Enter Pokémon name or use mic"; voiceSearchButton.classList.remove("text-red-500"); }); } else { console.warn("Web Speech API and annyang not supported in this browser."); voiceSearchButton.style.display = "none"; } // --- Initial Setup --- updateHistoryDisplay(); // Initialize history display on page load