import { RARITY_ORDER } from "./card-values.js";
import { add_or_update_card, load_cards_from_local } from "../index.js";
const CRAFT_COST = 5;
let currentSort = "default";
/**
* @description Creates a new frog-card element and appends it inside the <card-display> element.
* Also sets up the crafting UI for the displayed card.
* @param {Object} data - The card data object containing name, rarity, course, bio, and quantity properties.
*/
export function createCard(data) {
const card_display = document.querySelector("card-display");
if (!card_display){
return;
}
card_display.innerHTML = ""; // Clear previous card
const card = document.createElement("frog-card");
card.data = data;
card_display.appendChild(card);
setupCraftingUI(data);
}
/**
* @description Updates the card grid display by loading cards from local storage and creating
* card-thumbnail elements for each card. Sets up click event listeners for each card to open
* the card modal when clicked.
* @param {Array} cardData - An array of card data objects to display in the grid.
* @returns {void}
*/
export function updateCardGrid(cards) {
const container = document.getElementById("card-grid");
if (!container) return;
// Get the no-cards-message element
let noCardsMessage = document.getElementById("no-cards-message");
if (!noCardsMessage) {
noCardsMessage = document.createElement("div");
noCardsMessage.id = "no-cards-message";
noCardsMessage.className = "no-cards-message hidden";
noCardsMessage.innerHTML = `
<h2>No Cards Found</h2>
<p>Start collecting cards in the shop or gain points using the clicker!</p>
<div class="button-group">
<a href="shop.html" class="shop-button">Go to Shop</a>
<a href="clicker.html" class="clicker-button">Open Clicker</a>
</div>
`;
container.appendChild(noCardsMessage);
}
// Update total cards count
const totalCardsElement = document.getElementById("total-cards");
if (totalCardsElement) {
const totalCards = cards ? cards.reduce((sum, card) => sum + (card.quantity || 1), 0) : 0;
totalCardsElement.textContent = `Total Filtered Cards: ${totalCards}`;
}
// Clear existing cards
container.innerHTML = "";
container.appendChild(noCardsMessage);
if (!cards || cards.length === 0) {
noCardsMessage.classList.remove("hidden");
return;
}
noCardsMessage.classList.add("hidden");
cards.forEach((data, index) => {
const card = document.createElement("card-thumbnail");
card.data = data;
card.style.animationDelay = `${index * 0.1}s`;
// Listen for the custom card-clicked event
card.addEventListener("card-clicked", (e) => {
const selectedData = e.detail;
// Set the card title above the modal card
const titleEl = document.getElementById("card-title");
titleEl.textContent = `${selectedData.rarity} "${selectedData.name}" | quantity: ${selectedData.quantity}`;
createCard(selectedData);
const modal = document.getElementById("card-modal");
if (modal) {
modal.classList.remove("hidden");
}
});
container.appendChild(card);
});
}
/**
* @description Sets up the crafting UI for a given card, including the slider, summary text,
* and craft button. Handles the logic for determining if crafting is possible based on rarity
* and available quantity. Sets up event listeners for the slider and craft button.
* @param {Object} data - The card data object containing name, rarity, course, bio, and quantity properties.
*/
export function setupCraftingUI(data) {
const craftingUI = document.getElementById("crafting-ui");
const slider = document.getElementById("craft-slider");
const summary = document.getElementById("craft-summary");
const craftBtn = document.getElementById("craft-button");
if (!craftingUI || !slider || !summary || !craftBtn) {
console.error("Required crafting UI elements not found");
throw new Error("Elements not found");
}
// Hide crafting UI for legendary and special-edition cards
if (data.rarity === "legendary" || data.rarity === "special-edition") {
craftingUI.classList.add("hidden");
return;
}
const rarityIndex = RARITY_ORDER.indexOf(data.rarity);
const nextRarity = RARITY_ORDER[rarityIndex + 1];
const quantity = data.quantity;
const maxCraftable = Math.floor(quantity / CRAFT_COST);
craftingUI.classList.remove("hidden");
if (maxCraftable < 1) {
craftBtn.disabled = true;
slider.disabled = true;
summary.textContent = "Not enough cards to merge";
slider.value = 0;
slider.max = 0;
} else {
slider.disabled = false;
slider.max = maxCraftable;
slider.min = 0;
slider.value = 0; // Start at 0
const updateSummary = () => {
if (slider.value == 0) {
summary.textContent = "Slide to increase the number of cards to craft";
craftBtn.disabled = true;
} else {
summary.textContent = `Use ${slider.value * CRAFT_COST} ${data.rarity} "${data.name}" cards to craft ${slider.value} ${nextRarity} "${data.name}" card(s)`;
craftBtn.disabled = false;
}
};
slider.oninput = updateSummary;
updateSummary(); // Initialize the summary
}
craftBtn.onclick = () => {
if (craftBtn.disabled) return;
const craftAmount = parseInt(slider.value);
const new_data = {
name: data.name,
course: data.course,
bio: data.bio,
quantity: 1,
rarity: nextRarity
};
// Update the cards
add_or_update_card(new_data, craftAmount);
add_or_update_card(data, -craftAmount * CRAFT_COST);
// Update the grid to reflect changes
const searchInput = document.getElementById("search-input");
const filteredData = renderCards(searchInput.value);
updateCardGrid(filteredData);
//creates the popup message at the end of crafting
const popup = document.getElementById("craft-popup");
const amountSpan = document.getElementById("crafted-amount");
if (popup && amountSpan) {
amountSpan.textContent = `${craftAmount} ${nextRarity} "${data.name}"`;
popup.classList.remove("hidden");
popup.classList.add("show");
//remove the popup after 3 seconds
setTimeout(() => {
popup.classList.remove("show");
setTimeout(() => popup.classList.add("hidden"), 500);
}, 3000);
}
// Close the modal
const modal = document.getElementById("card-modal");
if (modal) {
modal.classList.add("hidden");
}
// Show success message (optional)
console.log(`Crafted ${craftAmount} ${nextRarity} "${data.name}"`);
//alert(`🎉 Congratulations! You crafted ${craftAmount} ${nextRarity} "${data.name}" card(s)!`);
};
}
/**
*
* @param {string} filter - The search filter string to apply to the card names.
* @returns {Array} An array of card objects that match the filter criteria.
* @description Renders the card grid based on the provided filter. It retrieves card data from local storage,
*/
function renderCards(filter = "") {
const localData = localStorage.getItem("card_data");
if (!localData) return;
const cards = JSON.parse(localData);
const container = document.getElementById("card-grid");
container.innerHTML = "";
//filter cards based on search
let filteredCards = cards.filter(data => {
const name = data.name?.toLowerCase() || "";
return !filter || name.includes(filter.toLowerCase());
});
//sort according to selected option
if (currentSort === "first-name-az") {
filteredCards.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
}
else if (currentSort === "first-name-za") {
filteredCards.sort((a, b) => (b.name || "").localeCompare(a.name || ""));
}
else if (currentSort === "rarity-asc") {
filteredCards.sort((a, b) => {
return RARITY_ORDER.indexOf(a.rarity?.toLowerCase()) - RARITY_ORDER.indexOf(b.rarity?.toLowerCase());
});
}
else if (currentSort === "rarity-desc") {
filteredCards.sort((a, b) => {
return RARITY_ORDER.indexOf(b.rarity?.toLowerCase()) - RARITY_ORDER.indexOf(a.rarity?.toLowerCase());
});
}
else if (currentSort === "last-name-az") {
filteredCards.sort((a, b) => {
const lastA = (a.name || "").trim().split(" ").slice(-1)[0].toLowerCase();
const lastB = (b.name || "").trim().split(" ").slice(-1)[0].toLowerCase();
return lastA.localeCompare(lastB);
});
}
else if (currentSort === "last-name-za") {
filteredCards.sort((a, b) => {
const lastA = (a.name || "").trim().split(" ").slice(-1)[0].toLowerCase();
const lastB = (b.name || "").trim().split(" ").slice(-1)[0].toLowerCase();
return lastB.localeCompare(lastA);
});
}
else if (currentSort === "quantity-asc") {
filteredCards.sort((a, b) => a.quantity - b.quantity);
}
else if (currentSort === "quantity-desc") {
filteredCards.sort((a, b) => b.quantity - a.quantity);
}
return filteredCards
}
/**
* @description Initializes the application when the DOM is fully loaded. Sets up the card grid,
* modal close functionality, and keyboard event listeners for closing the modal with the Escape key.
*/
window.addEventListener("DOMContentLoaded", () => {
init()
});
export function init() {
updateCardGrid(load_cards_from_local());
// Modal close logic
const searchInput = document.getElementById("search-input");
const sortSelect = document.getElementById("sort-select");
const closeBtn = document.getElementById("modal-close");
const modal = document.getElementById("card-modal");
if (closeBtn && modal) {
closeBtn.addEventListener("click", () => modal.classList.add("hidden"));
}
window.addEventListener("keydown", (e) => {
if (e.key === "Escape" && modal) {
modal.classList.add("hidden");
}
});
//search bar functionality
searchInput.addEventListener("input", () => {
const data = renderCards(searchInput.value);
updateCardGrid(data);
});
//sorting functionality
sortSelect.addEventListener("change", () => {
currentSort = sortSelect.value;
const data = renderCards(searchInput.value);
updateCardGrid(data);
});
}