From 636b43a2614bb810b0952000b79953fa2f06e2be Mon Sep 17 00:00:00 2001 From: Notoric Date: Tue, 19 Mar 2024 02:39:47 +0000 Subject: [PATCH] Added a feature to display a dynamic list of pokemon that you can filter by type and generation TODO: save current filters for page load, add popup page with info, make clickable cards, potential favourites system? --- css/styles.css | 377 ++++++++++++++++++++++++++++++++++++++++++++++++- index.html | 7 + js/scripts.js | 230 +++++++++++++++++++++++++++++- 3 files changed, 610 insertions(+), 4 deletions(-) diff --git a/css/styles.css b/css/styles.css index 918573c..9e0aef7 100644 --- a/css/styles.css +++ b/css/styles.css @@ -7,6 +7,24 @@ :root { --bgColor: #ffffff; + --normal: #999999; + --fighting: #C03028; + --flying: #A890F0; + --poison: #A040A0; + --ground: #E0C068; + --rock: #B8A038; + --bug: #A8B820; + --ghost: #705898; + --steel: #B8B8D0; + --fire: #F08030; + --water: #6890F0; + --grass: #78C850; + --electric: #F8D030; + --psychic: #F85888; + --ice: #98D8D8; + --dragon: #7038F8; + --dark: #41314e; + --fairy: #EE99AC; } @@ -82,7 +100,7 @@ h1 { } .black { - background-color: #000; + background-color: #000; /* Reference[0] https://www.w3schools.com/howto/howto_css_cutout_text.asp */ color: #fff; mix-blend-mode: multiply; } @@ -173,6 +191,357 @@ h3 { border-bottom: none; } +/* POKEDEX STYLES */ + +#pokedex { + max-width: 1300px; + margin: auto; + margin-bottom: 50px; +} + +#generation-selector, #type-selector { + overflow-x: auto; +} + +#pokedex h2 { + text-align: center; +} + +#generation-selector { + display: flex; +} + +#generation-selector input { + display: none; +} + +#generation-selector label { + display: inline-block; + padding: 10px; + margin: 10px; + min-height: 40px; + min-width: 40px; + text-align: center; + text-shadow: black 0px 0px 3px; + line-height: 40px; + border-radius: 30px; + background-color: #202020; + color: white; + cursor: pointer; + transition: background-color 0.5s; +} + +#generation-selector label:hover { + background-color: #303030; +} + +#generation-selector input:checked + label { + background-color: var(--bgColor); +} + +#type-selector { + display: flex; +} + +#type-selector input { + display: none; +} + +#type-selector label { + display: inline-block; + padding: 10px; + margin: 10px; + height: 40px; + width: 40px; + text-align: center; + line-height: 40px; + border-radius: 30px; + background-color: #202020; + color: white; + cursor: pointer; + transition: background-color 0.5s; +} + +#type-selector label img { + width: 40px; + height: 40px; + font-size: 10px; +} + +#type-selector label:hover { + background-color: #303030; +} + +#type-selector input:checked + label { + background-color: var(--bgColor); +} + +#normal:checked + label { + background-color: var(--normal) !important; +} + +#fighting:checked + label { + background-color: var(--fighting) !important; +} + +#flying:checked + label { + background-color: var(--flying) !important; +} + +#poison:checked + label { + background-color: var(--poison) !important; +} + +#ground:checked + label { + background-color: var(--ground) !important; +} + +#rock:checked + label { + background-color: var(--rock) !important; +} + +#bug:checked + label { + background-color: var(--bug) !important; +} + +#ghost:checked + label { + background-color: var(--ghost) !important; +} + +#steel:checked + label { + background-color: var(--steel) !important; +} + +#fire:checked + label { + background-color: var(--fire) !important; +} + +#water:checked + label { + background-color: var(--water) !important; +} + +#grass:checked + label { + background-color: var(--grass) !important; +} + +#electric:checked + label { + background-color: var(--electric) !important; +} + +#psychic:checked + label { + background-color: var(--psychic) !important; +} + +#ice:checked + label { + background-color: var(--ice) !important; +} + +#dragon:checked + label { + background-color: var(--dragon) !important; +} + +#dark:checked + label { + background-color: var(--dark) !important; +} + +#fairy:checked + label { + background-color: var(--fairy) !important; +} + +#pokemon-container .normal-primary-type { + background-color: var(--normal) !important; +} + +#pokemon-container .normal-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--normal) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .fighting-primary-type { + background-color: var(--fighting) !important; +} + +#pokemon-container .fighting-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--fighting) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .flying-primary-type { + background-color: var(--flying) !important; +} + +#pokemon-container .flying-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--flying) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .poison-primary-type { + background-color: var(--poison) !important; +} + +#pokemon-container .poison-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--poison) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .ground-primary-type { + background-color: var(--ground) !important; +} + +#pokemon-container .ground-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--ground) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .rock-primary-type { + background-color: var(--rock) !important; +} + +#pokemon-container .rock-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--rock) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .bug-primary-type { + background-color: var(--bug) !important; +} + +#pokemon-container .bug-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--bug) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .ghost-primary-type { + background-color: var(--ghost) !important; +} + +#pokemon-container .ghost-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--ghost) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .steel-primary-type { + background-color: var(--steel) !important; +} + +#pokemon-container .steel-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--steel) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .fire-primary-type { + background-color: var(--fire) !important; +} + +#pokemon-container .fire-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--fire) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .water-primary-type { + background-color: var(--water) !important; +} + +#pokemon-container .water-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--water) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .grass-primary-type { + background-color: var(--grass) !important; +} + +#pokemon-container .grass-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--grass) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .electric-primary-type { + background-color: var(--electric) !important; +} + +#pokemon-container .electric-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--electric) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .psychic-primary-type { + background-color: var(--psychic) !important; +} + +#pokemon-container .psychic-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--psychic) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .ice-primary-type { + background-color: var(--ice) !important; +} + +#pokemon-container .ice-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--ice) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .dragon-primary-type { + background-color: var(--dragon) !important; +} + +#pokemon-container .dragon-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--dragon) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .dark-primary-type { + background-color: var(--dark) !important; +} + +#pokemon-container .dark-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--dark) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container .fairy-primary-type { + background-color: var(--fairy) !important; +} + +#pokemon-container .fairy-secondary-type { + box-shadow: inset 0px -150px 100px -100px var(--fairy) , 0px 0px 0px 0px transparent !important; +} + +#pokemon-container { + display: flex; + flex-direction: row; + overflow-x: auto; + overflow-y: hidden; + width: 100%; + height: 220px; +} + +.pokemon-card { + height: 200px; + min-width: 300px; + background-color: #444; + margin: 10px; + border-radius: 40px; + overflow: hidden; +} + +.pokemon-card img { + position: relative; + top: -120px; + left: 100px; + height: 192px; + width: 192px; + margin: 10px; + image-rendering: pixelated; +} + +.pokemon-card .pokemon-name { + font-weight: 500; + font-size: 1.15em; + position: relative; + margin: 0; + top: 15px; + left: 15px; + z-index: 1; + text-shadow: black 1px 1px 3px; +} + +.pokemon-card .pokemon-id { + font-weight: 100; + font-size: 3.5em; + position: relative; + margin: 0; + top: 15px; + left: 15px; + z-index: 0; + text-shadow: #0008 1px 1px 2px; +} + /* CAROUSEL STYLES */ #carousel { @@ -344,4 +713,10 @@ h3 { grid-row: 3 / 3; } + /* POKEDEX STYLES */ + + #generation-selector { + justify-content: center; + } + } diff --git a/index.html b/index.html index a86b839..ff46785 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,13 @@
+
+

Pokedex

+
+
+
+
+

Features

diff --git a/js/scripts.js b/js/scripts.js index 41b87f1..e7ef355 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -1,4 +1,5 @@ -console.log("hello"); +let controller = new AbortController(); // Reference[1] https://levelup.gitconnected.com/asynchronous-tasks-got-you-down-heres-how-to-cancel-them-480801e69ae5 +let signal = controller.signal; // COLOUR PICKER BACKGROUND @@ -175,6 +176,227 @@ function transitionLake() { transitionBackground("assets/photos/hintersee-3601004.jpg", "white"); } +// POKEDEX + +async function initPokedex() { + await populateGenerations(); + await populateTypes(); + populatePokedex(signal); +} + +async function populateGenerations() { + const container = document.getElementById("generation-selector"); + const url = "https://pokeapi.co/api/v2/generation/"; + const response = await fetch(url); + const data = await response.json(); + + let generations = []; + let generationsURL = []; + + const genArray = data.results; + for (let i = 0; i < genArray.length; i++) { + const gen = genArray[i]; + const genName = gen.name; + const genId = genName.split("-")[1].toUpperCase(); + generations.push(genId); + generationsURL.push(gen.url); + } + + for (let i = 0; i < generations.length; i++) { + const gen = generations[i]; + const genButton = document.createElement("input"); + genButton.type = "checkbox"; + genButton.value = generationsURL[i]; + genButton.id = `gen${gen}`; + genButton.checked = true; + container.appendChild(genButton); + const genLabel = document.createElement("label"); + genLabel.htmlFor = `gen${gen}`; + genLabel.innerHTML = `${gen}`; + container.appendChild(genLabel); + } +} + +function getSelectedGenerations() { + const container = document.getElementById("generation-selector"); + const checkboxes = container.getElementsByTagName("input"); + let selected = []; + + for (let i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].checked) { + selected.push(checkboxes[i].value); + } + } + + return(selected); +} + +async function populateTypes() { + const container = document.getElementById("type-selector"); + const url = "https://pokeapi.co/api/v2/type"; + const response = await fetch(url); + const data = await response.json(); + + let types = []; + + const typeArray = data.results; + for (let i = 0; i < typeArray.length; i++) { + const type = typeArray[i]; + const typeResponse = await fetch(type.url); + const typeData = await typeResponse.json(); + if (typeData.id < 1000) { + types.push(type.name); + } + } + + for (let i = 0; i < types.length; i++) { + const type = types[i]; + const typeButton = document.createElement("input"); + typeButton.type = "checkbox"; + typeButton.value = type; + typeButton.id = type; + typeButton.checked = true; + container.appendChild(typeButton); + + const typeImg = document.createElement("img"); + typeImg.src = `https://raw.githubusercontent.com/duiker101/pokemon-type-svg-icons/5781623f147f1bf850f426cfe1874ba56a9b75ee/icons/${type}.svg`; + typeImg.alt = `${type}`; + + const typeLabel = document.createElement("label"); + typeLabel.htmlFor = type; + typeLabel.appendChild(typeImg); + container.appendChild(typeLabel); + } +} + +function getSelectedTypes() { + const container = document.getElementById("type-selector"); + const checkboxes = container.getElementsByTagName("input"); + let selected = []; + + for (let i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].checked) { + selected.push(checkboxes[i].value); + } + } + + return selected; +} + +async function populatePokedex(signal) { + const container = document.getElementById("pokemon-container"); + container.innerHTML = ""; + const types = await getSelectedTypes(); + const generations = await getSelectedGenerations(); + for (let i = 0; i < generations.length; i++) { + if (signal.aborted) { + return; + } + const url = generations[i]; + const response = await fetch(url); + const data = await response.json(); + const pokemon = data.pokemon_species; + for (let j = 0; j < pokemon.length; j++) { + if (signal.aborted) { + return; + } + const poke = pokemon[j]; + const speciesURL = poke.url; + const speciesResponse = await fetch(speciesURL); + const speciesData = await speciesResponse.json(); + const varieties = speciesData.varieties; + for (let k = 0; k < varieties.length; k++) { + if (signal.aborted) { + return; + } + const variety = varieties[k]; + const varietyURL = variety.pokemon.url; + const varietyResponse = await fetch(varietyURL); + const varietyData = await varietyResponse.json(); + const varietyTypes = varietyData.types; + if (types.includes(varietyTypes[0].type.name)) { + if (signal.aborted) { + return; + } + addPokemon(varietyData); + } else { + try { + if (types.includes(varietyTypes[1].type.name)) { + if (signal.aborted) { + return; + } + addPokemon(varietyData); + } + } catch { + //no second type + } + } + } + } + } +} + +function addPokemon(pokemonData) { + const container = document.getElementById("pokemon-container"); + + // if (container.childElementCount > 100) { + // return; + // } + + const pokemon = document.createElement("div"); + pokemon.className = "pokemon-card"; + pokemon.classList.add(pokemonData.types[0].type.name + "-primary-type"); + if (pokemonData.types[1]) { + pokemon.classList.add(pokemonData.types[1].type.name + "-secondary-type"); + } + pokemon.id = pokemonData.name; + + const pokemonName = document.createElement("p"); + pokemonName.className = "pokemon-name"; + let formatedName = ""; + pokemonData.name.split("-").forEach((word) => { + formatedName += word.charAt(0).toUpperCase() + word.slice(1) + " "; + }); + if (formatedName.length > 20) { + formatedName = formatedName.slice(0, formatedName.length - 1); + formatedName = formatedName.slice(0, formatedName.lastIndexOf(" ")); + } + pokemonName.innerHTML = formatedName; + pokemon.appendChild(pokemonName); + + const pokemonId = document.createElement("p"); + pokemonId.className = "pokemon-id"; + pokemonId.innerHTML = `#${pokemonData.id}`; + pokemon.style.order = pokemonData.id; + pokemon.appendChild(pokemonId); + + const pokemonImg = document.createElement("img"); + pokemonImg.src = pokemonData.sprites.front_default; + pokemonImg.alt = pokemonData.name; + pokemon.appendChild(pokemonImg); + + container.appendChild(pokemon); +} + +const generationSelector = document.getElementById("generation-selector"); +generationSelector.addEventListener("change", (event) => { + restartPokedexUpdate(); +}); + +const typeSelector = document.getElementById("type-selector"); +typeSelector.addEventListener("change", (event) => { + restartPokedexUpdate(); +}); + +function restartPokedexUpdate() { + controller.abort(); + setTimeout(() => { + controller = new AbortController(); + signal = controller.signal; + populatePokedex(signal); + }, 250); +} + // ACCORDIAN IMAGES function carouselExpand(i) { @@ -206,7 +428,7 @@ function initCarousel() { // INITIALIZATION // Set background colour to last saved value const bgColor = document.getElementById("bgColour"); -bgColor.value = localStorage.getItem("bgColour") || "#ffffff"; +bgColor.value = localStorage.getItem("bgColour") || "#ff0070"; const initR = parseInt(bgColour.value.substring(1, 3), 16); const initG = parseInt(bgColour.value.substring(3, 5), 16); const initB = parseInt(bgColour.value.substring(5, 7), 16); @@ -214,4 +436,6 @@ changeBackground(initR, initG, initB); document.documentElement.style.setProperty("--bgColor", bgColour.value); // Initialize carousel initCarousel(); -carouselExpand(1); \ No newline at end of file +carouselExpand(1); +// Initialize pokedex +initPokedex(); \ No newline at end of file