diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..febb024da --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": ["main"] +} diff --git a/README.md b/README.md index 0c237c1c3..f1c4e1b5a 100644 --- a/README.md +++ b/README.md @@ -1 +1,30 @@ -# Trilha JS Developer - Pokedex +# Pokédex 🕹️ + +Projeto de desenvolvimento de uma Pokédex proposto pelo Bootcamp da [Digital Innovation One - DIO](https://web.dio.me/home) sobre Desenvolvimento Frontend com Angular.
+ +Trata-se de uma aplicação que utiliza a [PokeApi](https://pokeapi.co/) para apresentar detalhes de Pokémons da Primeira Geração. + +## Tecnologias Utilizadas 🚀 + +[![Utilizadas](https://skillicons.dev/icons?i=js,html,css&theme=dark)](https://skillicons.dev) + +## Prints to Website 🖥️ + +
+ +### Página Inicial (Tela com Zoom-out de 70%) 🌐 + + + +### Tela Mobile 📱 + + + +### Detalhes do Pokémon 🔎 + + +
+ +## Conclusão 📝 + +

O projeto Pokédex foi um excelente desafio que eu me diverti bastante fazendo! Além disso, dei o meu melhor para incluir todas as ideias que vinham na minha cabeça durante a produção e tenho certeza que aprendi bastante desenvolvendo.

diff --git a/assets/css/global.css b/assets/css/global.css deleted file mode 100644 index 980e87861..000000000 --- a/assets/css/global.css +++ /dev/null @@ -1,24 +0,0 @@ -* { - font-family: 'Roboto', sans-serif; - box-sizing: border-box; -} - -body { - background-color: #f6f8fc; -} - -.content { - width: 100vw; - height: 100vh; - padding: 1rem; - background-color: #fff; -} - -@media screen and (min-width: 992px) { - .content { - max-width: 992px; - height: auto; - margin: 1rem auto; - border-radius: 1rem; - } -} \ No newline at end of file diff --git a/assets/css/pokedex.css b/assets/css/pokedex.css deleted file mode 100644 index 59eef2bde..000000000 --- a/assets/css/pokedex.css +++ /dev/null @@ -1,165 +0,0 @@ -.pokemons { - display: grid; - grid-template-columns: 1fr; - margin: 0; - padding: 0; - list-style: none; -} - -.normal { - background-color: #a6a877; -} - -.grass { - background-color: #77c850; -} - -.fire { - background-color: #ee7f30; -} - -.water { - background-color: #678fee; -} - -.electric { - background-color: #f7cf2e; -} - -.ice { - background-color: #98d5d7; -} - -.ground { - background-color: #dfbf69; -} - -.flying { - background-color: #a98ff0; -} - -.poison { - background-color: #a040a0; -} - -.fighting { - background-color: #bf3029; -} - -.psychic { - background-color: #f65687; -} - -.dark { - background-color: #725847; -} - -.rock { - background-color: #b8a137; -} - -.bug { - background-color: #a8b720; -} - -.ghost { - background-color: #6e5896; -} - -.steel { - background-color: #b9b7cf; -} - -.dragon { - background-color: #6f38f6; -} - -.fairy { - background-color: #f9aec7; -} - -.pokemon { - display: flex; - flex-direction: column; - margin: .5rem; - padding: 1rem; - border-radius: 1rem; -} - -.pokemon .number { - color: #000; - opacity: .3; - text-align: right; - font-size: .625rem; -} - -.pokemon .name { - text-transform: capitalize; - color: #fff; - margin-bottom: .25rem; -} - -.pokemon .detail { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; -} - -.pokemon .detail .types { - padding: 0; - margin: 0; - list-style: none; -} - -.pokemon .detail .types .type { - color: #fff; - padding: .25rem .5rem; - margin: .25rem 0; - font-size: .625rem; - border-radius: 1rem; - filter: brightness(1.1); - text-align: center; -} - -.pokemon .detail img { - max-width: 100%; - height: 70px; -} - -.pagination { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - width: 100%; - padding: 1rem; -} - -.pagination button { - padding: .25rem .5rem; - margin: .25rem 0; - font-size: .625rem; - color: #fff; - background-color: #6c79db; - border: none; - border-radius: 1rem; -} - -@media screen and (min-width: 380px) { - .pokemons { - grid-template-columns: 1fr 1fr; - } -} - -@media screen and (min-width: 576px) { - .pokemons { - grid-template-columns: 1fr 1fr 1fr; - } -} - -@media screen and (min-width: 992px) { - .pokemons { - grid-template-columns: 1fr 1fr 1fr 1fr; - } -} \ No newline at end of file diff --git a/assets/fonts/PokemonGB.ttf b/assets/fonts/PokemonGB.ttf new file mode 100644 index 000000000..b5025f062 Binary files /dev/null and b/assets/fonts/PokemonGB.ttf differ diff --git a/assets/img/logo.png b/assets/img/logo.png new file mode 100644 index 000000000..1733cd902 Binary files /dev/null and b/assets/img/logo.png differ diff --git a/assets/img/pokeball.ico b/assets/img/pokeball.ico new file mode 100644 index 000000000..ee3225059 Binary files /dev/null and b/assets/img/pokeball.ico differ diff --git a/assets/img/pokedex.png b/assets/img/pokedex.png new file mode 100644 index 000000000..54b7bb06d Binary files /dev/null and b/assets/img/pokedex.png differ diff --git a/assets/js/main.js b/assets/js/main.js deleted file mode 100644 index bcaa24508..000000000 --- a/assets/js/main.js +++ /dev/null @@ -1,47 +0,0 @@ -const pokemonList = document.getElementById('pokemonList') -const loadMoreButton = document.getElementById('loadMoreButton') - -const maxRecords = 151 -const limit = 10 -let offset = 0; - -function convertPokemonToLi(pokemon) { - return ` -
  • - #${pokemon.number} - ${pokemon.name} - -
    -
      - ${pokemon.types.map((type) => `
    1. ${type}
    2. `).join('')} -
    - - ${pokemon.name} -
    -
  • - ` -} - -function loadPokemonItens(offset, limit) { - pokeApi.getPokemons(offset, limit).then((pokemons = []) => { - const newHtml = pokemons.map(convertPokemonToLi).join('') - pokemonList.innerHTML += newHtml - }) -} - -loadPokemonItens(offset, limit) - -loadMoreButton.addEventListener('click', () => { - offset += limit - const qtdRecordsWithNexPage = offset + limit - - if (qtdRecordsWithNexPage >= maxRecords) { - const newLimit = maxRecords - offset - loadPokemonItens(offset, newLimit) - - loadMoreButton.parentElement.removeChild(loadMoreButton) - } else { - loadPokemonItens(offset, limit) - } -}) \ No newline at end of file diff --git a/assets/js/poke-api.js b/assets/js/poke-api.js deleted file mode 100644 index 38fbfd465..000000000 --- a/assets/js/poke-api.js +++ /dev/null @@ -1,35 +0,0 @@ - -const pokeApi = {} - -function convertPokeApiDetailToPokemon(pokeDetail) { - const pokemon = new Pokemon() - pokemon.number = pokeDetail.id - pokemon.name = pokeDetail.name - - const types = pokeDetail.types.map((typeSlot) => typeSlot.type.name) - const [type] = types - - pokemon.types = types - pokemon.type = type - - pokemon.photo = pokeDetail.sprites.other.dream_world.front_default - - return pokemon -} - -pokeApi.getPokemonDetail = (pokemon) => { - return fetch(pokemon.url) - .then((response) => response.json()) - .then(convertPokeApiDetailToPokemon) -} - -pokeApi.getPokemons = (offset = 0, limit = 5) => { - const url = `https://pokeapi.co/api/v2/pokemon?offset=${offset}&limit=${limit}` - - return fetch(url) - .then((response) => response.json()) - .then((jsonBody) => jsonBody.results) - .then((pokemons) => pokemons.map(pokeApi.getPokemonDetail)) - .then((detailRequests) => Promise.all(detailRequests)) - .then((pokemonsDetails) => pokemonsDetails) -} diff --git a/assets/js/pokemon-model.js b/assets/js/pokemon-model.js deleted file mode 100644 index b0d17bb90..000000000 --- a/assets/js/pokemon-model.js +++ /dev/null @@ -1,8 +0,0 @@ - -class Pokemon { - number; - name; - type; - types = []; - photo; -} diff --git a/assets/prints/detalhe.png b/assets/prints/detalhe.png new file mode 100644 index 000000000..f4d7f418f Binary files /dev/null and b/assets/prints/detalhe.png differ diff --git a/assets/prints/index.png b/assets/prints/index.png new file mode 100644 index 000000000..9643274e1 Binary files /dev/null and b/assets/prints/index.png differ diff --git a/assets/prints/mobile.png b/assets/prints/mobile.png new file mode 100644 index 000000000..002fa4c50 Binary files /dev/null and b/assets/prints/mobile.png differ diff --git a/index.html b/index.html index 1a017821d..f4c8c3a15 100644 --- a/index.html +++ b/index.html @@ -1,46 +1,69 @@ - - - - - - Pokedex + + + + + + + Pokédex - + - - - - + + + + - - - - + + + - + +
    +
    +

    + +

    +
    +
    -

    Pokedex

    + -
      - -
    +
      + +
    - +
    - - - - - +
    + +
    +
    +
    - \ No newline at end of file + + + + + + + diff --git a/src/css/global.css b/src/css/global.css new file mode 100644 index 000000000..c75dc54c3 --- /dev/null +++ b/src/css/global.css @@ -0,0 +1,102 @@ +@font-face { + font-family: "Pokemon"; + src: url("../../assets/fonts/PokemonGB.ttf") format("truetype"); + font-weight: normal; + font-style: normal; +} + +* { + font-family: "Pokemon"; + box-sizing: border-box; +} + +a { + text-decoration: none; + color: inherit; +} + +html { + scroll-behavior: smooth; + overflow-x: clip; + overscroll-behavior: none; + box-sizing: border-box; +} + +body { + background: url("../../assets/img/pokedex.png"); + background-color: #eb4441; + background-size: 100%; + background-repeat: no-repeat; +} + +.content { + width: 100vw; + height: fit-content; + padding: 1rem; + background-color: rgba(0, 0, 0, 0.5); +} + +.content img { + max-width: 100%; + align-items: center; +} + +.settings { + position: absolute; + top: 0; + left: 0; + height: 20vh; + padding: 0; + width: 20vh; + z-index: 20; +} + +.cog { + width: 100%; + height: auto; + border: 0; + color: azure; + position: absolute; + display: flex; + text-align: center; + align-items: center; + justify-content: center; +} + +.cog p { + position: absolute; + font-size: 8vh; + top: 0; + margin-left: 10px; + cursor: pointer; + background: rgba(0, 0, 0, 0.5); + border: 5px rgba(0, 0, 0, 0.5); + border-radius: 3rem; + width: 50%; +} + +@media screen and (min-width: 992px) { + .content { + max-width: 992px; + height: auto; + margin: 1rem auto; + border-radius: 1rem; + } +} + +@media (max-width: 800px) { + .settings { + width: 18vh; + left: 0; + bottom: 0 !important; + top: auto; + position: fixed; + } + + .content { + width: 100%; + margin: 0 auto; + border-radius: 1rem; + align-items: center; + } +} diff --git a/src/css/pokedetail.css b/src/css/pokedetail.css new file mode 100644 index 000000000..8ede3d995 --- /dev/null +++ b/src/css/pokedetail.css @@ -0,0 +1,162 @@ +.row { + display: flex; + flex-direction: row; + align-items: center; +} + +.info { + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + font-size: 0.7rem; + width: 100%; +} + +.info img { + max-width: 80%; + height: 200px; + margin: 1rem 0; +} + +.info i { + font-size: 3rem; + cursor: pointer; + position: absolute; +} + +#previous { + left: 10%; +} + +#next { + right: 10%; +} + +.info .type { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.about { + background-color: white; + height: fit-content; + padding: 1rem; + margin: 1rem 0; + border-radius: 4px; + box-shadow: 0 2px 8px -2px rgba(34, 34, 34, 0.4); + border-radius: 1rem; + width: 90%; +} + +.about .title { + display: flex; + flex-direction: row; + font-size: 1rem; +} + +.modal { + text-transform: uppercase; +} + +.modal .number { + font-size: 1rem; + cursor: default; +} + +.modal .name { + text-transform: capitalize; + cursor: default; +} + +.info hr { + width: 80%; + margin-left: 0.7rem; + margin-right: 0.7rem; + background-color: #000; +} + +ul { + padding: 0; + list-style: none; + text-transform: uppercase; +} + +.stats-container .stats { + display: flex; + align-items: center; + gap: 2.5rem; +} + +.stats-container .stats p { + min-width: 30px; +} + +.stats-container .progress-bar { + width: 100%; + height: 1rem; + background-color: #dddddd; + border-radius: 6px; + position: relative; +} +.stats-container .progress-bar .progress { + height: 100%; + border-radius: 6px; + position: absolute; +} + +.stats-container .stats :first-child { + flex: 1; +} + +.stats-container .stats :nth-child(2) { + flex: 1; +} + +.stats-container .stats :nth-child(3) { + flex: 9; +} + +.overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: black; + opacity: 0.7; + z-index: 1; +} + +.pokemonDetail { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #000; + border-radius: 1rem; + z-index: 1000; + width: 500px; + max-width: 100%; + padding: 5px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.detailOrganizer { + transition: 1s ease-in-out; +} + +.modal { + padding: 20px; + max-width: 100%; + border-radius: inherit; +} + +.modal > span { + cursor: pointer; +} diff --git a/src/css/pokedex.css b/src/css/pokedex.css new file mode 100644 index 000000000..77bfe99ff --- /dev/null +++ b/src/css/pokedex.css @@ -0,0 +1,182 @@ +.pokemons { + display: grid; + grid-template-columns: 1fr; + margin: 0; + padding: 0; + list-style: none; +} + +.normal { + background-color: #a6a877; +} + +.grass { + background-color: #77c850; +} + +.fire { + background-color: #ee7f30; +} + +.water { + background-color: #678fee; +} + +.electric { + background-color: #f7cf2e; +} + +.ice { + background-color: #98d5d7; +} + +.ground { + background-color: #dfbf69; +} + +.flying { + background-color: #a98ff0; +} + +.poison { + background-color: #a040a0; +} + +.fighting { + background-color: #bf3029; +} + +.psychic { + background-color: #f65687; +} + +.dark { + background-color: #725847; +} + +.rock { + background-color: #b8a137; +} + +.bug { + background-color: #a8b720; +} + +.ghost { + background-color: #6e5896; +} + +.steel { + background-color: #b9b7cf; +} + +.dragon { + background-color: #6f38f6; +} + +.fairy { + background-color: #f9aec7; +} + +.pokemon { + display: flex; + flex-direction: column; + margin: 0.5rem; + padding: 1rem; + border-radius: 1rem; + transition: ease-in-out 0.5s; + cursor: pointer; +} + +#logo { + transition: ease-in-out 0.5s; + cursor: pointer; + margin-bottom: 2rem; +} + +.pokemon:hover, +#logo:hover { + transform: scale(1.1); +} + +.number { + color: #000; + opacity: 0.3; + text-align: right; + font-size: 0.625rem; +} + +.name { + text-transform: capitalize; + color: #fff; + margin-bottom: 0.25rem; + text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; +} + +.detail { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.types { + padding: 0; + margin: 0; + list-style: none; +} + +.types .type { + color: black; + padding: 0.25rem 0.5rem; + margin: 0.25rem 0; + font-size: 0.6rem; + font-variant: small-caps; + font-weight: bolder; + border-radius: 1rem; + filter: brightness(1.1); + text-align: center; +} + +.detail img { + max-width: 70%; + height: 80px; +} + +.pagination { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 100%; + padding: 1rem; +} + +.pagination button { + padding: 1rem 1rem; + margin: 0.25rem 0; + font-size: 1.2rem; + color: #fff; + background-color: #0b74bf; + border: none; + border-radius: 1rem; + cursor: pointer; +} + +@media screen and (min-width: 380px) { + .pokemons { + grid-template-columns: 1fr 1fr; + } +} + +@media screen and (min-width: 576px) { + .pokemons { + grid-template-columns: 1fr 1fr 1fr; + } +} + +@media screen and (min-width: 992px) { + .pokemons { + grid-template-columns: 1fr 1fr 1fr 1fr; + } +} diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 000000000..4004180bb --- /dev/null +++ b/src/js/main.js @@ -0,0 +1,51 @@ +const pokemonList = document.getElementById("pokemonList"); +const loadMoreButton = document.getElementById("loadMoreButton"); + +const maxRecords = 151; +const limit = 12; +let offset = 0; + +function convertPokemonToLi(pokemon) { + return ` +
  • + #${pokemon.number} + ${pokemon.name} + +
    +
      + ${pokemon.types + .map((type) => `
    1. ${type}
    2. `) + .join("")} +
    + + ${pokemon.name} +
    +
  • + `; +} + +function loadPokemonItens(offset, limit) { + pokeApi.getPokemons(offset, limit).then((pokemons = []) => { + const newHtml = pokemons.map(convertPokemonToLi).join(""); + pokemonList.innerHTML += newHtml; + }); +} + +loadPokemonItens(offset, limit); + +loadMoreButton.addEventListener("click", () => { + offset += limit; + const qtdRecordsWithNexPage = offset + limit; + + if (qtdRecordsWithNexPage >= maxRecords) { + const newLimit = maxRecords - offset; + loadPokemonItens(offset, newLimit); + + loadMoreButton.parentElement.removeChild(loadMoreButton); + } else { + loadPokemonItens(offset, limit); + } +}); diff --git a/src/js/poke-api.js b/src/js/poke-api.js new file mode 100644 index 000000000..0e053ff75 --- /dev/null +++ b/src/js/poke-api.js @@ -0,0 +1,63 @@ +const pokeApi = {}; + +function convertPokeApiDetailToPokemon(pokeDetail) { + const pokemon = new Pokemon(); + pokemon.number = pokeDetail.id; + pokemon.name = pokeDetail.name; + pokemon.peso = pokeDetail.weight; + pokemon.altura = pokeDetail.height; + + const types = pokeDetail.types.map((typeSlot) => typeSlot.type.name); + const [type] = types; + + pokemon.types = types; + pokemon.type = type; + + pokemon.stats.hp = pokeDetail.stats.find((item) => item.stat.name === "hp"); + pokemon.stats.atk = pokeDetail.stats.find( + (item) => item.stat.name === "attack" + ); + + pokemon.stats.def = pokeDetail.stats.find( + (item) => item.stat.name === "defense" + ); + pokemon.stats.sAtk = pokeDetail.stats.find( + (item) => item.stat.name === "special-attack" + ); + + pokemon.stats.sDef = pokeDetail.stats.find( + (item) => item.stat.name === "special-defense" + ); + pokemon.stats.spd = pokeDetail.stats.find( + (item) => item.stat.name === "speed" + ); + + pokemon.photo = pokeDetail.sprites.other.showdown.front_default; + + return pokemon; +} + +pokeApi.getPokemonDetail = (pokemon) => { + return fetch(pokemon.url) + .then((response) => response.json()) + .then(convertPokeApiDetailToPokemon); +}; + +pokeApi.getPokemons = (offset = 0, limit = 5) => { + const url = `https://pokeapi.co/api/v2/pokemon?offset=${offset}&limit=${limit}`; + + return fetch(url) + .then((response) => response.json()) + .then((jsonBody) => jsonBody.results) + .then((pokemons) => pokemons.map(pokeApi.getPokemonDetail)) + .then((detailRequests) => Promise.all(detailRequests)) + .then((pokemonsDetails) => pokemonsDetails); +}; + +pokeApi.getPokemonByID = (pokemonId) => { + const url = `https://pokeapi.co/api/v2/pokemon/${pokemonId}`; + + return fetch(url) + .then((response) => response.json()) + .then((data) => convertPokeApiDetailToPokemon(data)); +}; diff --git a/src/js/pokeId.js b/src/js/pokeId.js new file mode 100644 index 000000000..ff9c2d87a --- /dev/null +++ b/src/js/pokeId.js @@ -0,0 +1,137 @@ +const overlay = document.getElementById("overlay"); +const pokemonDetail = document.getElementById("pokemonDetail"); +let currentPokemonId = 1; + +function PokemonByID(pokemon) { + return ` + + `; +} + +const fade = [{ opacity: 0 }, { opacity: 1 }]; + +const fadeTiming = { + duration: 350, + iterations: 1, +}; +let animation; +let visible = false; + +function fadeActivate() { + animation = pokemonDetail.animate(fade, fadeTiming); +} + +function showPokemonDetail(pokemonId) { + if (!visible) { + fadeActivate(); + visible = true; + pokeApi.getPokemonByID(pokemonId).then((data) => { + currentPokemonId = pokemonId; + const singlePokemon = PokemonByID(data); + Modal(singlePokemon); + }); + if (visible) { + overlay.addEventListener("click", () => { + closePokemonDetail(); + }); + } + } +} + +function Modal(singlePokemon) { + overlay.style.display = "block"; + pokemonDetail.style.display = "block"; + pokemonDetail.innerHTML = singlePokemon; +} + +function closePokemonDetail() { + if (visible) { + animation.reverse(); + animation.addEventListener("finish", function () { + overlay.style.display = "none"; + pokemonDetail.style.display = "none"; + }); + visible = false; + } +} + +function fetchPokemonById(pokemonId) { + return pokeApi.getPokemonByID(pokemonId); +} + +function showPokemonDetailById(pokemonId) { + fetchPokemonById(pokemonId).then((data) => { + const singlePokemon = PokemonByID(data); + Modal(singlePokemon); + }); +} + +function nextPokemon() { + const nextPokemonId = currentPokemonId + 1; + if (nextPokemonId == 152) { + currentPokemonId = 1; + showPokemonDetailById(currentPokemonId); + } else { + currentPokemonId++; + showPokemonDetailById(currentPokemonId); + } +} + +function previousPokemon() { + const previousPokemonId = currentPokemonId - 1; + if (previousPokemonId < 1) { + currentPokemonId = 151; + showPokemonDetailById(currentPokemonId); + } else { + currentPokemonId--; + showPokemonDetailById(currentPokemonId); + } +} diff --git a/src/js/pokemon-model.js b/src/js/pokemon-model.js new file mode 100644 index 000000000..3b178fa6a --- /dev/null +++ b/src/js/pokemon-model.js @@ -0,0 +1,16 @@ +class Pokemon { + number; + name; + type; + types = []; + photo; + description; + stats = { + hp: 0, + atk: 0, + def: 0, + sAtk: 0, + sDef: 0, + spd: 0, + }; +}