Source: assets/scripts/overlays.js

// overlays.js

/**
 * @fileoverview Manages all overlay-related UI behavior in the Pokédex web app.
 *
 * Includes logic for the loading overlay and the Pokémon detail overlay (dialog),
 * including rendering, toggling, navigation, and scroll locking.
 */

let isShiny = false;
let secondTypeName;

/**
 * Opens the Pokémon detail overlay.
 *
 * Renders the detail card for the given Pokémon ID,
 * makes the overlay visible, and disables body scrolling.
 *
 * @param {number} id - The ID of the Pokémon to display in the overlay.
 * @returns {Promise<void>}
 */
async function openOverlay(id) {
  await renderDialogCard(id);
  document.getElementById("overlay").classList.remove("d-none");
  disableScrollingBody();
}

/**
 * Closes the Pokémon detail overlay and enables body scrolling again.
 */
function closeOverlay() {
  document.getElementById("overlay").classList.add("d-none");
  enableScrollingBody();
}

/**
 * Renders the detailed dialog card for the selected Pokémon.
 *
 * Fetches the Pokémon data by ID, prepares the HTML structure,
 * resets the shiny toggle, and renders type icons and stats.
 *
 * @param {number} id - The ID of the selected Pokémon.
 * @returns {Promise<void>}
 */
async function renderDialogCard(id) {
  let container = document.getElementById("dialog-container");
  currentPokemon = await getPokemonData(id);
  const backgroundColor = typeColors[currentPokemon.types[0].type.name] || "#66aed7";
  isShiny = false;
  container.innerHTML = renderDialogCardTemplate(currentPokemon, backgroundColor);
  renderTypes("dialog-types-container");
  renderStats();
}

/**
 * Renders the "About" tab of the dialog card.
 *
 * Inserts the general information and abilities of the current Pokémon
 * into the detail info container.
 */
function renderAbout() {
  let container = getElementHelper("detail-info-container");
  container.innerHTML = renderAboutTemplate(currentPokemon);
  renderAboutAbilities(currentPokemon["abilities"]);
}

/**
 * Renders the abilities of the current Pokémon in the "About" tab.
 *
 * Iterates through the abilities array and appends each ability to the abilities container.
 *
 * @param {Array} abilities - The array of ability objects to render.
 */
function renderAboutAbilities(abilities) {
  let container = getElementHelper("abilities-container");

  for (let i = 0; i < abilities.length; i++) {
    const abilityName = abilities[i].ability.name;
    container.innerHTML += renderAboutAbilitiesTemplate(abilityName);
  }
}

/**
 * Renders the "Stats" tab of the dialog card.
 *
 * Clears the detail info container and inserts the stats template.
 * Also sets the second type color used for the stat bars.
 */
function renderStats() {
  let container = getElementHelper("detail-info-container");
  secondTypeColor = typeColors[getSecondTypeName()];
  container.innerHTML = "";
  container.innerHTML += renderStatsTemplate();
}

/**
 * Renders the "Moves" tab of the dialog card.
 *
 * Clears the detail info container and iterates through all available moves
 * of the current Pokémon, rendering each one individually.
 */
function renderMoves() {
  let container = getElementHelper("detail-info-container");
  container.innerHTML = ``;

  for (let i = 0; i < currentPokemon.moves.length; i++) {
    container.innerHTML += renderMovesTemplate(i);
  }
}

/**
 * Sets the active state for the selected detail tab button.
 *
 * Removes the "active" class from all navigation buttons
 * and applies it to the clicked button.
 *
 * @param {string} buttonId - The ID of the button to activate.
 */
function setActiveDetailsTab(buttonId) {
  document.querySelectorAll(".nav-details-btn").forEach((btn) => {
    btn.classList.remove("active");
  });

  getElementHelper(buttonId).classList.add("active");
}

/**
 * Switches to the next or previous Pokémon in the Pokédex.
 *
 * Calculates the new ID based on the given modifier and uses wrapping logic
 * to cycle from 151 to 1 and from 1 to 151 for seamless navigation.
 *
 * @param {number} modifier - The offset to apply to the current Pokémon ID (e.g. -1 or +1).
 */
function switchPokemon(modifier) {
  let newPokemonId = currentPokemon.id + modifier;
  newPokemonId = getWrappedId(newPokemonId);
  renderDialogCard(newPokemonId);
}

/**
 * Wraps the Pokémon ID within the valid Pokédex range (1–151).
 *
 * Used to ensure that navigation beyond the first or last Pokémon
 * correctly loops to the other end of the Pokédex.
 *
 * @param {number} id - The raw Pokémon ID (can be out of bounds).
 * @returns {number} The wrapped ID within the valid range.
 */
function getWrappedId(id) {
  if (id < 1) return 151;
  if (id > 151) return 1;
  return id;
}

/**
 * Toggles between the default and shiny version of the Pokémon GIF.
 *
 * Updates the image source based on the current shiny state,
 * adds a temporary glow animation, and flips the `isShiny` flag.
 */
function toggleShinyGif() {
  let container = getElementHelper("dialog-gif-container");
  checkIfShiny(container);
  isShiny = !isShiny;
  container.classList.add("glow");
  setTimeout(() => container.classList.remove("glow"), 500);
}

/**
 * Updates the Pokémon GIF source based on the current shiny state.
 *
 * Sets the image to either the shiny or default animated sprite,
 * depending on the value of `isShiny`.
 *
 * @param {HTMLImageElement} container - The image element whose source should be updated.
 */
function checkIfShiny(container) {
  if (!isShiny) {
    setTimeout(
      () => (container.src = `${currentPokemon["sprites"]["versions"]["generation-v"]["black-white"]["animated"]["front_shiny"]}`),
      100
    );
  } else {
    setTimeout(
      () => (container.src = `${currentPokemon["sprites"]["versions"]["generation-v"]["black-white"]["animated"]["front_default"]}`),
      100
    );
  }
}

/**
 * Returns the name of the adjacent Pokémon in the Pokédex.
 *
 * Calculates the next or previous Pokémon ID using a modifier,
 * applies wrapping logic at the edges (1 ↔ 151),
 * and returns the capitalized name from the search pool.
 *
 * @param {number} modifier - Offset to apply to the current Pokémon ID (e.g. -1 or +1).
 * @returns {string} The capitalized name of the adjacent Pokémon.
 */
function getNameOfAdjacentPokemon(modifier) {
  let indexAdjacentPokemon = currentPokemon.id + modifier;
  indexAdjacentPokemon = getWrappedId(indexAdjacentPokemon);
  let nameRef = capitalizeFirstLetter(searchPool[indexAdjacentPokemon].name);
  return nameRef;
}

/**
 * Returns the name of the second Pokémon type, if available.
 *
 * If the Pokémon has only one type, returns "unknown" as a fallback.
 *
 * @returns {string} The name of the second type or "unknown" if not present.
 */
function getSecondTypeName() {
  try {
    secondTypeName = currentPokemon.types[1].type.name;
  } catch (error) {
    secondTypeName = "unknown";
  }

  return secondTypeName;
}