diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1e267ed8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore vscode files +.vscode \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..73ef0e4c --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# JavaScript Project - App +Find Your Movie App + +# API source +https://imdb-api.com/api#SearchMovie-header + +# How to use + +Enter the title of the film you want to find in the search field. +Wait a few seconds. +Then click on the film you like to view more information about it. +In addition to the first search suggestions, you will get other suggested movies based on the category of the movie you selected. +You can navigate through your search results and suggestions using the pagination buttons. diff --git a/assets/cinema.jpg b/assets/cinema.jpg new file mode 100644 index 00000000..adbf5e59 Binary files /dev/null and b/assets/cinema.jpg differ diff --git a/index.html b/index.html index e69de29b..b48fdc86 100644 --- a/index.html +++ b/index.html @@ -0,0 +1,74 @@ + + + + + + + + + + + + movie app + + + +

+ Find.
+ Your.
+ Movie. +

+ + + + +
+
+
Loading...
+
+
+
+ + + +
+ + + +
+
+
Loading...
+
+
+
+ + + + + + + + + diff --git a/main.js b/main.js deleted file mode 100644 index e69de29b..00000000 diff --git a/scripts/main.js b/scripts/main.js new file mode 100644 index 00000000..db39e1c8 --- /dev/null +++ b/scripts/main.js @@ -0,0 +1,281 @@ +"use strict"; +import loadPaging from "./pagination.js"; +import loadPagingSuggestions from "./pagination-suggestions.js"; +import scrollToDetailBox from "./scrolling.js"; + +window.addEventListener("DOMContentLoaded", () => { + const apiKey = "k_pgd93xda"; // first api key + // const apiKey = "k_28x30ddn"; // third api key + // const apiKey = "k_n2nfzz4l"; // second api key + const insideDetailBox = document.getElementById("clicked-movie-id"); + const inputSearchMovie = document.getElementById("search-movie"); + const searchResultsMoviesContainer = document.getElementById("movies"); + const searchResultsMovies = document.getElementById("results-movies"); + const genresList = document.getElementById("genres-list"); + const suggestedResultsListContainer = + document.getElementById("suggested-movies"); + const suggestedResultsList = document.getElementById("suggested-results"); + const urlAPI = `https://imdb-api.com/en/API/SearchMovie/${apiKey}`; + const urlDetailsAPI = `https://imdb-api.com/en/API/Title/${apiKey}`; + const advancedSearchUrl = `https://imdb-api.com/API/AdvancedSearch/${apiKey}`; + + window.onload = function () { + document.getElementById("pagination-results").style.display = "none"; + document.getElementById("pagination-suggestions").style.display = "none"; + }; + + function init() { + inputSearchMovie.addEventListener("keyup", debounce(getMovies, 300)); + // event listener on click movie id from titles list + searchResultsMovies.addEventListener("click", async (event) => { + const movieID = event.target.closest("a").id; + await getDetails(movieID); + scrollToDetailBox(); + }); + // event listener on click movie id for genres movies + suggestedResultsList.addEventListener("click", async (event) => { + const movieID = event.target.closest("a").id; + await getDetails(movieID); + scrollToDetailBox(); + }); + } + init(); + + // wait for execute + function debounce(callback, delay) { + let timeout; + return function () { + clearTimeout(timeout); + timeout = setTimeout(callback, delay); + }; + } + + // clear html target + function clearHtml(target) { + target.innerHTML = ""; + } + + //show loader + function showLoader(target) { + const loader = target.getElementsByClassName("loader-container"); + loader[0].classList.add("visible"); + } + // hide loader + function hideLoader(target) { + const loader = target.getElementsByClassName("loader-container"); + loader[0].classList.remove("visible"); + } + + // search results + async function getMovies(movie) { + showLoader(searchResultsMoviesContainer); + const movieValue = inputSearchMovie.value; + const results = await fetch(`${urlAPI}/${movieValue}`) + .then((response) => response.json()) + .then((data) => data.results) + .then((results) => { + hideLoader(searchResultsMoviesContainer); + document.getElementById("pagination-results").style.display = "flex"; + // add pagintion + loadPaging(results.length, (pagingOptions) => { + clearHtml(searchResultsMovies); + const newArray = pageArraySplit(results, pagingOptions); + createMoviesDOM(newArray, searchResultsMovies); + }); + }) + .catch((error) => console.log(error)); + } + + // create movie list in DOM + function createMoviesDOM(movies, htmlTarget) { + for (const movie of movies) { + const movieElement = document.createElement("div"); + movieElement.classList.add("selected-movie"); + movieElement.setAttribute("id", "click-movie"); + movieElement.innerHTML = `${movie.title}`; + movieElement.style.backgroundImage = `url(${movie.image})`; + movieElement.style.backgroundSize = "cover"; + htmlTarget.appendChild(movieElement); + // movie title + const movieElementTitle = document.createElement("p"); + movieElementTitle.classList.add("results-movies-title"); + movieElementTitle.setAttribute("id", "click-movie"); + movieElementTitle.innerHTML = `${movie.title}`; + movieElement.appendChild(movieElementTitle); + } + } + + // get id from selected movie link and create div with details + async function getDetails(movieId) { + const details = await fetch(`${urlDetailsAPI}/${movieId}`) + .then((response) => response.json()) + .then((data) => { + clearHtml(insideDetailBox); + createMovieDetailsDom(data); + if (data.genreList) { + const genres = transformGenresToApiParam(data.genreList); // " " + showSuggestions(genres); + } + }) + .catch((error) => new Error("Details not found")); + } + + // create selected from API details + function createMovieDetailsDom(data) { + insideDetailBox.classList.add("detail-conatiner"); + //image + if (data.image) { + const detailBoxImage = document.createElement("div"); + const title = data.title ? data.title : ""; + detailBoxImage.innerHTML = `${title}`; + detailBoxImage.classList.add("movie-image"); + detailBoxImage.classList.add("box-left"); + insideDetailBox.appendChild(detailBoxImage); + } + //rightBox + const detailBoxRight = document.createElement("div"); + detailBoxRight.classList.add("box-right"); + insideDetailBox.append(detailBoxRight); + //title + if (data.title) { + const detailBox = document.createElement("div"); + detailBox.innerHTML = `${data.title}`; + detailBox.classList.add("title"); + detailBoxRight.appendChild(detailBox); + } + //plot + if (data.plot) { + const detailBoxPlot = document.createElement("p"); + detailBoxPlot.innerHTML = `${data.plot}`; + detailBoxPlot.classList.add("plot"); + detailBoxRight.appendChild(detailBoxPlot); + } + //rating + if (data.imDbRating) { + const ratingIMDb = document.createElement("p"); + ratingIMDb.innerHTML = `IMDb Rating`; + ratingIMDb.classList.add("rating-title"); + detailBoxRight.appendChild(ratingIMDb); + const detailBoxRating = document.createElement("p"); + detailBoxRating.innerHTML = `${data.imDbRating} / 10`; + detailBoxRating.classList.add("rating"); + detailBoxRight.appendChild(detailBoxRating); + } + // year + if (data.year) { + const detailBoxYear = document.createElement("p"); + detailBoxYear.innerHTML = `Year: ${data.year}`; + detailBoxYear.classList.add("year"); + detailBoxRight.appendChild(detailBoxYear); + } + // genres + if (data.genres) { + const detailBoxGenres = document.createElement("p"); + detailBoxGenres.innerHTML = `${data.genres}`; + detailBoxGenres.classList.add("genres"); + detailBoxGenres.id = "genres"; + detailBoxRight.appendChild(detailBoxGenres); + } + // list genres + if (data.genreList) { + createGenresDom(data.genreList, genresList); + } + // languages + if (data.languages) { + const detailBoxLanguages = document.createElement("p"); + detailBoxLanguages.innerHTML = `${data.languages}`; + detailBoxLanguages.classList.add("movie-languages"); + detailBoxRight.appendChild(detailBoxLanguages); + } + //stars + if (data.starList) { + createStars(data.starList, detailBoxRight); + } + //awards + if (data.awards) { + const detailBoxAwards = document.createElement("p"); + detailBoxAwards.innerHTML = `${data.awards}`; + detailBoxAwards.classList.add("movie-awards"); + detailBoxRight.appendChild(detailBoxAwards); + } + //runtimeStr + if (data.runtimeStr) { + const detailBoxRuntimeStr = document.createElement("p"); + detailBoxRuntimeStr.innerHTML = `${data.runtimeStr}`; + detailBoxRuntimeStr.classList.add("runtime-str"); + detailBoxRight.appendChild(detailBoxRuntimeStr); + } + //trailer + if (data.trailer) { + const detailBoxTrailer = document.createElement("a"); + detailBoxTrailer.innerHTML = `${data.trailer}`; + detailBoxTrailer.classList.add("trailer"); + detailBoxRight.appendChild(detailBoxTrailer); + } + } + + // list with movie's stars + function createStars(stars, box) { + const detailBoxStars = document.createElement("ul"); + detailBoxStars.classList.add("stars"); + box.appendChild(detailBoxStars); + + for (const star of stars) { + const starElement = document.createElement("li"); + starElement.innerHTML = star.name; + detailBoxStars.appendChild(starElement); + } + } + + //suggested movies + async function showSuggestions(genres) { + showLoader(suggestedResultsListContainer); + const suggestions = await fetch(`${advancedSearchUrl}/?genres=${genres}`) + .then((response) => response.json()) + .then((data) => data.results) + .then((results) => { + hideLoader(suggestedResultsListContainer); + document.getElementById("pagination-suggestions").style.display = + "flex"; + loadPagingSuggestions(results.length, (pagingOptions) => { + clearHtml(suggestedResultsList); + const newArray = pageArraySplit(results, pagingOptions); + createMoviesDOM(newArray, suggestedResultsList); + }); + }) + .catch((error) => new Error("Suggestions not found")); + } + + // transform genres to api param + function transformGenresToApiParam(genres) { + const forSuggestionsGenres = genres.map((genre) => { + return genre.value; + }); + return forSuggestionsGenres.join().toLowerCase(); + } + + // create div with genres in DOM + function createGenresDom(genres) { + const genresElement = document.getElementById("genres-list"); + genresElement.classList.add("genres-suggestions"); + genresElement.innerHTML = ""; + const suggestionsHeader = document.createElement("h2"); + suggestionsHeader.innerHTML = `Other suggested movies`; + genresElement.appendChild(suggestionsHeader); + for (const genre of genres) { + const genreElement = document.createElement("li"); + genreElement.classList.add("single-genre"); + genreElement.innerHTML = `${genre.value}`; + genresElement.appendChild(genreElement); + } + } + // pagination for search results + function pageArraySplit(array, pagingOptions) { + const currentPageNumber = pagingOptions.currentPageNumber; + const perPage = pagingOptions.perPage; + const startingIndex = (currentPageNumber - 1) * perPage; + const endingIndex = startingIndex + perPage; + + return array.slice(startingIndex, endingIndex); + } +}); diff --git a/scripts/pagination-suggestions.js b/scripts/pagination-suggestions.js new file mode 100644 index 00000000..72ab974f --- /dev/null +++ b/scripts/pagination-suggestions.js @@ -0,0 +1,38 @@ +"use strict"; +const prevBtn = document.getElementById("prev-bt"); +const nextBtn = document.getElementById("next-bt"); +const curPage = document.getElementById("cur-page"); +const totPages = document.getElementById("tot-pages"); + +let width = document.body.clientWidth; +let PER_PAGE = width > 767 ? 5 : 1; + +export default function loadPagingSuggestions(totalItems, callback) { + let currentPageNumber = 1; + prevBtn.disabled = currentPageNumber === 1; + curPage.textContent = currentPageNumber; + const totalPageCount = Math.ceil(totalItems / PER_PAGE); + totPages.textContent = totalPageCount; + + function updatePaging() { + curPage.textContent = currentPageNumber; + const pagingOptions = { + currentPageNumber: currentPageNumber, + perPage: PER_PAGE, + }; + callback(pagingOptions); + nextBtn.disabled = currentPageNumber === totalPageCount; + prevBtn.disabled = currentPageNumber === 1; + } + updatePaging(); + + nextBtn.addEventListener("click", () => { + currentPageNumber++; + updatePaging(); + }); + + prevBtn.addEventListener("click", () => { + currentPageNumber--; + updatePaging(); + }); +} diff --git a/scripts/pagination.js b/scripts/pagination.js new file mode 100644 index 00000000..147f1275 --- /dev/null +++ b/scripts/pagination.js @@ -0,0 +1,38 @@ +"use strict"; +const previousButton = document.getElementById("previous"); +const nextButton = document.getElementById("next"); +const currentPage = document.getElementById("current-page"); +const totalPages = document.getElementById("total-pages"); + +let width = document.body.clientWidth; +let PER_PAGE = width > 767 ? 5 : 1; + +export default function loadPaging(totalItems, callback) { + let currentPageNumber = 1; + previousButton.disabled = currentPageNumber === 1; + currentPage.textContent = currentPageNumber; + const totalPageCount = Math.ceil(totalItems / PER_PAGE); + totalPages.textContent = totalPageCount; + + function updatePaging() { + currentPage.textContent = currentPageNumber; + const pagingOptions = { + currentPageNumber: currentPageNumber, + perPage: PER_PAGE, + }; + callback(pagingOptions); + nextButton.disabled = currentPageNumber === totalPageCount; + previousButton.disabled = currentPageNumber === 1; + } + updatePaging(); + + nextButton.addEventListener("click", () => { + currentPageNumber++; + updatePaging(); + }); + + previousButton.addEventListener("click", () => { + currentPageNumber--; + updatePaging(); + }); +} diff --git a/scripts/scrolling.js b/scripts/scrolling.js new file mode 100644 index 00000000..d6e40a11 --- /dev/null +++ b/scripts/scrolling.js @@ -0,0 +1,8 @@ +//on click id selected-movie scroll to div with id = click-movie +export default function scrollToDetailBox() { + const clickedMovie = document.getElementById("clicked-movie-id"); + clickedMovie.scrollIntoView({ + behavior: "smooth", + block: "start", + }); +} diff --git a/style.css b/style.css deleted file mode 100644 index e69de29b..00000000 diff --git a/styles/loader.css b/styles/loader.css new file mode 100644 index 00000000..19add600 --- /dev/null +++ b/styles/loader.css @@ -0,0 +1,65 @@ +.loader-container { + display: none; + padding-top: 100px; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.1); +} +.loader-container.visible { + min-height: 300px; + display: flex; + justify-content: center; + height: 100%; + width: 100%; + position: absolute; + background: #000000a8; + z-index: 9999; +} +.loader { + position: absolute; + color: #fff; + font-size: 10px; + width: 1em; + height: 1em; + border-radius: 50%; + position: relative; + text-indent: -9999em; + animation: mulShdSpin 1.3s infinite linear; + transform: translateZ(0); +} + +@keyframes mulShdSpin { + 0%, + 100% { + box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, + 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0; + } + 12.5% { + box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, + 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em; + } + 25% { + box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, + 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em; + } + 37.5% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, + 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em; + } + 50% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, + 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em; + } + 62.5% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, + 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em; + } + 75% { + box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, + 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0; + } + 87.5% { + box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, + 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em; + } +} diff --git a/styles/style.css b/styles/style.css new file mode 100644 index 00000000..0a5c3b46 --- /dev/null +++ b/styles/style.css @@ -0,0 +1,305 @@ +body { + background-color: rgb(0, 0, 0); + font-family: "Montserrat", sans-serif; + color: white; + text-align: center; + margin: 0 auto; +} +a { + cursor: pointer; +} +h1 { + font-size: 120px; + font-weight: 700; + background-clip: text; + -webkit-background-clip: text; + color: transparent; + line-height: 1; + text-transform: uppercase; + font-family: "Inter", sans-serif; + font-weight: 900; + background-image: linear-gradient( + 0deg, + rgba(255, 0, 150, 0.15), + rgba(255, 0, 150, 0.15) + ), + url(https://elachcdv.github.io/javascript-project-2022/assets/cinema.jpg); + background-size: cover; +} +.bold { + font-weight: bold; +} +/* input */ +label { + font-size: 1.5em; + color: black; + display: block; +} +input { + font-size: 1.2em; + margin: 0 auto 100px auto; + padding: 20px 50px; + background: black; + text-align: center; + color: rgb(212, 212, 212); + display: block; + border: 1px solid #170f32; + box-shadow: 1px 3px 55px 3px #3e268d; + border-radius: 10px; +} +input:focus { + outline: none; +} +/* results */ +.results-movies, +.results-genres { + display: flex; + align-items: center; + justify-content: space-evenly; + align-content: center; + flex-direction: row; + flex-wrap: wrap; + margin-bottom: 100px; +} +.selected-movie { + max-width: 250px; + margin: 40px auto; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + align-content: center; + flex-direction: column; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} +.selected-movie > a { + font-size: 0px; +} +.selected-movie a { + width: 250px; + height: 350px; + border-radius: 10px; +} +p.results-movies-title { + font-size: 18px; + position: relative; + bottom: -50px; + font-weight: bold; + background-color: rgb(0 0 0 / 81%); +} +.detail-conatiner { + max-width: 1100px; + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-column-gap: 30px; + grid-row-gap: 30px; + text-align: justify; + margin: 100px auto; + justify-items: center; + align-items: center; +} +.movie-image img { + border-radius: 15px; + max-height: 600px; + width: 100%; +} +.box-left, +.box-right { + height: auto; + float: left; +} +.box-right { + padding: 50px; + max-width: 600px; +} +/* DETAILS MOVIE */ +/* rating */ +.title { + font-size: 2em; + font-weight: bold; +} +.plot, +.year, +.genres, +.movie-languages, +.stars { + font-size: 1.2em; +} +.rating { + font-size: 2em; + font-weight: 900; + margin: 0; +} +.rating::before { + content: "\2605"; + color: goldenrod; + margin-right: 10px; +} +.rating-title { + font-size: 1em; + font-weight: bold; + margin: 20px 0 0 0; +} +/* awards */ +.movie-awards { + font-size: 1.1em; + /* margin: 0; */ + font-weight: bold; +} +/* suggestions */ +.genres-suggestions { + list-style: none; + display: flex; + justify-content: center; + align-items: center; + flex-direction: row; + margin: 50px auto 10px auto; + flex-wrap: wrap; +} +ul.genres-suggestions { + padding-left: inherit; +} +.single-genre { + color: white; + padding: 20px 30px; + border-radius: 9px; + font-size: 15px; + font-weight: bold; +} +.genres-suggestions h2 { + font-size: 26px; + font-weight: bold; + width: 100%; +} +/* pagination */ +.next-btn, +.prev-btn { + display: flex; + width: 20px; + height: 20px; + background-color: black; + color: white; + align-items: center; + justify-content: center; +} +.next-btn span, +.prev-btn span { + display: flex; + align-items: stretch; + justify-content: center; +} +.pagination { + align-items: center; + justify-content: space-between; + width: 300px; + margin: 0 auto; +} +button { + font-size: 20px; + --glow-color: rgb(38, 30, 140); + --btn-color: rgb(80, 71, 195); + border: 0.25em solid var(--glow-color); + padding: 1em 2.2em; + color: var(--glow-color); + font-weight: bold; + background-color: var(--btn-color); + border-radius: 1em; + outline: none; + text-shadow: 0 0 0.25em var(--glow-color); + position: relative; + transition: all 0.3s; +} +button::after { + pointer-events: none; + content: ""; + position: absolute; + top: 120%; + left: 0; + height: 100%; + width: 100%; + background-color: var(--glow-spread-color); + filter: blur(2em); + opacity: 0.7; + transform: perspective(1.5em) rotateX(35deg) scale(1, 0.6); +} +button:hover { + color: white; + background-color: rgb(16 12 56); +} +button:active { + box-shadow: 0 0 0.6em 0.25em var(--glow-color), + 0 0 2.5em 2em var(--glow-spread-color), + inset 0 0 0.5em 0.25em var(--glow-color); +} +/* media queries */ +@media only screen and (max-width: 1024px) { + p.results-movies-title { + font-size: 16px; + } +} +@media only screen and (min-width: 480px) and (max-width: 1024px) { + .selected-movie { + max-width: 150px; + } + .selected-movie a { + width: 150px; + height: 200px; + } +} +@media only screen and (max-width: 480px) { + h1 { + font-size: 70px; + } + .selected-movie a { + height: 300px; + } + .single-genre { + padding: 10px; + } + .genres-suggestions h2 { + font-size: 20px; + } + input { + padding: 10px; + margin: 0 auto 30px auto; + } + .detail-conatiner { + border-radius: 20px; + padding-top: 30px; + border: 1px solid white; + max-width: 100%; + display: flex; + text-align: justify; + margin: 50px 10px 10px 10px; + justify-items: center; + align-items: center; + flex-direction: column; + justify-content: center; + align-content: center; + } + .box-left, + .box-right { + height: auto; + float: none; + } + .movie-image img { + max-height: 380px; + max-width: 100%; + } + .plot, + .year, + .genres, + .movie-languages, + .stars { + font-size: 1em; + } + .box-right { + padding: 10px 30px 0px 30px; + } + .results-movies, + .results-genres { + margin-bottom: 50px; + } +}