diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f457eb --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# Superhero Hunter + +Superhero Hunter using vanilla JavaScript project for frontend web development. + +1. Git repository link: https://github.com/satyam-software-developer/stopwatch-vanilla-javascript.git +2. Hosted link: https://satyam-software-developer.github.io/stopwatch-vanilla-javascript/ + +## Overview + +The Superhero Hunter App is a web application that allows users to search for superheroes using the Marvel API. It displays a list of superheroes based on user input and provides additional information on each superhero. The app also includes a feature to mark superheroes as favorites and view them on a separate page. This app is built using vanilla JavaScript and Marvel’s API, styled using CSS frameworks such as Bootstrap. + +## Description + +This is a web application built using JavaScript, HTML, and CSS that allows users to search for their favorite Marvel superheroes and view their detailed information. The app also allows users to add characters to their favorites list for easy access. + +## Features + +- Search functionality for Marvel superheroes +- Add characters to favorites list +- View detailed information for characters + +# Home Page + +- Displays a list of superheroes. +- Includes a search bar to filter superheroes based on the search query. +- Each superhero result has a "Favorite" button. +- Clicking on a superhero name opens a new page with detailed information. + +# Superhero Page + +- Displays detailed information about the superhero (name, photo, bio, comics, events, series, stories). + +# Favorite Superheroes Page + +- Displays a list of favorite superheroes. +- This list persists between browser sessions using localStorage. +- Each superhero has a "Remove from favorites" button. + +## Marvel API Integration + +The app utilizes the Marvel API to fetch superhero data. This includes: + +- Listing superheroes. +- Detailed superhero information. +- Search functionality based on user input. + +## API Authentication + +To use the Marvel API, we need to generate a hash using a combination of: + +- Timestamp (ts). +- Private Key. +- Public Key. + +## Functionality + +# Home Page + +- Search: Enter the superhero name to filter results using the Marvel API. +- Favorite: Each superhero has a "Favorite" button that adds the superhero to the "My Favorite Superheroes" list, stored in localStorage. +- View Superhero: Clicking on the superhero’s name leads to a detailed page with more information. + +# Superhero Page + +- Displays a lot of information, including name, photo, bio, comics, events, series, and stories, fetched from the Marvel API. + +# Favorite Superheroes Page + +- Lists all superheroes marked as favorite. +- Includes a "Remove from favorites" button for each superhero, which updates the list in real-time and in localStorage. + +## LocalStorage for Favorites + +The "My Favorite Superheroes" list is stored in the browser’s localStorage, ensuring that the list persists between browser sessions. You can add and remove superheroes from this list. + +## Technologies Used + +- HTML5 +- CSS3 (Bootstrap) +- JavaScript (Vanilla) for API calls, DOM manipulation, and event handling. +- Marvel API for superhero data. +- localStorage for persisting favorite superheroes. + +## Dependencies + +- Javascript +- HTML +- CSS +- Marvel API('https://developer.marvel.com/') + +## Deployment + +- Clone the repository to your local machine + +```bash + git clone https://github.com/satyam-software-developer/stopwatch-vanilla-javascript.git +``` + +- Obtain an API key from Marvel Developer Portal (https://developer.marvel.com/) and add it to the appropriate location in the code + +- Run the application by opening the index.html file in your browser. + +## Usage + +1. Search for a Marvel superhero by typing their name in the search bar and clicking the "Search" button. +2. Click on a character to view their detailed information. +3. Click the "Add to Favorites" button to add a character to your favorites list. +4. View your favorite characters by clicking on the "Favorites" tab. + +## Note + +The app is using a free developer API key from Marvel, thus the usage of the app is limited by the terms of service of Marvel's API. + +## License + +This project is licensed under the MIT License. + +## DEMO + +- https://satyam-software-developer.github.io/stopwatch-vanilla-javascript/ + +## Author + +SATYAM KUMAR diff --git a/css/favorites.css b/css/favorites.css new file mode 100644 index 0000000..755c132 --- /dev/null +++ b/css/favorites.css @@ -0,0 +1,85 @@ +/* This ensures that there is no horizontal scroll bar and prevents horizontal overflow */ +body { + overflow-x: hidden; +} + +/* +This container is used to hold multiple card elements (superheroes). +It ensures that: +- The cards are spaced evenly (justify-content: space-evenly). +- The cards will wrap to the next row when they overflow (flex-wrap: wrap). +- The container doesn’t have scroll overflow (overflow: hidden). +*/ +.cards-container { + justify-content: space-evenly; + flex-wrap: wrap; + overflow: hidden; +} + +/* +This class styles buttons inside elements with the 'character-info' class. +It ensures that the button takes the full width of its parent container. +*/ +.character-info .btn { + width: 100%; +} + +/* +Styles the individual superhero card: +- Gives it a translucent background color (hsla value provides semi-transparent color). +- Sets a fixed height (484px) and width (250px) for consistency. +- Adds margin to create spacing between the cards. +- Centers text within the card (text-align: center). +- Adds a border with semi-transparent blue color. +- Rounds the corners of the card using border-radius. +- Ensures there's some space between items in the card using 'gap'. +- Adds a backdrop blur filter to give a glassy appearance to the card background. +- Sets the z-index to ensure that the card layers correctly above any potential overlapping elements. +*/ +.card { + background-color: hsla(195, 55%, 59%, 0.648); + height: 484px; + width: 250px; + margin: 40px 25px; + text-align: center; + border: 1px solid rgba(67, 93, 196, 0.749); + border-radius: 10px; + gap: 5px; + z-index: 2; + backdrop-filter: blur(3px); +} + +/* +This targets images within the '.card' class. +It rounds the top corners of the images to match the rounded corners of the card container. +*/ +.card img { + border-radius: 10px 10px 0 0; +} + +/* +This styles the name text of the superhero inside the card. +It increases the font size for emphasis and visibility. +*/ +.name { + font-size: 20px; +} + +/* +Styles the 'remove from favorites' button at the bottom of the card. +- Rounds the bottom corners to match the card's rounded corners. +- Changes the text color of the button to black. +*/ +.remove-btn { + border-radius: 0 0 10px 10px; + color: black; +} + +/* +This class is used when no characters are displayed. +It increases the font size and sets the text color to a CSS variable (--text-color). +*/ +.no-characters { + font-size: 40px; + color: var(--text-color); +} diff --git a/css/more-info.css b/css/more-info.css new file mode 100644 index 0000000..edb69b7 --- /dev/null +++ b/css/more-info.css @@ -0,0 +1,159 @@ +/* Sets the width and height of the body to take up the full viewport width and height (100vw for width and 100vh for height). */ +body { + width: 100vw; + height: 100vh; +} + +/* +Styles the container with the class 'info'. +- Centers its content horizontally and vertically using 'justify-content' and 'align-items'. +- Takes up 88% of the viewport height (remaining 12% is likely for other elements like headers or footers). +*/ +.info { + justify-content: center; + align-items: center; + height: 88%; +} + +/* +Styles the element with the ID 'info-container', which likely contains the main information about the hero. +- Gives it a translucent background color with rgba. +- Sets the width to 60% of the viewport, allowing for a centered appearance. +- The height is set to 'auto', allowing it to adjust based on its content. +- Adds a gap of 15px between child elements and 15px padding on top/bottom and 50px on the sides. +- Adds a blur effect to the background using 'backdrop-filter'. +- The border-radius of 10px makes the corners rounded for a smooth appearance. +*/ +#info-container { + background-color: rgba(134, 192, 211, 0.95); + width: 60%; + height: auto; + gap: 15px; + padding: 15px 50px; + backdrop-filter: blur(2px); + border-radius: 10px; +} + +/* +Centers the hero's name inside the container by using 'justify-content: center'. +This likely affects a flexbox container. +*/ +.hero-name { + justify-content: center; +} + +/* +Adds a gap of 30px between the hero image and the additional information about the hero. +This helps to space out the layout horizontally. +*/ +.hero-img-and-more-info { + gap: 30px; +} + +/* +Styles the hero image inside the hero information section. +- Sets the width to 20% of its container (responsive to container size). +- Sets the height to 'auto', allowing the image to scale proportionally. +- Ensures the minimum width of the image is 120px so it doesn't get too small. +*/ +.hero-img { + width: 20%; + height: auto; + min-width: 120px; +} + +/* +Styles the additional information about the hero. +- Adds a gap of 15px between the individual pieces of information. +- Sets the font size to 1.3rem, making the text slightly larger than default. +*/ +.more-info { + gap: 15px; + font-size: 1.3rem; +} + +/* +These classes ('id', 'comics', 'series', 'stories') are used for different pieces of hero information. +- Adds a small gap of 5px between child elements within these sections. +*/ +.id, +.comics, +.series, +.stories { + gap: 5px; +} + +/* +Styles the hero description text. +- Sets the font size to 1.1rem for better readability. +*/ +.hero-discription { + font-size: 1.1rem; +} + +/* +Specifically targets the 'b' (bold) elements inside the hero description. +- Increases the font size to 1.3rem to emphasize bold text, making it stand out. +*/ +.hero-discription b { + font-size: 1.3rem; +} + +/* +Styles the background image. +- Sets the z-index to -1 to push the image behind other elements on the page. +*/ +.bg-img { + z-index: -1; +} + +/* +Media query to handle responsiveness for screens smaller than or equal to 678px width (typically mobile devices). +This section makes the layout adapt to smaller screens. +*/ +@media screen and (max-width: 678px) { + /* + Changes the layout of the hero image and more info container. + - Flex direction becomes 'column' so the image and info are stacked vertically. + - Centers the elements horizontally using 'align-items: center'. + */ + .hero-img-and-more-info { + flex-direction: column; + align-items: center; + } + + /* + Centers the hero description text for smaller screens. + */ + .hero-discription { + text-align: center; + } + + /* + In smaller screens, the info container takes up 85% of the viewport width to maintain a good layout on mobile. + */ + #info-container { + width: 85%; + } + + /* + Sets the landscape image to take the full width of its container on smaller screens. + */ + #landscapeImage { + width: 100%; + } + + /* + Ensures the height of the 'info' container takes up the entire viewport on smaller screens. + */ + .info { + height: 100vh; + } + + /* + Reduces the gap between elements in the 'more-info' section to 0px for compact display on small screens. + */ + .more-info { + gap: 0px; + } +} diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..480d438 --- /dev/null +++ b/css/styles.css @@ -0,0 +1,449 @@ +/* +Resetting the default margin and padding for all elements. +Setting box-sizing to border-box ensures that padding and borders are included within the element’s width and height. +*/ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* +Declaring custom CSS variables (for light and dark mode color schemes) using the :root selector. +These variables will be reused throughout the styles for easier theme management. +*/ + +/* Light Mode Colors */ +:root { + --background-light-color: #fff; + --navbar-light-background-color: #252221f5; + --navbar-light-color: #d9330aec; + --searchBar-light-box-shadow: #bb42d9; + --text-light-color: #000; + + /* Dark Mode Colors */ + --background-dark-color: #000; + --navbar-dark-background-color: #330065; + --navbar-dark-color: #aa40b0; + --searchBar-dark-box-shadow: #972d80; + --text-dark-color: #fff; +} + +/* +Applies the dark color scheme by updating the variables. +This style is applied when the custom attribute `color-scheme="dark"` is present on the element. +*/ +[color-scheme="dark"] { + --background-color: var(--background-dark-color); + --navbar-background-color: var(--navbar-dark-background-color); + --navbar-color: var(--navbar-dark-color); + --searchBar-box-shadow: var(--searchBar-dark-box-shadow); + --text-color: var(--text-dark-color); +} + +/* +Applies the light color scheme by updating the variables. +This style is applied when the custom attribute `color-scheme="light"` is present on the element. +*/ +[color-scheme="light"] { + --background-color: var(--background-light-color); + --navbar-background-color: var(--navbar-light-background-color); + --navbar-color: var(--navbar-light-color); + --searchBar-box-shadow: var(--searchBar-light-box-shadow); + --text-color: var(var(--text-light-color)); +} + +/* +Applies the background color based on the selected color scheme. +Sets font family, aligns the content to the center, and makes the body span the entire viewport. +*/ +body { + background-color: var(--background-color); + font-family: "Kanit", sans-serif; + align-items: center; + height: 100vh; + width: 100vw; +} + +/* +Reusable utility class to create a horizontal flex container. +*/ +.flex-row { + display: flex; + flex-direction: row; +} + +/* +Reusable utility class to create a vertical flex container. +*/ +.flex-col { + display: flex; + flex-direction: column; +} + +/* ---------------------- Navbar Styles --------------------------------------------- */ + +/* +Styles the main navbar container. +- Makes the navbar stretch to 100% width. +- Uses flexbox to space the content evenly. +- Sets the background color and other text-related styles based on the theme. +*/ +.navbar { + justify-content: space-between; + align-items: center; + width: 100%; + background-color: var(--navbar-background-color); + font-weight: 600; + letter-spacing: 2px; + font-size: 30px; + padding: 0 20px; +} + +/* +Styles the left side of the navbar, typically containing the brand/logo. +Centers its content horizontally and adds a gap of 10px between items. +*/ +.navbar-brand { + align-items: center; + gap: 10px; +} + +/* +Styles anchor tags in the navbar. +Ensures the text decoration is removed and the color is set based on the current theme. +*/ +.navbar-brand a { + text-decoration: none; + color: var(--navbar-color); +} + +/* +Styles the logo on the right side of the navbar. +Sets the width to 3% of the container and ensures a minimum width of 45px. +*/ +.logo { + width: 3%; + min-width: 45px; +} + +/* +Styles the buttons for 'Favorites' and theme toggle on the right side of the navbar. +Centers the items in the container and adds a gap of 10px between them. +*/ +.favAndTheme-btn { + align-items: center; + gap: 10px; +} + +/* +Styles anchor links that lead to different pages. +Centers the text and removes the default text-decoration. +*/ +.link-to-different-page { + justify-content: center; + text-decoration: none; +} + +/* +Reusable button styles for all buttons. +Sets no border, custom padding, background color, and other properties like height, color, and font-size. +*/ +.btn { + border: none; + outline: none; + padding: 10px; + background-color: var(--navbar-color); + border-radius: 3px; + color: black; + height: 40px; + font-size: 16px; +} + +/* +Styles the theme toggle button specifically. +Sets the width of the button and the size of the icon inside. +*/ +#theme-btn { + width: 40px; +} + +#theme-btn i { + font-size: 20px; +} + +/* +Styles the 'Favorites' button specifically. +Sets a fixed width of 140px and larger font size for the button. +*/ +.fav-btn { + font-size: 16px; + width: 140px; + color: black; +} + +/* +Styles the favorite icon inside the button. +Sets its color to black. +*/ +.fav-icon { + color: black; +} + +/* +Styles the background image on the right side of the page. +Fixes its position and makes it 90% of the viewport height, with a slight blur applied. +*/ +.bg-right-side-img { + position: fixed; + right: 0; + bottom: 0; + height: 90%; + filter: blur(2px); +} + +/* ----------------------- Search Bar and Results Section ----------------------------- */ + +/* +Styles the search bar container. +Centers its contents and sets its height to 50px. +*/ +.search-bar-container { + display: flex; + align-items: center; + justify-content: center; + height: 50px; +} + +/* +Styles the search icon container (left part of the search bar). +- Sets its background and shadow based on the theme. +- Centers the icon inside with flexbox and gives it a height and width of 50px. +- Adds some margin at the top and rounds the left side of the container. +*/ +.search-icon-container { + background: var(--navbar-background-color); + box-shadow: 0px 0px 7px 0px var(--searchBar-box-shadow); + color: #b7b1a8; + height: 50px; + width: 50px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + margin-top: 50px; + border-radius: 10px 0 0 10px; +} + +/* +Styles the search bar input and its container. +Defines a minimum width for the input and custom padding, font size, shadow, and border radius. +*/ +.searchBar-items { + min-width: 40%; +} + +#search-bar { + background: var(--navbar-background-color); + color: #fff; + min-width: 550px; + height: 50px; + font-size: 20px; + border: none; + box-shadow: 0px 0px 7px 0px var(--searchBar-box-shadow); + border-radius: 0 10px 10px 0; + outline: none; + padding: 10px; + letter-spacing: 2px; + margin-top: 50px; + z-index: 1; +} + +/* +Styles the placeholder text inside the search input. +Gives it a lighter color and slightly larger font size. +*/ +input::placeholder { + color: #b7b1a8; + font-size: 20px; +} + +/* +Styles the search results container and individual items. +- Applies a blur to the backdrop. +- Sets a background color for each result and adjusts the size and appearance of the text. +*/ +#search-results { + min-width: 435px; + margin-top: 30px; + list-style: none; + z-index: 2; + backdrop-filter: blur(4px); +} + +#search-results li { + background: rgba(84, 148, 204, 0.639); + height: 100px; + color: white; + width: 100%; +} + +#search-results li .hero-name { + transition: all 0.2s ease; +} + +/* +Changes the color of the hero name to red when the search result is hovered. +*/ +#search-results li:hover .hero-name { + color: red; +} + +/* +Styles the container that wraps individual search results. +- Adds padding and adjusts spacing between elements with flexbox. +*/ +.single-search-result { + padding: 0 5px; + justify-content: space-between; + align-items: center; + gap: 10px; + border: 1px solid black; + width: inherit; +} + +/* +Styles links within the character information, ensuring they have no default decoration and are black. +*/ +.character-info { + text-decoration: none; + color: black; +} + +/* +Displays the hero information in a flex row and centers it vertically. +*/ +.hero-info { + display: flex; + align-items: center; +} + +/* +Increases the font size of the hero name to make it more prominent. +*/ +.hero-name { + font-size: 26px; +} + +/* +Styles the buttons within the search results. +Adds gap between them and internal padding. +*/ +.buttons { + gap: 5px; + padding: 0 10px; +} + +/* ------------------------------- Background Image Styles ----------------------------- */ + +/* +Styles the background image to cover 70% of the viewport width. +Fixes its position at the bottom of the page. +*/ +.bg-img { + width: 70%; + min-width: 600px; + position: fixed; + bottom: -30px; + left: 0; + right: 0; + margin: auto; +} + +/* ----------------- Toast Notifications for Favorites/Removal -------------------------- */ + +/* +Styles for toast notifications (e.g., 'Favorite added' or 'Removed'). +- Positions them at the bottom of the screen. +- Adjusts the font size, width, and color. +*/ +.fav-toast, +.remove-toast { + position: absolute; + bottom: 100px; + font-size: 24px; + width: 350px; + color: black; + letter-spacing: 1px; + padding: 10px 20px; + z-index: 2; + text-align: center; + left: 0; + right: 0; + margin: auto; + border-radius: 5px; +} + +/* +Toggles the visibility of the toast notifications. +They are shown or hidden based on the custom data attribute `data-visiblity`. +*/ +.fav-toast[data-visiblity="show"], +.remove-toast[data-visiblity="show"] { + display: block; +} + +.fav-toast[data-visiblity="hide"], +.remove-toast[data-visiblity="hide"] { + display: none; +} + +/* +Styles the 'Favorite added' toast. +Gives it a custom blue background and white shadow. +*/ +.fav-toast { + background-color: #4f99c7dc; + box-shadow: 0px 0px 3px 3px white; +} + +/* +Styles the 'Removed from favorites' toast. +Uses a purple background and white shadow. +*/ +.remove-toast { + background-color: hsla(281, 43%, 60%, 0.831); + box-shadow: 0px 0px 3px 3px white; +} + +/* ------------------- Media Queries for Small Screens ------------------------------- */ + +/* +Responsive styles for small screens (max-width: 576px). +Adjusts the width of the search bar and changes the navbar layout to a column layout with gaps. +*/ +@media screen and (max-width: 576px) { + #search-bar { + min-width: 435px; + } + + .navbar { + flex-direction: column; + gap: 10px; + padding: 10px 0; + } + + .navbar-brand { + display: flex; + flex-direction: column; + } + + .navbar-brand span { + margin-top: -35px; + } + + .logo { + width: 25%; + min-width: 45px; + } +} diff --git a/favorites.html b/favorites.html new file mode 100644 index 0000000..72076cf --- /dev/null +++ b/favorites.html @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + Superhero Hunter + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + +
+
+ + + Removed from + Favourites + + + + + + + + + + + + + diff --git a/images/background-light-img3.png b/images/background-light-img3.png new file mode 100644 index 0000000..c5ff0f1 Binary files /dev/null and b/images/background-light-img3.png differ diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000..daf0a00 Binary files /dev/null and b/images/icon.png differ diff --git a/images/mask.gif2.png b/images/mask.gif2.png new file mode 100644 index 0000000..e829646 Binary files /dev/null and b/images/mask.gif2.png differ diff --git a/images/mask.gif3.png b/images/mask.gif3.png new file mode 100644 index 0000000..f522fb6 Binary files /dev/null and b/images/mask.gif3.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..9777123 --- /dev/null +++ b/index.html @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + Superhero Hunter + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + + + +
+ + +
+ + + Added to Favourites + Removed from + Favourites + + + + + + + + + + + + diff --git a/js/favorites.js b/js/favorites.js new file mode 100644 index 0000000..70367f3 --- /dev/null +++ b/js/favorites.js @@ -0,0 +1,206 @@ +// Selecting the card container from the DOM using its ID +let cardContainer = document.getElementById("container"); + +// Event listener attached to the window object to execute when the page is fully loaded +window.addEventListener("load", function () { + // Retrieving the 'favouriteCharacters' array from localStorage + let favourites = localStorage.getItem("favouriteCharacters"); + + // If 'favouriteCharacters' is null, display a message indicating no characters are present + if (favourites == null) { + cardContainer.innerHTML = + '

No characters present in Favourites

'; + return; // Exit the function if no favourites are found + } + // If 'favourites' is not null, parse the JSON string to convert it into an array + else { + favourites = JSON.parse(this.localStorage.getItem("favouriteCharacters")); + } + + // If all characters have been deleted and the favourites array is empty, display a no characters message + if (favourites.length == 0) { + cardContainer.innerHTML = + '

No characters present in Favourites

'; + return; // Exit the function + } + + // Clear the card container before inserting new elements + cardContainer.innerHTML = ""; + + // Iterate through each character in the favourites array + favourites.forEach((character) => { + // Add a card for each character in the DOM with relevant information (image, name, ID, comics, etc.) + cardContainer.innerHTML += ` +
+ + ${character.name} + Id : ${character.id} + Comics : ${character.comics} + Series : ${character.series} + Stories : ${character.stories} + + + +
+ ${character.id} + ${character.name} + ${character.comics} + ${character.series} + ${character.stories} + ${character.description} + ${character.landscapeImage} + ${character.portraitImage} + ${character.squareImage} +
+ +
+ `; + }); + // Once all cards are inserted into the DOM, attach event listeners to the buttons + addEvent(); +}); + +// Function to attach event listeners to remove buttons and character info buttons after cards are rendered in DOM +function addEvent() { + // Attach click event listener to all remove buttons in the DOM + let removeBtn = document.querySelectorAll(".remove-btn"); + removeBtn.forEach((btn) => + btn.addEventListener("click", removeCharacterFromFavourites) + ); + + // Attach click event listener to all character info buttons for navigating to detailed info page + let characterInfo = document.querySelectorAll(".character-info"); + characterInfo.forEach((character) => + character.addEventListener("click", addInfoInLocalStorage) + ); +} + +// Function to remove a character from the favourites list +function removeCharacterFromFavourites() { + // Extract the character's ID from the card's DOM element to identify which character to remove + let idOfCharacterToBeDeleted = + this.parentElement.children[2].innerHTML.substring(5); // Get the character ID by trimming 'Id : ' prefix + + // Retrieve the current 'favouriteCharacters' array from localStorage + let favourites = JSON.parse(localStorage.getItem("favouriteCharacters")); + + // Retrieve the map of favourite character IDs to manage which IDs are in favourites + let favouritesCharacterIDs = new Map( + JSON.parse(localStorage.getItem("favouritesCharacterIDs")) + ); + + // Remove the character ID from the 'favouritesCharacterIDs' map + favouritesCharacterIDs.delete(`${idOfCharacterToBeDeleted}`); + + // Find and remove the character object from the favourites array based on the character ID + favourites.forEach(function (favourite, index) { + if (favourite.id == idOfCharacterToBeDeleted) { + // Remove the character from the array + favourites.splice(index, 1); + } + }); + + // If the favourites array is now empty, display the no characters message in the DOM + if (favourites.length == 0) { + cardContainer.innerHTML = + '

No characters present in Favourites

'; + } + + // Update the modified favourites array and IDs map back into localStorage + localStorage.setItem("favouriteCharacters", JSON.stringify(favourites)); + localStorage.setItem( + "favouritesCharacterIDs", + JSON.stringify([...favouritesCharacterIDs]) + ); + + // Remove the character card from the DOM + this.parentElement.remove(); + + // Display a "Removed from favourites" toast notification + document + .querySelector(".remove-toast") + .setAttribute("data-visiblity", "show"); + + // Hide the toast notification after 1 second + setTimeout(function () { + document + .querySelector(".remove-toast") + .setAttribute("data-visiblity", "hide"); + }, 1000); +} + +// Function to store selected character info into localStorage when the user clicks "More Info" +function addInfoInLocalStorage() { + // Extract detailed information of the selected character and store it in 'heroInfo' object + let heroInfo = { + name: this.parentElement.children[7].children[1].innerHTML, + description: this.parentElement.children[7].children[5].innerHTML, + comics: this.parentElement.children[7].children[2].innerHTML, + series: this.parentElement.children[7].children[3].innerHTML, + stories: this.parentElement.children[7].children[4].innerHTML, + portraitImage: this.parentElement.children[7].children[7].innerHTML, + id: this.parentElement.children[7].children[0].innerHTML, + landscapeImage: this.parentElement.children[7].children[6].innerHTML, + }; + + // Store the 'heroInfo' object in localStorage for later use on the info page + localStorage.setItem("heroInfo", JSON.stringify(heroInfo)); +} + +/*------------------------------------- Theme Changing ------------------------------------------------- */ + +// Select the theme toggle button from the DOM using its ID +let themeButton = document.getElementById("theme-btn"); + +// Add an event listener to handle theme changes when the user clicks the theme button +themeButton.addEventListener("click", themeChanger); + +// IIFE (Immediately Invoked Function Expression) to apply the previously set theme from localStorage when the page loads +(function () { + // Retrieve the current theme from localStorage, or default to 'light' if no theme is set + let currentTheme = localStorage.getItem("theme"); + if (currentTheme == null) { + root.setAttribute("color-scheme", "light"); // Set default theme to light + themeButton.innerHTML = ``; // Change button icon to moon for light theme + themeButton.style.backgroundColor = "#0b8e99"; // Set background color for light theme + localStorage.setItem("theme", "light"); // Save the light theme in localStorage + return; + } + + // Apply the stored theme based on the current theme value + switch (currentTheme) { + case "light": + root.setAttribute("color-scheme", "light"); // Apply light theme + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#0b8e99"; + break; + case "dark": + root.setAttribute("color-scheme", "dark"); // Apply dark theme + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#a82fb3"; + themeButton.childNodes[0].style.color = "black"; + break; + } +})(); + +// Function to toggle between light and dark themes when the theme button is clicked +function themeChanger() { + let root = document.getElementById("root"); + + // Toggle to dark theme if currently in light theme + if (root.getAttribute("color-scheme") == "light") { + root.setAttribute("color-scheme", "dark"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#a82fb3"; + themeButton.childNodes[0].style.color = "black"; + localStorage.setItem("theme", "dark"); // Save the dark theme in localStorage + + // Toggle to light theme if currently in dark theme + } else if (root.getAttribute("color-scheme") == "dark") { + root.setAttribute("color-scheme", "light"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#0b8e99"; + themeButton.childNodes[0].style.color = "white"; + localStorage.setItem("theme", "light"); // Save the light theme in localStorage + } +} diff --git a/js/more-info.js b/js/more-info.js new file mode 100644 index 0000000..17c5cd2 --- /dev/null +++ b/js/more-info.js @@ -0,0 +1,292 @@ +// Selecting the elements from the DOM for later use +let info = document.getElementById("info-container"); +let title = document.getElementById("page-title"); + +// Retrieving the heroInfo object from localStorage, which was saved when the user clicked on "More Info". +// This data will be used to display hero details on the page. +let heroInfo = JSON.parse(localStorage.getItem("heroInfo")); + +// Dynamically updating the page title with the hero's name followed by " | SH" (presumably for "SuperHero") +title.innerHTML = heroInfo.name + " | SH"; + +// Adding an event listener for when the page is fully loaded +window.addEventListener("load", function () { + // Retrieving the IDs of the user's favorite characters from localStorage + let favouritesCharacterIDs = localStorage.getItem("favouritesCharacterIDs"); + + // If no favorites exist, initialize an empty Map to store IDs + if (favouritesCharacterIDs == null) { + favouritesCharacterIDs = new Map(); + } else if (favouritesCharacterIDs != null) { + // If favorites do exist, parse them from a JSON string and convert back to a Map + favouritesCharacterIDs = new Map( + JSON.parse(localStorage.getItem("favouritesCharacterIDs")) + ); + } + + // Adding the hero's information to the page dynamically by manipulating the innerHTML + info.innerHTML = ` +
${heroInfo.name}
+
+ + +
+
+ ID:${heroInfo.id} +
+
+ Comics:${heroInfo.comics} +
+
+ Series:${heroInfo.series} +
+
+ Stories:${heroInfo.stories} +
+
+
+
+ Description: +

${ + heroInfo.description != "" + ? heroInfo.description + : "No Description Available" + }

+
+
+ ${heroInfo.name} + ${heroInfo.portraitImage} + ${heroInfo.landscapeImage} + ${heroInfo.id} + ${heroInfo.comics} + ${heroInfo.series} + ${heroInfo.stories} + ${heroInfo.squareImage} + ${heroInfo.description} +
+ ' + } + + `; + // Call the function that attaches event listeners to the dynamic content after it's been rendered + addEvent(); +}); + +// This event listener handles image switching based on screen size (responsive design) +// It switches between portrait and landscape images depending on the screen width +window.addEventListener("resize", function () { + let portraitImage = document.getElementById("portraitImage"); + let landscapeImage = document.getElementById("landscapeImage"); + + // If the screen width is less than 678px, display the landscape image and hide the portrait image + if (document.body.clientWidth < 678) { + portraitImage.style.display = "none"; + landscapeImage.style.display = "block"; + } else { + // If the screen width is greater than or equal to 678px, show the portrait image and hide the landscape image + landscapeImage.style.display = "none"; + portraitImage.style.display = "block"; + } +}); + +// Function that gets called after the content has been loaded +// It adds a click event listener to the "Add to Favourites" button +function addEvent() { + let favouriteButton = document.querySelector(".add-to-fav-btn"); + favouriteButton.addEventListener("click", addToFavourites); +} + +// This function handles adding or removing the hero from the user's favorites list +function addToFavourites() { + // If the button currently says "Add to Favourites" + if ( + this.innerHTML == + '   Add to Favourites' + ) { + // Create an object that stores the relevant hero information + let heroInfo = { + name: this.parentElement.children[3].children[0].innerHTML, + description: this.parentElement.children[3].children[8].innerHTML, + comics: this.parentElement.children[3].children[4].innerHTML, + series: this.parentElement.children[3].children[5].innerHTML, + stories: this.parentElement.children[3].children[6].innerHTML, + portraitImage: this.parentElement.children[3].children[1].innerHTML, + id: this.parentElement.children[3].children[3].innerHTML, + landscapeImage: this.parentElement.children[3].children[2].innerHTML, + squareImage: this.parentElement.children[3].children[7].innerHTML, + }; + + // Retrieve the array of favorite characters from localStorage + let favouritesArray = localStorage.getItem("favouriteCharacters"); + + // If there are no favorite characters yet, initialize the array as empty + if (favouritesArray == null) { + favouritesArray = []; + } else { + // If the array exists, parse it back into an array + favouritesArray = JSON.parse(localStorage.getItem("favouriteCharacters")); + } + + // Retrieve the map of favorite character IDs from localStorage + let favouritesCharacterIDs = localStorage.getItem("favouritesCharacterIDs"); + + if (favouritesCharacterIDs == null) { + // If the map doesn't exist, initialize it as an empty Map + favouritesCharacterIDs = new Map(); + } else { + // Parse and convert it back to a Map object + favouritesCharacterIDs = new Map( + JSON.parse(localStorage.getItem("favouritesCharacterIDs")) + ); + } + + // Add the current hero's ID to the map + favouritesCharacterIDs.set(heroInfo.id, true); + + // Add the hero's info to the favorites array + favouritesArray.push(heroInfo); + + // Save the updated map and array back to localStorage + localStorage.setItem( + "favouritesCharacterIDs", + JSON.stringify([...favouritesCharacterIDs]) + ); + localStorage.setItem( + "favouriteCharacters", + JSON.stringify(favouritesArray) + ); + + // Change the button text to indicate the hero is now a favorite + this.innerHTML = + '   Remove from Favourites'; + + // Show a toast message indicating the hero was added to favorites + document.querySelector(".fav-toast").setAttribute("data-visiblity", "show"); + + // Hide the toast message after 1 second + setTimeout(function () { + document + .querySelector(".fav-toast") + .setAttribute("data-visiblity", "hide"); + }, 1000); + } + // If the button currently says "Remove from Favourites" + else { + // Get the ID of the hero to remove from favorites + let idOfCharacterToBeRemoveFromFavourites = + this.parentElement.children[3].children[3].innerHTML; + + // Retrieve the favorites array and map from localStorage + let favouritesArray = JSON.parse( + localStorage.getItem("favouriteCharacters") + ); + let favouritesCharacterIDs = new Map( + JSON.parse(localStorage.getItem("favouritesCharacterIDs")) + ); + + // Create a new array that will contain all characters except the one being removed + let newFavouritesArray = []; + + // Remove the character's ID from the map + favouritesCharacterIDs.delete(`${idOfCharacterToBeRemoveFromFavourites}`); + + // Filter the favorites array to remove the selected character + favouritesArray.forEach((favourite) => { + if (idOfCharacterToBeRemoveFromFavourites != favourite.id) { + newFavouritesArray.push(favourite); + } + }); + + // Save the updated favorites array and map back to localStorage + localStorage.setItem( + "favouriteCharacters", + JSON.stringify(newFavouritesArray) + ); + localStorage.setItem( + "favouritesCharacterIDs", + JSON.stringify([...favouritesCharacterIDs]) + ); + + // Change the button text back to "Add to Favourites" + this.innerHTML = + '   Add to Favourites'; + + // Show a toast message indicating the hero was removed from favorites + document + .querySelector(".remove-toast") + .setAttribute("data-visiblity", "show"); + + // Hide the toast message after 1 second + setTimeout(function () { + document + .querySelector(".remove-toast") + .setAttribute("data-visiblity", "hide"); + }, 1000); + } +} + +/*----------------------------------------- Theme Changing ------------------------------------------------- */ + +// Selecting the theme toggle button +let themeButton = document.getElementById("theme-btn"); + +// Add an event listener to handle theme switching when the button is clicked +themeButton.addEventListener("click", themeChanger); + +// Immediately Invoked Function Expression (IIFE) that checks localStorage for the saved theme and applies it +(function () { + let currentTheme = localStorage.getItem("theme"); + + // If no theme is stored, set the default theme to light + if (currentTheme == null) { + root.setAttribute("color-scheme", "light"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#0b8e99"; + localStorage.setItem("theme", "light"); + return; + } + + // Apply the stored theme and adjust the button accordingly + switch (currentTheme) { + case "light": + root.setAttribute("color-scheme", "light"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#0b8e99"; + break; + case "dark": + root.setAttribute("color-scheme", "dark"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#a82fb3"; + themeButton.childNodes[0].style.color = "black"; + break; + } +})(); + +// Function that handles theme switching when the user clicks the theme button +function themeChanger() { + let root = document.getElementById("root"); + + // If the current theme is light, switch to dark theme + if (root.getAttribute("color-scheme") == "light") { + root.setAttribute("color-scheme", "dark"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#a82fb3"; + themeButton.childNodes[0].style.color = "black"; + localStorage.setItem("theme", "dark"); + } + // If the current theme is dark, switch back to light theme + else if (root.getAttribute("color-scheme") == "dark") { + root.setAttribute("color-scheme", "light"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#0b8e99"; + themeButton.childNodes[0].style.color = "white"; + localStorage.setItem("theme", "light"); + } +} diff --git a/js/script.js b/js/script.js new file mode 100644 index 0000000..87f6486 --- /dev/null +++ b/js/script.js @@ -0,0 +1,392 @@ +// Public key for Marvel API authentication +// Satyam Public key +// 8ee65ec0b199279c5b89089cf9c56021 + +// Private key for Marvel API authentication +// Satyam Private key +// da14b55a0c1576b13badd1fd8429e7be353f620a + +// Combined hash for API request authentication +// Satyam hash +// 1da14b55a0c1576b13badd1fd8429e7be353f620a8ee65ec0b199279c5b89089cf9c56021 +// MD5 hash of the combined hash +// md5(hash) = f30452873be9f4740aaebfcdd00c2d42 + +// API endpoint for Marvel characters +// satyam: https://gateway.marvel.com/v1/public/characters?apikey=8ee65ec0b199279c5b89089cf9c56021 + +/*------------ Selecting the element from DOM ----------------------------------------*/ + +// Select the search bar input element from the DOM by its ID 'search-bar' +let searchBar = document.getElementById("search-bar"); + +// Select the unordered list element that will display the search results by its ID 'search-results' +let searchResults = document.getElementById("search-results"); + +// Adding event listener to the search bar for input changes +searchBar.addEventListener("input", () => searchHeros(searchBar.value)); + +// Function to make an API call to search for heroes +async function searchHeros(textSearched) { + // Define the public and private keys for API access + const PUBLIC_KEY = "8ee65ec0b199279c5b89089cf9c56021"; + const PRIVATE_KEY = "da14b55a0c1576b13badd1fd8429e7be353f620a"; + + // Generate a timestamp for the request + let ts = new Date().getTime(); + // Create a hash using MD5 for the API request + let hash = CryptoJS.MD5(ts + PRIVATE_KEY + PUBLIC_KEY).toString(); + + // If the search bar is empty, clear the displayed results + if (textSearched.length == 0) { + searchResults.innerHTML = ``; + return; + } + + // Fetch data from the Marvel API based on the search input + await fetch( + `https://gateway.marvel.com/v1/public/characters?nameStartsWith=${textSearched}&ts=${ts}&apikey=${PUBLIC_KEY}&hash=${hash}` + ) + .then((res) => res.json()) // Convert the response to JSON format + .then((data) => showSearchedResults(data.data.results)); // Pass the results to the function to display them +} + +// Function for displaying the searched results in the DOM +// Accepts an array of searched heroes as an argument +function showSearchedResults(searchedHero) { + // Retrieve the IDs of characters that are added to favourites from localStorage + let favouritesCharacterIDs = localStorage.getItem("favouritesCharacterIDs"); + + if (favouritesCharacterIDs == null) { + // Initialize it with an empty map if no favourites are found + favouritesCharacterIDs = new Map(); + } else if (favouritesCharacterIDs != null) { + // Parse and convert to map if favourites IDs exist + favouritesCharacterIDs = new Map( + JSON.parse(localStorage.getItem("favouritesCharacterIDs")) + ); + } + + // Clear previous search results + searchResults.innerHTML = ``; + // Initialize a count to limit displayed results + let count = 1; + + // Iterate over the searched heroes array + for (const key in searchedHero) { + // Display only the first 5 results + if (count <= 5) { + // Get a single hero object from the searched results + let hero = searchedHero[key]; + // Append the hero information to the search results in the DOM + searchResults.innerHTML += ` +
  • +
    + + +
    +
    + ' + } +
    +
    + ${hero.name} + ${hero.description} + ${hero.comics.available} + ${hero.series.available} + ${hero.stories.available} + ${ + hero.thumbnail.path + + "/portrait_uncanny." + + hero.thumbnail.extension + } + ${hero.id} + ${ + hero.thumbnail.path + + "/landscape_incredible." + + hero.thumbnail.extension + } + ${ + hero.thumbnail.path + + "/standard_fantastic." + + hero.thumbnail.extension + } +
    +
  • + `; + } + count++; + } + // Attach event listeners to the buttons after they are inserted into the DOM + events(); +} + +// Function for attaching event listeners to buttons +function events() { + // Select all buttons that add characters to favourites + let favouriteButton = document.querySelectorAll(".add-to-fav-btn"); + // Add click event listener to each button + favouriteButton.forEach((btn) => + btn.addEventListener("click", addToFavourites) + ); + + // Select all character info links + let characterInfo = document.querySelectorAll(".character-info"); + // Add click event listener to each character info link + characterInfo.forEach((character) => + character.addEventListener("click", addInfoInLocalStorage) + ); +} + +// Function invoked when "Add to Favourites" or "Remove from Favourites" button is clicked +// The appropriate action is taken according to the button clicked +function addToFavourites() { + // Check if the button is for adding to favourites + if ( + this.innerHTML == + '   Add to Favourites' + ) { + // Create an object containing relevant info of the hero to add to favourites + let heroInfo = { + name: this.parentElement.parentElement.children[2].children[0].innerHTML, + description: + this.parentElement.parentElement.children[2].children[1].innerHTML, + comics: + this.parentElement.parentElement.children[2].children[2].innerHTML, + series: + this.parentElement.parentElement.children[2].children[3].innerHTML, + stories: + this.parentElement.parentElement.children[2].children[4].innerHTML, + portraitImage: + this.parentElement.parentElement.children[2].children[5].innerHTML, + id: this.parentElement.parentElement.children[2].children[6].innerHTML, + landscapeImage: + this.parentElement.parentElement.children[2].children[7].innerHTML, + squareImage: + this.parentElement.parentElement.children[2].children[8].innerHTML, + }; + + // Retrieve the favourites array from localStorage + let favouritesArray = localStorage.getItem("favouriteCharacters"); + + // If favouritesArray is null (first time visiting), create a new array + if (favouritesArray == null) { + favouritesArray = []; + } else { + // Otherwise, parse it into an array + favouritesArray = JSON.parse(localStorage.getItem("favouriteCharacters")); + } + + // Retrieve the favourites character IDs from localStorage + let favouritesCharacterIDs = localStorage.getItem("favouritesCharacterIDs"); + + if (favouritesCharacterIDs == null) { + // Initialize it with an empty map if no IDs are found + favouritesCharacterIDs = new Map(); + } else { + // Parse and convert it into a map if IDs exist + favouritesCharacterIDs = new Map( + JSON.parse(localStorage.getItem("favouritesCharacterIDs")) + ); + } + + // Add the character ID to the favouritesCharacterIDs map + favouritesCharacterIDs.set(heroInfo.id, true); + + // Add the heroInfo object to favouritesArray + favouritesArray.push(heroInfo); + + // Store the updated favouritesCharacterIDs map in localStorage + localStorage.setItem( + "favouritesCharacterIDs", + JSON.stringify([...favouritesCharacterIDs]) + ); + // Store the updated favouritesCharacters array in localStorage + localStorage.setItem( + "favouriteCharacters", + JSON.stringify(favouritesArray) + ); + + // Change the button text to "Remove from Favourites" + this.innerHTML = + '   Remove from Favourites'; + + // Show a toast notification indicating the character has been added to favourites + document.querySelector(".fav-toast").setAttribute("data-visiblity", "show"); + // Hide the toast notification after 1 second + setTimeout(function () { + document + .querySelector(".fav-toast") + .setAttribute("data-visiblity", "hide"); + }, 1000); + } else { + // If the button was clicked for removing from favourites + let heroID = + this.parentElement.parentElement.children[2].children[6].innerHTML; + + // Retrieve the favourites array from localStorage + let favouritesArray = localStorage.getItem("favouriteCharacters"); + // Parse the array if it exists + if (favouritesArray) { + favouritesArray = JSON.parse(favouritesArray); + } + + // Filter out the hero being removed from the favouritesArray + favouritesArray = favouritesArray.filter((hero) => hero.id !== heroID); + + // Retrieve the favourites character IDs from localStorage + let favouritesCharacterIDs = localStorage.getItem("favouritesCharacterIDs"); + // Parse the IDs into a map + favouritesCharacterIDs = new Map(JSON.parse(favouritesCharacterIDs)); + + // Remove the character ID from the favouritesCharacterIDs map + favouritesCharacterIDs.delete(heroID); + + // Store the updated favouritesCharacterIDs map in localStorage + localStorage.setItem( + "favouritesCharacterIDs", + JSON.stringify([...favouritesCharacterIDs]) + ); + // Store the updated favouritesCharacters array in localStorage + localStorage.setItem( + "favouriteCharacters", + JSON.stringify(favouritesArray) + ); + + // Change the button text to "Add to Favourites" + this.innerHTML = + '   Add to Favourites'; + + // Show a toast notification indicating the character has been removed from favourites + document.querySelector(".fav-toast").setAttribute("data-visiblity", "show"); + // Hide the toast notification after 1 second + setTimeout(function () { + document + .querySelector(".fav-toast") + .setAttribute("data-visiblity", "hide"); + }, 1000); + } +} + +// Function which stores the info object of the character for which the user wants to see the info +function addInfoInLocalStorage() { + // This function stores the character's data in localStorage. + // When the user clicks on the info button, this data is saved so that + // when the info page is opened, it can fetch the heroInfo and display the data. + + let heroInfo = { + // Retrieve the character's name from the DOM structure + name: this.parentElement.parentElement.parentElement.children[2].children[0] + .innerHTML, + // Retrieve the character's description + description: + this.parentElement.parentElement.parentElement.children[2].children[1] + .innerHTML, + // Retrieve the number of comics the character appears in + comics: + this.parentElement.parentElement.parentElement.children[2].children[2] + .innerHTML, + // Retrieve the number of series the character appears in + series: + this.parentElement.parentElement.parentElement.children[2].children[3] + .innerHTML, + // Retrieve the number of stories the character appears in + stories: + this.parentElement.parentElement.parentElement.children[2].children[4] + .innerHTML, + // Retrieve the path for the character's portrait image + portraitImage: + this.parentElement.parentElement.parentElement.children[2].children[5] + .innerHTML, + // Retrieve the character's ID + id: this.parentElement.parentElement.parentElement.children[2].children[6] + .innerHTML, + // Retrieve the path for the character's landscape image + landscapeImage: + this.parentElement.parentElement.parentElement.children[2].children[7] + .innerHTML, + // Retrieve the path for the character's square image + squareImage: + this.parentElement.parentElement.parentElement.children[2].children[8] + .innerHTML, + }; + + // Store the heroInfo object in localStorage as a JSON string + localStorage.setItem("heroInfo", JSON.stringify(heroInfo)); +} + +/*-------------------- Theme Changing ------------------------------------- */ + +// Selection of the theme button from the DOM by its ID 'theme-btn' +let themeButton = document.getElementById("theme-btn"); + +// Add a click event listener to the theme button that triggers the themeChanger function +themeButton.addEventListener("click", themeChanger); + +// Immediately Invoked Function Expression (IIFE) to check localStorage +// and apply the previously set theme when the page loads +(function () { + // Retrieve the current theme from localStorage + let currentTheme = localStorage.getItem("theme"); + if (currentTheme == null) { + // If no theme is set, default to light theme + root.setAttribute("color-scheme", "light"); + // Update the theme button icon and style + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#0b8e99"; + // Store the default theme in localStorage + localStorage.setItem("theme", "light"); + return; // Exit the function as the default theme is set + } + + // Apply the theme based on the value retrieved from localStorage + switch (currentTheme) { + case "light": + // Set to light theme and update button appearance + root.setAttribute("color-scheme", "light"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#0b8e99"; + break; // Exit switch statement + case "dark": + // Set to dark theme and update button appearance + root.setAttribute("color-scheme", "dark"); + themeButton.innerHTML = ``; + themeButton.style.backgroundColor = "#a82fb3"; + themeButton.childNodes[0].style.color = "black"; // Set icon color + break; // Exit switch statement + } +})(); + +// Function for handling theme button changes +function themeChanger() { + // Select the root element from the DOM + let root = document.getElementById("root"); + + // Check the current color scheme of the root element + if (root.getAttribute("color-scheme") == "light") { + // If the current theme is light, switch to dark theme + root.setAttribute("color-scheme", "dark"); + themeButton.innerHTML = ``; // Update button icon + themeButton.style.backgroundColor = "#a82fb3"; // Update button background color + themeButton.childNodes[0].style.color = "black"; // Change icon color + localStorage.setItem("theme", "dark"); // Save the dark theme in localStorage + } else if (root.getAttribute("color-scheme") == "dark") { + // If the current theme is dark, switch to light theme + root.setAttribute("color-scheme", "light"); + themeButton.innerHTML = ``; // Update button icon + themeButton.style.backgroundColor = "#0b8e99"; // Update button background color + themeButton.childNodes[0].style.color = "white"; // Change icon color + localStorage.setItem("theme", "light"); // Save the light theme in localStorage + } +} diff --git a/more-info.html b/more-info.html new file mode 100644 index 0000000..15d29ca --- /dev/null +++ b/more-info.html @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + Superhero Hunter + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + +
    +
    + + + Added to Favourites + Removed from + Favourites + + + + + + + + + + + +