├── README.md ├── accordion ├── index.html ├── script.js └── style.css ├── booklist-app ├── README.md ├── app-oo.js ├── app.js ├── class.js ├── index.html └── style.css ├── bookmarker ├── index.html ├── script.js └── style.css ├── carousel ├── img │ ├── slide-1.jpeg │ ├── slide-2.jpeg │ └── slide-3.jpeg ├── index.html ├── script.js └── style.css ├── dark-mode-toggle ├── index.html ├── script.js └── style.css ├── drag-n-drop ├── deck.png ├── index.html ├── script.js └── style.css ├── filterable-list ├── filterable-list-v2 │ ├── index.html │ ├── script.js │ └── style.css ├── index.html └── script.js ├── github-finder-app ├── README.md ├── app.js ├── github.js ├── index.html ├── style.css └── ui.js ├── math-game ├── index.html ├── script.js └── style.css ├── modal ├── index.html ├── script.js └── style.css ├── number-guesser ├── index.html ├── script.js └── style.css ├── page-coming-soon-countdown ├── img │ ├── background.jpg │ ├── favicon.ico │ ├── icon.png │ └── logo.png ├── index.html ├── script.js └── style.css ├── pagination ├── index.html ├── script.js └── style.css ├── password-generator ├── index.html ├── script.js └── style.css ├── pet-finder ├── index.html ├── pet-finder-v2 │ ├── index.html │ ├── script.js │ └── style.css ├── script.js └── style.css ├── quiz-app ├── index.html ├── script.js └── style.css ├── quotes-machine ├── .prettierignore ├── index.html ├── script.js └── style.css ├── scroll-progress ├── index.html ├── script.js └── style.css ├── social-media-counter ├── index.html ├── script.js └── style.css ├── speed-typing-game ├── README.md ├── index.html └── script.js ├── tags-drag-and-drop ├── index.html ├── script.js └── style.css ├── text-to-speech ├── .vscode │ └── settings.json ├── index.html ├── script.js ├── speakerIcon.svg ├── styles.css └── waveIcon.svg ├── todos ├── README.md ├── index.html ├── script.js └── style.css ├── typing-effect ├── index.html ├── script.js └── style.css ├── typing-suggestions ├── index.html ├── script.js └── style.css ├── weather-app ├── README.md ├── app.js ├── img │ └── sky.jpg ├── index.html └── style.css ├── weekly-budget-app ├── README.md ├── index.html ├── script.js └── style.css └── weight-converter ├── index.html ├── script.js └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # Vanilla Javascript Projects 2 | 3 | > Click on the links below to access each App 4 | 5 | 1. [toDos](https://aman-maharshi.github.io/vanilla-js/todos/) 6 | 1. [Quiz App](https://aman-maharshi.github.io/vanilla-js/quiz-app/) 7 | 1. [Text to Speech](https://aman-maharshi.github.io/vanilla-js/text-to-speech/) 8 | 1. [Weekly Budget Tracker](https://aman-maharshi.github.io/vanilla-js/weekly-budget-app/) 9 | 1. [Tags Drag and Drop](https://aman-maharshi.github.io/vanilla-js/tags-drag-and-drop/) 10 | 1. [Password Generator](https://aman-maharshi.github.io/vanilla-js/password-generator/) 11 | 1. [Quotes Machine](https://aman-maharshi.github.io/vanilla-js/quotes-machine/) 12 | 1. [Website Bookmarker](https://aman-maharshi.github.io/vanilla-js/bookmarker/) 13 | 1. [Booklist App](https://aman-maharshi.github.io/vanilla-js/booklist-app/) 14 | 1. [Typing Effect](https://aman-maharshi.github.io/vanilla-js/typing-effect/) 15 | 1. [Weight Converter](https://aman-maharshi.github.io/vanilla-js/weight-converter/) 16 | 17 | #### API Projects - Apps using data from external APIs 18 | 19 | 1. [Weather App](https://aman-maharshi.github.io/vanilla-js/weather-app/) 20 | 1. [Pet Finder](https://aman-maharshi.github.io/vanilla-js/pet-finder/) 21 | 1. [Pagination](https://aman-maharshi.github.io/vanilla-js/pagination/) 22 | 1. [Typing Suggestions](https://aman-maharshi.github.io/vanilla-js/typing-suggestions/) 23 | 24 | #### Web Components - Elements used on many websites 25 | 26 | 1. [Image Slider](https://aman-maharshi.github.io/vanilla-js/carousel/) 27 | 1. [Filterable List](https://aman-maharshi.github.io/vanilla-js/filterable-list/) 28 | 1. [Accordion](https://aman-maharshi.github.io/vanilla-js/accordion/) 29 | 1. [Light/Dark Theme Switcher](https://aman-maharshi.github.io/vanilla-js/dark-mode-toggle/) 30 | 1. [Scroll Progress Indicator](https://aman-maharshi.github.io/vanilla-js/scroll-progress/) 31 | 1. [Social Media Counter](https://aman-maharshi.github.io/vanilla-js/social-media-counter/) 32 | 1. [Page Coming Soon](https://aman-maharshi.github.io/vanilla-js/page-coming-soon-countdown/) 33 | 1. [Drag n Drop](https://aman-maharshi.github.io/vanilla-js/drag-n-drop/) 34 | 1. [Popup Modal](https://aman-maharshi.github.io/vanilla-js/modal/) 35 | 36 | #### Games 37 | 38 | 1. [Speed Typing Lite](https://aman-maharshi.github.io/vanilla-js/speed-typing-game/) 39 | 1. [Number Guesser](https://aman-maharshi.github.io/vanilla-js/number-guesser/) 40 | -------------------------------------------------------------------------------- /accordion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JavaScript Questions 8 | 9 | 10 | 11 |

JavaScript Questions

12 | 13 |
14 |
15 |
16 | What is a closure, and how/why would you use one? 17 |
18 |
19 |
20 | A closure is the combination of a function and the lexical environment within which that function was declared. The word "lexical" refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available. Closures are functions that have access to the outer (enclosing) function's variables—scope chain even after the outer function has returned. 21 |
22 |
23 |
24 | 25 |
26 |
27 | Explain event delegation 28 |
29 |
30 |
31 | Event delegation is a technique involving adding event listeners to a parent element instead of adding them to the descendant elements. The listener will fire whenever the event is triggered on the descendant elements due to event bubbling up the DOM. The benefits of this technique are: 32 |
    33 |
  • Memory footprint goes down because only one single handler is needed on the parent element, rather than having to attach event handlers on each descendant.
  • 34 |
  • There is no need to unbind the handler from elements that are removed and to bind the event for new elements.
  • 35 |
36 |
37 |
38 |
39 | 40 |
41 |
What's the difference between .call and .apply?
42 |
43 |
Both .call and .apply are used to invoke functions and the first parameter will be used as the value of this within the function. However, .call takes in comma-separated arguments as the next arguments while .apply takes in an array of arguments as the next argument. An easy way to remember this is C for call and comma-separated and A for apply and an array of arguments.
44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /accordion/script.js: -------------------------------------------------------------------------------- 1 | const accordionHeaders = document.querySelectorAll(".accordion-item-header") 2 | const accordionBodies = document.querySelectorAll(".accordion-item-body") 3 | 4 | accordionHeaders.forEach(item => { 5 | item.addEventListener("click", e => { 6 | itemHeader = e.target 7 | itemBody = e.target.nextElementSibling 8 | itemHeader.classList.toggle("active") 9 | itemBody.classList.toggle("hide") 10 | }) 11 | }) 12 | 13 | // hiding all on page load / refresh 14 | document.addEventListener("DOMContentLoaded", () => { 15 | accordionBodies.forEach(item => { 16 | item.classList.add("hide") 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /accordion/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&display=swap"); 2 | * { 3 | box-sizing: border-box; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | body { 8 | font-family: "Raleway", sans-serif; 9 | background: #303952; 10 | background: linear-gradient(to bottom, #323232 0%, #3f3f3f 40%, #1c1c1c 150%), linear-gradient(to top, rgba(255, 255, 255, 0.4) 0%, rgba(0, 0, 0, 0.25) 200%); 11 | color: white; 12 | font-size: 1.1rem; 13 | height: 100vh; 14 | } 15 | h1 { 16 | text-align: center; 17 | margin: 2rem 0; 18 | font-size: 2.5rem; 19 | background: linear-gradient(to left, #00a7ff 0%, #00bc3f 100%); 20 | background-clip: text; 21 | -webkit-background-clip: text; 22 | -webkit-text-fill-color: transparent; 23 | } 24 | ul { 25 | margin-left: 1rem; 26 | } 27 | li { 28 | margin: 0.5rem 0; 29 | } 30 | span { 31 | padding: 2px 4px; 32 | border-radius: 5px; 33 | background: rgb(223, 223, 223); 34 | } 35 | .accordion { 36 | width: 90%; 37 | max-width: 1000px; 38 | margin: 2rem auto; 39 | } 40 | .accordion-item { 41 | background: white; 42 | color: #111; 43 | margin: 1rem 0; 44 | border-radius: 0.5rem; 45 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.25); 46 | } 47 | .accordion-item-header { 48 | padding: 1rem; 49 | font-weight: bold; 50 | cursor: pointer; 51 | position: relative; 52 | } 53 | .accordion-item-header::after { 54 | content: "+"; 55 | position: absolute; 56 | right: 1rem; 57 | top: 0; 58 | font-size: 2.5rem; 59 | font-weight: 400; 60 | } 61 | .accordion-item-header.active::after { 62 | content: "-"; 63 | } 64 | .accordioon-item-body { 65 | transform: all 0.3s ease; 66 | } 67 | .accordion-item-body.hide { 68 | display: none; 69 | } 70 | .accordion-item-body-content { 71 | padding: 1rem; 72 | line-height: 1.5; 73 | } 74 | -------------------------------------------------------------------------------- /booklist-app/README.md: -------------------------------------------------------------------------------- 1 | > To use the App, click on the link Below 2 | 3 | # [My Books](https://aman-maharshi.github.io/vanilla-js/booklist-app/) 4 | 5 | ### An app to keep track of your reading 6 | 7 | * Add books to the list 8 | * Store book details to the browser's Local Storage so the list is persistent even when the page is reloded 9 | * When an entry is removed from the list it should also be cleared from the Local Storage -------------------------------------------------------------------------------- /booklist-app/app-oo.js: -------------------------------------------------------------------------------- 1 | /* 2 | ISSUE: After removing a item from the table, when we add a next book, it shows undefined for the bookTitle!!! 3 | */ 4 | // CLASSES 5 | class Book { 6 | constructor(title, author) { 7 | this.title = title; 8 | this.author = author; 9 | } 10 | } 11 | 12 | class UI { 13 | addBookToTable(bookObject) { 14 | let tr = document.createElement('tr'); 15 | let row = `${bookObject.title} 16 | ${bookObject.author} 17 | X 18 | ` 19 | tr.innerHTML = row; 20 | tableBody.appendChild(tr); 21 | form.reset(); 22 | } 23 | showAlert(message, nameOfClass) { 24 | alertDiv.style.display = 'block'; 25 | alertDiv.textContent = message; 26 | alertDiv.className = nameOfClass; 27 | setTimeout(() => { 28 | alertDiv.style.display = 'none'; 29 | }, 1500) 30 | } 31 | removeItem(targetEvent) { 32 | if(targetEvent.classList.contains('btn-danger')) { 33 | targetEvent.parentElement.parentElement.remove(); 34 | updateInterface.showAlert('Book Removed!', 'alert-warning'); 35 | } 36 | } 37 | } 38 | const updateInterface = new UI; 39 | 40 | class Storage { 41 | static getBooks() { 42 | const details = localStorage.getItem('books'); 43 | if(details === null) { 44 | localStorage.setItem('books', '[]'); 45 | } 46 | return JSON.parse(localStorage.getItem('books')); 47 | } 48 | static addBook(book) { 49 | const booksArray = Storage.getBooks(); 50 | booksArray.push(book); 51 | localStorage.setItem('books', JSON.stringify(booksArray)); 52 | } 53 | static displayBooks() { 54 | const booksArray = Storage.getBooks(); 55 | if (booksArray.length != 0) { 56 | booksArray.forEach((obj) => { 57 | updateInterface.addBookToTable(obj); 58 | }); 59 | } 60 | } 61 | static removeBook(target) { 62 | bookTitle = target.parentElement.parentElement.firstChild.textContent; 63 | const array = Storage.getBooks(); 64 | array.forEach((item, index) => { 65 | if(bookTitle === item.title) { 66 | array.splice(index, 1); 67 | } 68 | }) 69 | localStorage.setItem('books', JSON.stringify(array)); 70 | } 71 | } 72 | 73 | // DOM VARIABLES 74 | let form = document.querySelector('#book-form'), 75 | bookTitle = document.querySelector('#title'), 76 | bookAuthor = document.querySelector('#author'), 77 | alertDiv = document.querySelector('#alert'), 78 | tableBody = document.querySelector('#book-list'); 79 | 80 | // EVENT LISTENERS 81 | form.addEventListener('submit', function(e) { 82 | e.preventDefault(); 83 | let bookName = bookTitle.value, 84 | authorName = bookAuthor.value; 85 | 86 | console.log(bookName, authorName); 87 | const book = new Book(bookName, authorName); 88 | if (bookName === '' || authorName === '') { 89 | updateInterface.showAlert('All fields are mandatory!', 'alert-danger'); 90 | } 91 | else { 92 | updateInterface.addBookToTable(book); 93 | updateInterface.showAlert('Book Added!', 'alert-success'); 94 | Storage.addBook(book); 95 | } 96 | }); 97 | tableBody.addEventListener('click', function(e) { 98 | updateInterface.removeItem(e.target); 99 | Storage.removeBook(e.target); 100 | }); 101 | document.addEventListener('DOMContentLoaded', function(){ 102 | Storage.displayBooks(); 103 | }) 104 | 105 | -------------------------------------------------------------------------------- /booklist-app/app.js: -------------------------------------------------------------------------------- 1 | // VARIABLES 2 | let form = document.querySelector('#book-form'), 3 | title = document.querySelector('#title'), 4 | author = document.querySelector('#author'), 5 | tableBody = document.querySelector('#book-list'); 6 | 7 | let error = true; // initially both the fields are blank 8 | 9 | 10 | // EVENT LISTENERS 11 | title.addEventListener('blur', validation); 12 | author.addEventListener('blur', validation); 13 | form.addEventListener('submit', addBookToList); 14 | tableBody.addEventListener('click', removeBook); 15 | document.addEventListener('DOMContentLoaded', init); 16 | 17 | 18 | // FUNCTIONS 19 | function validation() { 20 | if(this.value != '') { 21 | this.className = 'form-control is-valid'; 22 | error = false; 23 | } 24 | else { 25 | this.className = 'form-control is-invalid'; 26 | error = true; 27 | this.nextElementSibling.className = 'invalid-feedback'; 28 | this.nextElementSibling.textContent = 'Field can\'t be left blank'; 29 | } 30 | } 31 | 32 | function addBookToList(e) { 33 | e.preventDefault(); 34 | if (error == false) { 35 | let bookName = title.value, 36 | bookAuthor = author.value; 37 | 38 | let tr = document.createElement('tr'); 39 | tr.innerHTML = ` 40 | ${bookName} 41 | ${bookAuthor} 42 | X 43 | ` 44 | tableBody.appendChild(tr); 45 | 46 | form.reset(); 47 | title.className = 'form-control'; 48 | author.className = 'form-control'; 49 | 50 | addToLocalStorage(bookName, bookAuthor); 51 | } 52 | } 53 | 54 | function removeBook(e) { 55 | // console.log(e.target); 56 | if(e.target.classList.contains('btn-danger')) { 57 | // console.log(e.target.parentElement.parentElement.firstElementChild.textContent) 58 | // remvoe from Local Storage 59 | removeBookFromLocalStorage(e.target.parentElement.parentElement.firstElementChild.textContent); 60 | //remove from DOM 61 | e.target.parentElement.parentElement.remove(); 62 | } 63 | } 64 | 65 | function removeBookFromLocalStorage(name) { 66 | let books = JSON.parse(localStorage.getItem('books')); 67 | books.forEach(function(book, index) { 68 | if(book.book === name) { 69 | books.splice(index, 1) 70 | } 71 | }) 72 | localStorage.setItem('books', JSON.stringify(books)); 73 | } 74 | 75 | function getFromLocalStorage() { 76 | if (localStorage.getItem('books') == null) { 77 | localStorage.setItem('books', '[]'); 78 | return JSON.parse(localStorage.getItem('books')); 79 | } 80 | else { 81 | return JSON.parse(localStorage.getItem('books')); 82 | } 83 | } 84 | 85 | function addToLocalStorage(bookName, authorName) { 86 | let obj = { 87 | book: bookName, 88 | author: authorName 89 | } 90 | let ls = getFromLocalStorage(); 91 | ls.push(obj); 92 | localStorage.setItem('books', JSON.stringify(ls)); 93 | } 94 | 95 | function init() { 96 | let books = getFromLocalStorage(); 97 | books.forEach(function(book) { 98 | let tr = document.createElement('tr'); 99 | tr.innerHTML = ` 100 | ${book.book} 101 | ${book.author} 102 | X 103 | ` 104 | tableBody.appendChild(tr); 105 | }) 106 | } -------------------------------------------------------------------------------- /booklist-app/class.js: -------------------------------------------------------------------------------- 1 | /* 2 | CLASSES 3 | ---------------------------------------------*/ 4 | // Book Class: Represent a Book 5 | class Book { 6 | constructor(title, author) { 7 | this.title = title; 8 | this.author = author; 9 | } 10 | } 11 | 12 | // UI Class: Handle UI Tasks like - display books, add book to list, remove book from list, show alert 13 | class UI { 14 | // static - so we don't have to instantiate UI everytime we wanna use displayBooks() 15 | static displayBooks() { 16 | let storedBooks = Store.getBooks(); 17 | storedBooks.forEach(function(book) { 18 | UI.addBookToList(book); 19 | }) 20 | } 21 | static addBookToList(book) { 22 | let tbody = document.getElementById('book-list'); 23 | let element = document.createElement('tr'); 24 | let template = ` 25 | ${book.title} 26 | ${book.author} 27 | X 28 | `; 29 | element.innerHTML = template; 30 | tbody.appendChild(element); 31 | } 32 | static showAlert(message, type) { 33 | let div = document.createElement('div'); 34 | div.className = `alert alert-${type}`; 35 | div.appendChild(document.createTextNode(message)); 36 | document.getElementById('alert').appendChild(div); 37 | setTimeout(function() { 38 | document.getElementById('alert').textContent = ''; 39 | }, 1000) 40 | } 41 | } 42 | 43 | // Storage Class: Handles Storage 44 | class Store { 45 | static getBooks() { 46 | let books; 47 | if (localStorage.getItem('books') === null) { 48 | books = []; 49 | } 50 | else { 51 | books = JSON.parse(localStorage.getItem('books')); 52 | } 53 | return books; 54 | } 55 | static addBook(book) { 56 | let books = Store.getBooks(); 57 | books.push(book); 58 | localStorage.setItem('books', JSON.stringify(books)); 59 | 60 | } 61 | static removeBook(bookName) { 62 | let books = Store.getBooks(); 63 | books.forEach(function(book, index){ 64 | if(book.title == bookName) { 65 | books.splice(index, 1) 66 | } 67 | }); 68 | localStorage.setItem('books', JSON.stringify(books)); 69 | 70 | } 71 | } 72 | 73 | /* 74 | EVENTS 75 | ---------------------------------------------*/ 76 | 77 | // Event: Display Books 78 | document.addEventListener('DOMContentLoaded', UI.displayBooks); 79 | 80 | // Event: Add a Book 81 | document.getElementById('book-form').addEventListener('submit', function(e) { 82 | e.preventDefault(); 83 | const bookName = document.getElementById('title').value; 84 | const bookAuthor = document.getElementById('author').value; 85 | 86 | if (bookName == '' || bookAuthor == '') { 87 | UI.showAlert('Fields can\'t be blank', 'danger'); 88 | document.getElementById('book-form').reset(); 89 | } 90 | else { 91 | // instantiating a new book 92 | const newBook = new Book(bookName, bookAuthor); 93 | Store.addBook(newBook); 94 | UI.addBookToList(newBook); 95 | UI.showAlert('Book Added', 'success') 96 | document.getElementById('book-form').reset(); 97 | } 98 | }); 99 | 100 | // Event: Remove a Book 101 | document.getElementById('book-list').addEventListener('click', function(e) { 102 | if(e.target.classList.contains('btn-danger')) { 103 | Store.removeBook(e.target.parentElement.parentElement.firstElementChild.textContent); 104 | e.target.parentElement.parentElement.remove(); 105 | UI.showAlert('Book Removed', 'success') 106 | } 107 | }) -------------------------------------------------------------------------------- /booklist-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | My Books 12 | 13 | 14 |
15 |

My Books

16 |
17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 | 26 | 27 |
28 |
29 |
30 | 31 |
32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
TitleAuthor
45 |
46 |
47 |
48 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /booklist-app/style.css: -------------------------------------------------------------------------------- 1 | footer { 2 | position: absolute; 3 | bottom: 0; 4 | width: 100%; 5 | } 6 | @media (max-width: 770px) { 7 | footer { 8 | display: none; 9 | } 10 | } 11 | @media (max-height: 700px) { 12 | footer { 13 | display: none; 14 | } 15 | } 16 | .alert-danger, .alert-success, .alert-warning { 17 | margin-bottom: 1em; 18 | padding: 0.5em; 19 | color: white; 20 | border-radius: 5px; 21 | color: #333; 22 | } 23 | .alert-danger { 24 | background: lightcoral; 25 | } 26 | .alert-success { 27 | background: rgb(110, 207, 162); 28 | } 29 | .alert-warning { 30 | background: rgb(218, 218, 127); 31 | } -------------------------------------------------------------------------------- /bookmarker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bookmarker App 8 | 9 | 10 | 11 |
12 |

Bookmarker Application

13 |

Bookmark your favorite Websites and Links

14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 |
    24 |
25 |
26 |
27 |
28 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /bookmarker/script.js: -------------------------------------------------------------------------------- 1 | // GLOBAL VARIABLES 2 | const urlInput = document.querySelector('.url'); 3 | const titleInput = document.querySelector('.title'); 4 | const btn = document.querySelector('.bookmark-btn'); 5 | const list = document.querySelector('.output-list'); 6 | 7 | // EVENT LISTENERS 8 | btn.addEventListener('click', addToList); 9 | document.addEventListener('DOMContentLoaded', loadFromLocalStorage); 10 | list.addEventListener('click', deleteItem); 11 | list.addEventListener('click', editItem); 12 | 13 | // FUNCTIONS 14 | // to add a new bookmark or edit an existing bookmark 15 | function addToList(e) { 16 | e.preventDefault(); 17 | let url = urlInput.value; 18 | let title = titleInput.value; 19 | let editedItem = document.querySelector('.marker'); 20 | 21 | // for a new item 22 | if(!editedItem) { 23 | if(url != '' && title != '') { 24 | showListItem(url, title); 25 | 26 | saveToLocalStorage(url, title); 27 | urlInput.value = ''; 28 | titleInput.value = ''; 29 | } 30 | } 31 | // for an edited item 32 | else { 33 | // make changes to the local storage 34 | let tempTitle = editedItem.firstChild.textContent; 35 | let storageUrls = getFromLocalStorage('url'); 36 | let storageTitles = getFromLocalStorage('title'); 37 | let index = storageTitles.indexOf(tempTitle); 38 | storageTitles[index] = title; 39 | storageUrls[index] = url; 40 | localStorage.setItem('url', JSON.stringify(storageUrls)); 41 | localStorage.setItem('title', JSON.stringify(storageTitles)); 42 | 43 | // make changes to the DOM 44 | editedItem.firstChild.textContent = title; 45 | editedItem.firstChild.setAttribute("href", url); 46 | 47 | // removing the marker class after editing 48 | editedItem.classList.remove('marker'); 49 | urlInput.value = ''; 50 | titleInput.value = ''; 51 | } 52 | } 53 | 54 | function showListItem(url, title) { 55 | // creating the list item 56 | let li = document.createElement('li'); 57 | let a = document.createElement('a'); 58 | a.setAttribute('href', url); 59 | a.setAttribute('target', '_blank'); 60 | a.textContent = title; 61 | li.appendChild(a); 62 | 63 | // adding the edit and delete button to the item 64 | let edit = document.createElement('div'); 65 | let cross = document.createElement('div'); 66 | edit.textContent = "Edit"; 67 | cross.textContent = "X"; 68 | edit.setAttribute('class', 'edit'); 69 | cross.setAttribute('class', 'cross'); 70 | li.appendChild(edit); 71 | li.appendChild(cross); 72 | 73 | list.appendChild(li); 74 | } 75 | 76 | function saveToLocalStorage(url, title) { 77 | let storageItemsUrl = getFromLocalStorage('url'); 78 | let storageItemsTitle = getFromLocalStorage('title'); 79 | storageItemsUrl.push(url); 80 | storageItemsTitle.push(title); 81 | localStorage.setItem('url', JSON.stringify(storageItemsUrl)); 82 | localStorage.setItem('title', JSON.stringify(storageItemsTitle)); 83 | } 84 | 85 | // get an empty array (for the very first item) or the array stored in local storage 86 | function getFromLocalStorage(item) { 87 | let items = localStorage.getItem(item); 88 | if(items == null) { 89 | items = []; 90 | } 91 | else { 92 | items = JSON.parse(items); 93 | } 94 | return items; 95 | } 96 | 97 | function loadFromLocalStorage() { 98 | let url = JSON.parse(localStorage.getItem('url')); 99 | let title = JSON.parse(localStorage.getItem('title')); 100 | if(url.length != 0 && url.length != 0) { 101 | for(let i = 0; i < url.length; i++) { 102 | showListItem(url[i], title[i]); 103 | } 104 | } 105 | } 106 | 107 | function deleteItem(e) { 108 | if(e.target.classList.contains('cross')) { 109 | let title = e.target.parentElement.firstChild.textContent; 110 | let url = e.target.parentElement.firstChild.getAttribute('href'); 111 | removeFromLocalStorage(url, title); 112 | e.target.parentElement.remove(); 113 | } 114 | } 115 | 116 | function removeFromLocalStorage(url, title) { 117 | let storageUrls = getFromLocalStorage('url'); 118 | let storageTitles = getFromLocalStorage('title'); 119 | 120 | // modifing the above arrays to remove the clicked content (title and url) 121 | storageUrls.splice(storageUrls.indexOf(url), 1); 122 | storageTitles.splice(storageTitles.indexOf(title), 1); 123 | 124 | localStorage.setItem('url', JSON.stringify(storageUrls)); 125 | localStorage.setItem('title', JSON.stringify(storageTitles)); 126 | 127 | } 128 | 129 | function editItem(e) { 130 | if(e.target.classList.contains('edit')) { 131 | let element = e.target.parentElement; 132 | let text = e.target.parentElement.textContent; 133 | let url = e.target.parentElement.firstChild.getAttribute('href'); 134 | 135 | // add a 'marker' class to the edited element 136 | element.setAttribute('class', 'marker') 137 | 138 | urlInput.value = url; 139 | // remove the last 5 characters(EditX) from the text and show it in the input box 140 | titleInput.value = text.slice(0, text.length-5); 141 | } 142 | } -------------------------------------------------------------------------------- /bookmarker/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;600&display=swap'); 2 | body { 3 | margin: 0; 4 | font-family: 'Raleway', sans-serif; 5 | } 6 | * { 7 | box-sizing: border-box; 8 | } 9 | header { 10 | padding: 1rem; 11 | background: #fbe958; 12 | border-bottom: 3px solid #b9a60f; 13 | } 14 | .heading { 15 | font-size: 2rem; 16 | text-align: center; 17 | margin: 1rem; 18 | margin-bottom: 0; 19 | font-weight: bold; 20 | } 21 | .sub-heading { 22 | text-align: center; 23 | margin-top: 0.7rem; 24 | color: #968a28; 25 | font-size: 0.95rem; 26 | } 27 | main { 28 | padding: 1rem; 29 | padding-top: 3rem; 30 | min-height: 75vh; 31 | } 32 | .container { 33 | width: 800px; 34 | margin: 0 auto; 35 | } 36 | .input-form { 37 | display: flex; 38 | justify-content: center; 39 | } 40 | .input-form input { 41 | margin-right: 2rem; 42 | padding: 5px 10px; 43 | border: 1px solid #ccc; 44 | color: #555; 45 | font-size: 0.95rem; 46 | border-radius: 2px; 47 | width: 100%; 48 | } 49 | @media (max-width: 576px) { 50 | .container { 51 | width: auto; 52 | } 53 | .input-form { 54 | flex-direction: column; 55 | } 56 | .input-form input { 57 | margin-right: 0; 58 | margin-bottom: 1rem; 59 | } 60 | } 61 | .bookmark-btn { 62 | padding: 8px 18px; 63 | cursor: pointer; 64 | border-radius: 2px; 65 | background: #0e8a7e; 66 | border: 2px solid #024a43; 67 | letter-spacing: 1px; 68 | color: white; 69 | text-transform: uppercase; 70 | } 71 | .bookmark-btn:hover { 72 | background: #009688; 73 | } 74 | .output-container { 75 | margin-top: 2rem; 76 | } 77 | .output-list { 78 | width: 80%; 79 | margin: 0 auto; 80 | list-style: none; 81 | padding: 0; 82 | } 83 | .output-list li { 84 | padding: 10px; 85 | display: flex; 86 | justify-content: space-between; 87 | border-bottom: 1px solid #e0e0e0; 88 | } 89 | .output-list a { 90 | text-decoration: none; 91 | color: #0087ff; 92 | font-weight: bold; 93 | letter-spacing: 1px; 94 | } 95 | .output-list a:hover { 96 | text-decoration: underline; 97 | } 98 | .cross { 99 | font-size: 0.9rem; 100 | color: #f44336; 101 | border: 1px solid; 102 | padding: 3px 6px; 103 | border-radius: 50%; 104 | cursor: pointer; 105 | } 106 | .cross:hover { 107 | box-shadow: 1px 2px 2px gray; 108 | } 109 | .cross:active { 110 | box-shadow: 1px 2px 2px #f44336; 111 | } 112 | .edit { 113 | text-decoration: underline; 114 | color: gray; 115 | margin-left: auto; 116 | margin-right: 1.5rem; 117 | line-height: 1.25; 118 | cursor: pointer; 119 | } 120 | .edit:hover { 121 | color: black; 122 | } 123 | .edit:active { 124 | color: black; 125 | text-shadow: 0 1px 2px gray; 126 | } 127 | 128 | footer { 129 | font-size: 0.95rem; 130 | text-align: center; 131 | } 132 | footer a { 133 | color: #0e8a7e; 134 | text-decoration: none; 135 | letter-spacing: 1px; 136 | } 137 | footer a:hover { 138 | text-decoration: underline; 139 | } -------------------------------------------------------------------------------- /carousel/img/slide-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/carousel/img/slide-1.jpeg -------------------------------------------------------------------------------- /carousel/img/slide-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/carousel/img/slide-2.jpeg -------------------------------------------------------------------------------- /carousel/img/slide-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/carousel/img/slide-3.jpeg -------------------------------------------------------------------------------- /carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vanilla JS Carousel 8 | 9 | 10 | 11 | 12 | 13 |
14 | 37 |
38 | 39 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /carousel/script.js: -------------------------------------------------------------------------------- 1 | const right = document.querySelector('.carousel__button--right'); 2 | const left = document.querySelector('.carousel__button--left'); 3 | const slides = document.querySelectorAll('.carousel__slide'); 4 | const indicators = document.querySelectorAll('.carousel__indicator'); 5 | 6 | // hiding all but the first slide 7 | slides.forEach((item, index)=> { 8 | if(index != 0) { 9 | item.classList.add('hidden'); 10 | } 11 | }) 12 | 13 | let count = 0; 14 | 15 | function incCount() { 16 | if(count != 2) { 17 | count++; 18 | } 19 | else { 20 | count = 0; 21 | } 22 | } 23 | 24 | function decCount() { 25 | if(count == 0) { 26 | count = 2; 27 | } 28 | else { 29 | count--; 30 | } 31 | } 32 | 33 | function changeIndicator() { 34 | // hide any active indicator 35 | indicators.forEach(item => { 36 | if(item.classList.contains('current-slide')) { 37 | item.classList.remove('current-slide'); 38 | } 39 | }) 40 | // add indicator to the item with index = count 41 | indicators.forEach((item, index)=> { 42 | if(index == count) { 43 | item.classList.add('current-slide'); 44 | } 45 | }) 46 | } 47 | 48 | right.addEventListener('click', ()=> { 49 | slides.forEach((item) => { 50 | if(!(item.classList.contains('hidden'))) { 51 | item.classList.add('hidden'); 52 | } 53 | }) 54 | incCount(); 55 | //console.log(count); 56 | changeIndicator() 57 | slides.forEach((item, index)=> { 58 | if(index == count) { 59 | item.classList.remove('hidden'); 60 | } 61 | }) 62 | }) 63 | 64 | left.addEventListener('click', ()=> { 65 | slides.forEach((item) => { 66 | if(!(item.classList.contains('hidden'))) { 67 | item.classList.add('hidden'); 68 | } 69 | }) 70 | decCount(); 71 | //console.log(count); 72 | changeIndicator() 73 | slides.forEach((item, index)=> { 74 | if(index == count) { 75 | item.classList.remove('hidden'); 76 | } 77 | }) 78 | }) 79 | 80 | 81 | // Changing slides when indicators are clicked 82 | indicators.forEach((element, index) => { 83 | element.addEventListener('click', ()=> { 84 | // Change Image 85 | slides.forEach(item => { 86 | if(!(item.classList.contains('hidden'))) { 87 | item.classList.add('hidden'); 88 | } 89 | }) 90 | slides[index].classList.remove('hidden'); 91 | 92 | // Change Indicator 93 | indicators.forEach(item => { 94 | if(item.classList.contains('current-slide')) { 95 | item.classList.remove('current-slide'); 96 | } 97 | }) 98 | indicators[index].classList.add('current-slide'); 99 | }) 100 | 101 | }) -------------------------------------------------------------------------------- /carousel/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: 'Quicksand', sans-serif; 6 | } 7 | button { 8 | font-size: 1.25rem; 9 | color: rgb(58, 58, 58); 10 | cursor: pointer; 11 | border: none; 12 | outline: none; 13 | } 14 | section { 15 | display: flex; 16 | justify-content: center; 17 | height: 80vh; 18 | padding-top: 5rem; 19 | } 20 | .carousel { 21 | position: relative; 22 | height: 500px; 23 | width: 500px; 24 | } 25 | .carousel__button { 26 | position: absolute; 27 | z-index: 10; 28 | top: 50%; 29 | transform: translateY(-50%); 30 | } 31 | .carousel__button--left { 32 | left: -30px; 33 | } 34 | .carousel__button--right { 35 | right: -30px; 36 | } 37 | .carousel__track-container { 38 | height: 100%; 39 | } 40 | .carousel__track { 41 | list-style-type: none; 42 | height: 100%; 43 | position: relative; 44 | } 45 | .carousel__slide { 46 | position: absolute; 47 | } 48 | @media (max-width: 576px) { 49 | .carousel { 50 | width: 350px; 51 | height: 350px; 52 | } 53 | .carousel__image { 54 | width: 100%; 55 | } 56 | } 57 | .carousel__nav { 58 | text-align: center; 59 | padding-top: 10px; 60 | } 61 | .carousel__indicator { 62 | height: 15px; 63 | width: 15px; 64 | background: rgba(0,0,0,0.3); 65 | border-radius: 50%; 66 | margin: 5px 10px; 67 | } 68 | .current-slide { 69 | background: rgba(0,0,0,0.7); 70 | } 71 | footer { 72 | text-align: center; 73 | position: absolute; 74 | bottom: 1rem; 75 | width: 100%; 76 | color: rgb(58, 58, 58); 77 | } 78 | footer a { 79 | text-decoration: none; 80 | color: rgb(43, 131, 204); 81 | } 82 | footer a:hover { 83 | color: rgb(25, 79, 122); 84 | } 85 | .hidden { 86 | display: none; 87 | } 88 | -------------------------------------------------------------------------------- /dark-mode-toggle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Home 10 | 11 | 12 |
13 |

Light/Dark Mode

14 |
15 | 16 | 17 |
18 |

Light/Dark theme toggle switch for websites. These are useful in using screen devices in dark or to save some battery life.

19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /dark-mode-toggle/script.js: -------------------------------------------------------------------------------- 1 | let html = document.querySelector('html'); 2 | let toggleBtn = document.getElementById('switch'); 3 | 4 | 5 | toggleBtn.addEventListener('click', changeTheme); 6 | 7 | 8 | function changeTheme() { 9 | if(toggleBtn.checked === true) { 10 | html.setAttribute('theme', 'dark'); 11 | } 12 | else { 13 | html.setAttribute('theme', 'light'); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /dark-mode-toggle/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Oxygen'); 2 | html { 3 | font-family: Oxygen, sans-serif; 4 | height: 100%; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | 9 | /* color variables for the default light theme */ 10 | --bg: #fcfcfc; 11 | --bg-card: #ebebeb; 12 | --color-heading: #0077ff; 13 | --color-text: #333333; 14 | } 15 | 16 | /* color variables if the button is toggled, triggered by javascript */ 17 | html[theme='dark'] { 18 | --bg: #333333; 19 | --bg-card: #434343; 20 | --color-heading: #3694ff; 21 | --color-text: #b5b5b5; 22 | } 23 | 24 | body { 25 | background-color: var(--bg); 26 | /* border: 1px dotted gray; */ 27 | } 28 | .container { 29 | background-color: var(--bg-card ); 30 | padding: 4em; 31 | margin: 2em; 32 | border-radius: 10px; 33 | 34 | display: grid; 35 | grid-column: 80% auto; 36 | grid-row: auto auto; 37 | grid-template-areas: 38 | "title switch" 39 | "content content" 40 | } 41 | h1 { 42 | margin: 0; 43 | color: var(--color-heading); 44 | } 45 | p { 46 | color: var(--color-text); 47 | grid-area: content; 48 | margin-top: 2em; 49 | font-size: 1.1rem; 50 | line-height: 1.8rem; 51 | } 52 | 53 | /************************** 54 | TOGGLE BUTTON STYELS 55 | ***************************/ 56 | 57 | input[type='checkbox'] { 58 | height: 0; 59 | width: 0; 60 | visibility: hidden; 61 | } 62 | label { 63 | height: 27px; 64 | width: 52px; 65 | background: gray; 66 | float: right; 67 | border-radius: 100px; 68 | cursor: pointer; 69 | text-indent: -9999px; /* hiding the text "Toggle"*/ 70 | position: relative; 71 | } 72 | label:after { 73 | content: ''; 74 | position: absolute; 75 | height: 20px; 76 | width: 20px; 77 | top: 3px; 78 | left: 3px; 79 | background: var(--bg); 80 | border-radius: 90px; 81 | transition: 0.3s; 82 | } 83 | input:checked + label { 84 | background: var(--color-heading); 85 | } 86 | input:checked + label:after { 87 | left: calc(100% - 3px); 88 | transform: translateX(-100%); 89 | } 90 | label:active:after { 91 | width: 45px; 92 | } 93 | 94 | -------------------------------------------------------------------------------- /drag-n-drop/deck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/drag-n-drop/deck.png -------------------------------------------------------------------------------- /drag-n-drop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Drag N Drop 10 | 11 | 12 | 13 |

Drag n Drop

14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /drag-n-drop/script.js: -------------------------------------------------------------------------------- 1 | // VARIABLES 2 | let fill = document.querySelector('.fill'), 3 | emptyBox = document.querySelectorAll('.empty'); 4 | 5 | // EVENT LISTENERS 6 | fill.addEventListener('dragstart', dragStart); 7 | fill.addEventListener('dragend', dragEnd); 8 | 9 | // to call 4 different events for each box 10 | emptyBox.forEach(function(box) { 11 | box.addEventListener('dragover', dragOver); 12 | box.addEventListener('dragenter', dragEnter); 13 | box.addEventListener('dragleave', dragLeave); 14 | box.addEventListener('drop', dragDrop); 15 | }); 16 | 17 | 18 | // FUNCTIONS 19 | function dragStart() { 20 | this.className += ' hold'; 21 | // hiding the image from the box when dragStart triggers 22 | setTimeout(() => (this.className = 'invisible'),0); 23 | } 24 | function dragEnd() { 25 | // putting the image back when dragEnd triggers 26 | this.className = 'fill' 27 | } 28 | 29 | function dragOver(e) { 30 | e.preventDefault(); 31 | } 32 | function dragEnter(e) { 33 | e.preventDefault(); 34 | this.className += ' hovered' 35 | } 36 | function dragLeave() { 37 | this.className = 'empty'; 38 | } 39 | function dragDrop() { 40 | this.className = 'empty'; 41 | // this.className += ' fill'; 42 | this.append(fill); // appending the entire div 43 | } -------------------------------------------------------------------------------- /drag-n-drop/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Righteous'); 2 | body { 3 | background-image: linear-gradient(to top, #3f51b1 0%, #5a55ae 13%, #7b5fac 25%, #8f6aae 38%, #a86aa4 50%, #cc6b8e 62%, #f18271 75%, #f3a469 87%, #f7c978 100%); 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | height: 100vh; 8 | width: 100vw; 9 | } 10 | h1 { 11 | position: absolute; 12 | top: 1em; 13 | font-family: Righteous, sans-serif; 14 | font-size: 4rem; 15 | background: -webkit-linear-gradient(#30cfd0, #330867); 16 | -webkit-background-clip: text; 17 | -webkit-text-fill-color: transparent; 18 | z-index: 1; 19 | } 20 | .container { 21 | z-index: 5; 22 | padding: 5px; 23 | } 24 | @media (max-width: 730px) { 25 | .container { 26 | width: 350px; 27 | } 28 | h1 { 29 | font-size: 3.2rem; 30 | } 31 | } 32 | .empty { 33 | display: inline-block; 34 | background: white; 35 | height: 160px; 36 | width: 160px; 37 | } 38 | .fill { 39 | background-color: #16a085; 40 | /* background-image: url('https://source.unsplash.com/random/150x150'); */ 41 | background-image: url('deck.png'); 42 | position: relative; 43 | height: 150px; 44 | width: 150px; 45 | top: 5px; 46 | left: 5px; 47 | cursor: pointer; 48 | } 49 | /* Javascript Triggered Classes*/ 50 | .hold { 51 | border: 4px solid black; 52 | } 53 | .hovered { 54 | background: rgb(88, 88, 88); 55 | } 56 | .invisible { 57 | display: none; 58 | } -------------------------------------------------------------------------------- /filterable-list/filterable-list-v2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | List Search 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Front End Developer Skills

16 | 17 | 18 | 19 | 33 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /filterable-list/filterable-list-v2/script.js: -------------------------------------------------------------------------------- 1 | let myInput = document.querySelector(".custom-input") 2 | let myList = document.querySelectorAll(".custom-list li a") 3 | 4 | window.addEventListener("load", () => { 5 | myInput.focus() 6 | }) 7 | 8 | myInput.addEventListener("input", () => { 9 | myList.forEach(val => { 10 | console.log(myInput.value.toLowerCase()) 11 | if (val.textContent.toLowerCase().indexOf(myInput.value.toLowerCase()) == -1) { 12 | val.parentElement.style.display = "none" 13 | } 14 | if (myInput.value == "") { 15 | val.parentElement.style.display = "block" 16 | } 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /filterable-list/filterable-list-v2/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Playfair+Display:400,700&display=swap"); 2 | 3 | body { 4 | margin: 0; 5 | font-family: "Playfair Display", serif; 6 | background: rgba(0, 0, 0, 0.9); 7 | color: white; 8 | font-size: 18px; 9 | } 10 | 11 | section { 12 | display: flex; 13 | align-items: center; 14 | padding-left: 3rem; 15 | } 16 | 17 | .container { 18 | padding: 1rem; 19 | } 20 | 21 | h1 { 22 | font-size: 3rem; 23 | font-weight: 700; 24 | background: linear-gradient(to left, #00a7ff 0%, #00bc3f 100%); 25 | background-clip: text; 26 | -webkit-background-clip: text; 27 | -webkit-text-fill-color: transparent; 28 | } 29 | 30 | .custom-input { 31 | border: none; 32 | height: 3.25rem; 33 | padding-left: 10px; 34 | min-width: 300px; 35 | font-size: 1rem; 36 | } 37 | 38 | .custom-input:focus, 39 | .custom-input:active { 40 | outline: none; 41 | } 42 | 43 | .custom-list { 44 | list-style: none; 45 | padding: 0; 46 | } 47 | 48 | .custom-list li { 49 | margin-bottom: 5px; 50 | padding-bottom: 5px; 51 | width: 250px; 52 | transition: all 0.5s ease; 53 | } 54 | 55 | .custom-list a { 56 | color: inherit; 57 | text-decoration: none; 58 | font-size: 1.2rem; 59 | } 60 | 61 | .custom-list a:hover { 62 | color: #946e79; 63 | } 64 | 65 | @media (max-width: 576px) { 66 | section { 67 | padding-left: 0; 68 | } 69 | h1 { 70 | font-size: 2.25rem; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /filterable-list/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Filterable List 9 | 14 | 15 | 16 |
17 |

Front End Developer Sills

18 | 19 | 63 |
64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /filterable-list/script.js: -------------------------------------------------------------------------------- 1 | // VARIABLES 2 | let textInput = document.getElementById('search'), 3 | names = document.querySelectorAll('.collection-item'), 4 | heading = document.querySelectorAll('.collection-header'); 5 | 6 | // EVENT LISTENERS 7 | textInput.addEventListener('input', filterList); 8 | document.addEventListener('DOMContentLoaded', ()=> { 9 | textInput.focus(); 10 | }) 11 | 12 | // FUNCTIONS 13 | function filterList() { 14 | let inputValue = textInput.value.toUpperCase(); 15 | inputLetter = textInput.value.toUpperCase().substring(0,1); 16 | 17 | // hiding the name with matching letters 18 | for (var i=0; i < names.length; i++) { 19 | let link = names[i].querySelector('a'); 20 | let content = link.textContent.toUpperCase(); 21 | 22 | // if input item characters matches a name 23 | if (content.indexOf(inputValue) > -1) { 24 | } 25 | else { 26 | link.parentElement.style.display = "none"; 27 | } 28 | } 29 | 30 | // hiding the names heading 31 | for(var j=0; j search for a GitHub user and get their details 4 | 5 | API Used - [GitHub API ](https://developer.github.com/v3/) 6 | -------------------------------------------------------------------------------- /github-finder-app/app.js: -------------------------------------------------------------------------------- 1 | const github = new GitHub() 2 | const ui = new UI() 3 | 4 | const searchInput = document.querySelector("#search-input") 5 | 6 | searchInput.addEventListener("keyup", e => { 7 | const searchText = e.target.value 8 | if (searchText != "") { 9 | github.getUser(searchText).then(data => { 10 | if (data.profile.message == "Not Found") { 11 | // Show user not found alert 12 | ui.showAlert(searchText) 13 | } else { 14 | // Show user profile 15 | ui.showProfile(data.profile) 16 | } 17 | }) 18 | } else { 19 | // Clear profile 20 | ui.clearProfile() 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /github-finder-app/github.js: -------------------------------------------------------------------------------- 1 | class GitHub { 2 | constructor() { 3 | this.client_id = "f87f791e3012f3924b6b" 4 | this.client_secret = "15656eee74dc31b6fcd74d4961c31fb842e5e01e" 5 | } 6 | 7 | async getUser(user) { 8 | const profileResponse = await fetch(`https://api.github.com/users/${user}?client_id=${this.client_id}&client_secret=${this.client_secret}`) 9 | const profileData = await profileResponse.json() 10 | return { 11 | profile: profileData 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /github-finder-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GitHub Finder 8 | 9 | 10 | 11 |
12 |

GitHub Finder

13 |

Search for a GitHub user and get their profile and repository details

14 |
15 |
16 | 19 |
20 |

21 |
22 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /github-finder-app/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Heebo:wght@400;700&display=swap"); 2 | 3 | body { 4 | font-family: "Heebo", sans-serif; 5 | color: #2c3e50; 6 | margin: 0; 7 | } 8 | header { 9 | background: #2c3e50; 10 | padding: 5px; 11 | color: #44c549; 12 | text-align: center; 13 | } 14 | .title { 15 | font-size: 2.2rem; 16 | font-weight: 700; 17 | margin: 1rem 0 0.5rem 0; 18 | } 19 | .desc { 20 | font-size: 1.2rem; 21 | margin: 0.5rem 0; 22 | } 23 | main { 24 | padding: 1rem; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | } 29 | .search { 30 | padding: 1rem; 31 | display: flex; 32 | justify-content: center; 33 | } 34 | .search input { 35 | font-size: 1.1rem; 36 | padding: 7px 14px; 37 | width: 600px; 38 | border: 1px solid rgb(182, 182, 182); 39 | border-radius: 5px; 40 | } 41 | .profile { 42 | width: 1000px; 43 | } 44 | .profile-container { 45 | display: flex; 46 | justify-content: center; 47 | background: rgb(238, 238, 238); 48 | box-shadow: 1px 1px 2px lightgray; 49 | border-radius: 5px; 50 | margin-bottom: 1rem; 51 | padding: 1rem; 52 | } 53 | .user-dp { 54 | flex: 1; 55 | display: flex; 56 | justify-content: center; 57 | } 58 | .user-dp img { 59 | width: 80%; 60 | border-radius: 50%; 61 | } 62 | .user-details { 63 | flex: 3; 64 | } 65 | .user-details p { 66 | font-size: 1.2rem; 67 | margin: 0.3rem 0; 68 | color: gray; 69 | } 70 | .user-details span { 71 | color: #2c3e50; 72 | padding-left: 0.5rem; 73 | width: 100px; 74 | display: inline-block; 75 | } 76 | .profile-container-two { 77 | justify-content: space-around; 78 | } 79 | .custom-btn, 80 | .profile-btn { 81 | background: #c0d3ff; 82 | padding: 0.5rem; 83 | border-radius: 5px; 84 | } 85 | .custom-btn span { 86 | display: inline-block; 87 | margin-left: 0.5rem; 88 | background: white; 89 | padding: 3px; 90 | width: 35px; 91 | text-align: center; 92 | border-radius: 3px; 93 | } 94 | .profile-btn { 95 | text-decoration: none; 96 | display: flex; 97 | align-items: center; 98 | padding: 0.5rem 1rem; 99 | color: #2196f3; 100 | box-shadow: 2px 2px 2px gray; 101 | background: white; 102 | } 103 | .profile-btn:hover { 104 | color: #673ab7; 105 | text-decoration: underline; 106 | } 107 | .alert { 108 | text-align: center; 109 | color: crimson; 110 | } 111 | .alert span { 112 | color: gray; 113 | } 114 | footer { 115 | text-align: center; 116 | position: absolute; 117 | bottom: 1rem; 118 | left: 50%; 119 | transform: translateX(-50%); 120 | } 121 | footer a { 122 | color: #2196f3; 123 | text-decoration: none; 124 | } 125 | footer a:hover { 126 | color: #673ab7; 127 | text-decoration: underline; 128 | } 129 | @media (max-width: 756px) { 130 | .search input { 131 | width: 350px; 132 | } 133 | .profile { 134 | width: 320px; 135 | } 136 | .profile-container { 137 | flex-direction: column; 138 | } 139 | .user-dp img { 140 | width: 50%; 141 | } 142 | .user-details p { 143 | font-size: 1rem; 144 | } 145 | .profile-container-two { 146 | align-items: center; 147 | } 148 | .custom-btn { 149 | margin: 0.5rem; 150 | } 151 | footer { 152 | display: none; 153 | } 154 | } 155 | @media (max-height: 756px) { 156 | footer { 157 | display: none; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /github-finder-app/ui.js: -------------------------------------------------------------------------------- 1 | class UI { 2 | constructor() { 3 | this.profile = document.querySelector(".profile") 4 | this.profile.style.display = "none" 5 | } 6 | showProfile(user) { 7 | this.profile.style.display = "block" 8 | const html = ` 9 |
10 |
11 | 12 |
13 |
14 |

Name ${user.name}

15 |

Bio ${user.bio}

16 |

Location ${user.location}

17 |

Blog ${user.blog}

18 |

Company ${user.company}

19 |
20 |
21 |
22 |
Public Repos ${user.public_repos}
23 | View Profile 24 |
Followers ${user.followers}
25 |
26 | ` 27 | this.profile.innerHTML = html 28 | } 29 | clearProfile() { 30 | this.profile.style.display = "none" 31 | } 32 | showAlert(inputText) { 33 | this.profile.innerHTML = `

User ${inputText} not found

` 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /math-game/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Math Game 8 | 9 | 10 | 11 |
12 |

13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /math-game/script.js: -------------------------------------------------------------------------------- 1 | let problemElement = document.querySelector(".problem") 2 | let userAnswer = document.querySelector(".answer") 3 | 4 | let state = { 5 | score: 0, 6 | wrongAnswers: 0, 7 | problem: "", 8 | answer: "" 9 | } 10 | 11 | function generateNumber(max) { 12 | return Math.floor(Math.random() * (max + 1)) 13 | } 14 | 15 | function generateMathProblem() { 16 | let problem = "" 17 | let mathOperations = "+-*" 18 | let randomOperation = mathOperations[generateNumber(mathOperations.length - 1)] 19 | problem = `${generateNumber(10)} ${randomOperation} ${generateNumber(10)}` 20 | return { 21 | problem: problem, 22 | solution: eval(problem) 23 | } 24 | } 25 | 26 | function updateProblem() { 27 | let expression = generateMathProblem() 28 | state.problem = expression.problem 29 | state.answer = expression.solution 30 | problemElement.innerHTML = state.problem 31 | } 32 | 33 | document.addEventListener("DOMContentLoaded", () => { 34 | updateProblem() 35 | userAnswer.focus() 36 | }) 37 | userAnswer.addEventListener("keyup", e => { 38 | if (parseInt(e.target.value) == state.answer) { 39 | console.log("Correct") 40 | } else { 41 | console.log("Incorrect") 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /math-game/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Epilogue:wght@400;500&display=swap"); 2 | 3 | body { 4 | margin: 0; 5 | font-family: "Epilogue", sans-serif; 6 | } 7 | .app { 8 | text-align: center; 9 | } 10 | .problem { 11 | font-size: 5rem; 12 | margin-bottom: 1.5rem; 13 | min-height: 100px; 14 | line-height: 1.5; 15 | font-weight: 500; 16 | } 17 | .answer { 18 | padding: 4px 8px; 19 | font-size: 2rem; 20 | } 21 | -------------------------------------------------------------------------------- /modal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Modal 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /modal/script.js: -------------------------------------------------------------------------------- 1 | const openBtn = document.getElementById('open'); 2 | const closeBtn = document.getElementById('close'); 3 | const modal = document.getElementsByClassName('modal-container')[0]; 4 | 5 | openBtn.addEventListener('click', () => { 6 | modal.classList.toggle('toggle-modal'); 7 | }); 8 | closeBtn.addEventListener('click', () => { 9 | modal.classList.toggle('toggle-modal'); 10 | }); -------------------------------------------------------------------------------- /modal/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | height: 100%; 3 | } 4 | body { 5 | font-family: 'Playfair Display', serif; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | height: 100%; 10 | background: #EFEEEB; 11 | } 12 | .modal-container { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | height: 100%; 17 | width: 100%; 18 | background: rgba(0, 0, 0, 0.6); 19 | display: none; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | .modal { 24 | width: 600px; 25 | max-width: 85%; 26 | background: #EFEEEB; 27 | padding: 1rem; 28 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6); 29 | } 30 | h1 { 31 | color: #202329; 32 | } 33 | p { 34 | color: #40434A; 35 | } 36 | button { 37 | cursor: pointer; 38 | font-size: 1rem; 39 | color: #202329; 40 | font-family: 'Playfair Display', serif; 41 | border: none; 42 | background: #009A9C; 43 | color: #EFEEEB; 44 | padding: 7px 14px; 45 | border-radius: 25px; 46 | outline: none; 47 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); 48 | } 49 | .toggle-modal { 50 | display: flex; 51 | } 52 | -------------------------------------------------------------------------------- /number-guesser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Number Guesser 9 | 10 | 11 |
12 |
13 |

Number Guesser

14 |

Guess a number between 1 and 10

15 |
16 | 17 | 18 |
19 |

20 |
21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /number-guesser/script.js: -------------------------------------------------------------------------------- 1 | // VARIABLES 2 | // generating a random number between 0 and 9 3 | let generatedNumber = Math.floor(Math.random() * 10); 4 | 5 | let form = document.querySelector('#form'), 6 | inputNumber = document.querySelector('#userInput'), 7 | info = document.querySelector('#info'), 8 | submit = document.querySelector('#submit'); 9 | 10 | let attempts = 3; 11 | 12 | // EVENT LISTENERS 13 | form.addEventListener('submit', startGame); 14 | 15 | // FUNTIONS 16 | function startGame(e) { 17 | e.preventDefault(); 18 | let userNumber = inputNumber.value; 19 | if (userNumber != '') { 20 | attempts--; 21 | inputNumber.value = ''; 22 | // console.log('You Entered: ', userNumber); 23 | // console.log('Correct Answer: ', generatedNumber); 24 | // console.log('Attempts Remaining: ', attempts); 25 | gameLogic(userNumber); 26 | } 27 | else { 28 | alert('Enter a Number: '); 29 | inputNumber.focus(); 30 | } 31 | } 32 | 33 | function gameLogic(userNumber) { 34 | if(userNumber == generatedNumber && attempts >= 0) { 35 | let str = `${userNumber} is correct`; 36 | info.style.color = 'green'; 37 | info.innerHTML = str; 38 | // reseting values 39 | attempts = 3; 40 | generatedNumber = Math.floor(Math.random() * 10); 41 | } 42 | else { 43 | let str = `${userNumber} is not correct, you have ${attempts} guesses left`; 44 | info.style.color = 'red'; 45 | info.innerHTML = str; 46 | if (attempts == 0) { 47 | endGame(); 48 | } 49 | } 50 | } 51 | 52 | function endGame() { 53 | submit.disabled = true; 54 | let str = `Game Over, The correct answer was ${generatedNumber}
To replay, reload the page`; 55 | info.style.color = 'red'; 56 | info.innerHTML = str; 57 | } 58 | -------------------------------------------------------------------------------- /number-guesser/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: 'Franklin Gothic Medium', sans-serif 3 | } 4 | body { 5 | margin: 0; 6 | } 7 | .card { 8 | background-color:rgb(165, 236, 165); 9 | background-image: linear-gradient(to top, #96fbc4 0%, #f9f586 100%); 10 | padding: 1em; 11 | max-width: 800px; 12 | margin: auto; 13 | } 14 | form { 15 | display: flex; 16 | /* border: 1px solid pink; */ 17 | } 18 | form > * { 19 | font-size: 1.5em; 20 | } 21 | input[type="number"] { 22 | width: 120px; 23 | } 24 | input[type="submit"] { 25 | border: 1px solid gray; 26 | border-radius: 5px; 27 | cursor: pointer; 28 | font-size: 1em; 29 | padding: 7px 14px; 30 | margin-left: 1em; 31 | text-transform: uppercase; 32 | } 33 | input[type="submit"]:hover { 34 | border-color: black; 35 | } 36 | #info { 37 | color: red; 38 | font-size: 0.9em; 39 | padding: 5px; 40 | height: 15px; 41 | } 42 | -------------------------------------------------------------------------------- /page-coming-soon-countdown/img/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/page-coming-soon-countdown/img/background.jpg -------------------------------------------------------------------------------- /page-coming-soon-countdown/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/page-coming-soon-countdown/img/favicon.ico -------------------------------------------------------------------------------- /page-coming-soon-countdown/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/page-coming-soon-countdown/img/icon.png -------------------------------------------------------------------------------- /page-coming-soon-countdown/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/page-coming-soon-countdown/img/logo.png -------------------------------------------------------------------------------- /page-coming-soon-countdown/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Coming Soon 10 | 11 | 12 |
13 |
14 | 15 |

Coming Soon!

16 |
17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /page-coming-soon-countdown/script.js: -------------------------------------------------------------------------------- 1 | const element = document.querySelector(".countdown") 2 | const launchDate = new Date("Aug 21, 2021 09:00:00") 3 | 4 | // Launch Date (ms) 5 | const end = launchDate.getTime() 6 | 7 | // Update every second 8 | const interval = setInterval(function() { 9 | // Get todays date and time (ms) 10 | const start = new Date().getTime() 11 | const distance = end - start 12 | 13 | // Calculations 14 | const days = Math.floor(distance / (1000 * 60 * 60 * 24)) 15 | const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) 16 | const min = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)) 17 | const sec = Math.floor((distance % (1000 * 60)) / 1000) 18 | // console.log(days, hours, min, sec); 19 | 20 | // Update DOM 21 | element.innerHTML = ` 22 |
${days}

days

23 |
${hours}

hours

24 |
${min}

minutes

25 |
${sec}

seconds

26 | ` 27 | 28 | if (days <= 0) { 29 | // stop countdown 30 | clearInterval(interval) 31 | element.previousElementSibling.textContent = "" 32 | element.textContent = "Launch Date Passed" 33 | element.style.color = "cyan" 34 | } 35 | }, 1000) 36 | -------------------------------------------------------------------------------- /page-coming-soon-countdown/style.css: -------------------------------------------------------------------------------- 1 | 2 | @import url('https://fonts.googleapis.com/css?family=Lato:400,700'); 3 | html { 4 | box-sizing: border-box; 5 | } 6 | *, *:before, *:after { 7 | box-sizing: inherit; 8 | } 9 | body { 10 | font-family: Lato, sans-serif; 11 | background: lightgray; 12 | margin: 0; 13 | } 14 | /* 15 | SECTION 16 | */ 17 | .landing { 18 | background-image: url(img/background.jpg); 19 | background-size: cover; 20 | background-position: center; 21 | height: 100vh; 22 | position: relative; /* overlay will be positioned absolute*/ 23 | color: white; 24 | } 25 | .overlay { 26 | position: absolute; 27 | left: 0; 28 | top: 0; 29 | height: 100%; 30 | width: 100%; 31 | background-color: rgba(0,0,0,0.4); 32 | text-align: center; 33 | } 34 | .logo { 35 | opacity: 0.8; 36 | border-radius: 1em; 37 | margin-top: 4em; 38 | } 39 | h1 { 40 | font-size: 3rem; 41 | margin-top: 0.5em; 42 | } 43 | .countdown { 44 | font-size: 4rem; 45 | max-width: 800px; 46 | margin: auto; 47 | display: flex; 48 | justify-content: space-around; 49 | } 50 | .countdown > div { 51 | border: 1px solid white; 52 | padding: 0.5em; 53 | border-radius: 0.5em; 54 | } 55 | .countdown .sub { 56 | font-size: 1.2rem; 57 | margin: 0; 58 | color: cyan; 59 | } 60 | /* 61 | MEDIA QUERIES 62 | */ 63 | @media (max-width: 600px) { 64 | .logo { 65 | height: 200px; 66 | } 67 | h1 { 68 | font-size: 3rem; 69 | } 70 | .countdown { 71 | font-size: 3rem; 72 | width: 100%; 73 | } 74 | } 75 | @media (max-width: 500px) { 76 | .logo { 77 | height: 150px; 78 | } 79 | h1 { 80 | font-size: 2.5rem; 81 | } 82 | .countdown { 83 | flex-direction: column; 84 | width: 70%; 85 | } 86 | .countdown div:not(:first-child) { 87 | display: none; 88 | } 89 | } 90 | @media (max-height: 650px) { 91 | .logo { 92 | height: 150px; 93 | } 94 | .countdown { 95 | font-size: 2rem; 96 | } 97 | } -------------------------------------------------------------------------------- /pagination/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Pagination 8 | 9 | 10 | 11 |
12 |
13 |

Pagination

14 |

The following data is being fetched from JSON Placeholder API

15 |
16 |
Loading...
17 | 29 |
30 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /pagination/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | Fetching and displaying the API Data 3 | */ 4 | let content = document.querySelector(".content") 5 | 6 | async function getApiData() { 7 | const apiResponse = await fetch("https://jsonplaceholder.typicode.com/posts") 8 | const apiData = await apiResponse.json() 9 | return apiData 10 | } 11 | 12 | let elementsPerPage = 10 13 | 14 | function printElments(startingElement, numberOfElements) { 15 | getApiData().then(data => { 16 | data = data.splice(startingElement, numberOfElements) 17 | let str = "" 18 | data.forEach(item => { 19 | str += `

${item.id}. ${item.title}

` 20 | //console.log(item.title) 21 | }) 22 | content.innerHTML = str 23 | }) 24 | } 25 | // initially loading the first 10 elements 26 | printElments(0, elementsPerPage) 27 | 28 | /* 29 | Pagination functionality 30 | */ 31 | 32 | let paginationLink = document.querySelectorAll(".pagination__link") 33 | 34 | paginationLink.forEach(item => { 35 | item.addEventListener("click", loadPageData) 36 | }) 37 | 38 | function loadPageData() { 39 | let pageNumber = parseInt(this.textContent) 40 | printElments(pageNumber * 10 - 10, elementsPerPage) 41 | } 42 | -------------------------------------------------------------------------------- /pagination/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Barlow:wght@400;600&display=swap"); 2 | 3 | body { 4 | font-family: "Barlow", sans-serif; 5 | color: #333; 6 | font-size: 1.1rem; 7 | margin: 0; 8 | } 9 | 10 | a { 11 | text-decoration: none; 12 | color: rgb(15, 96, 163); 13 | } 14 | a:hover { 15 | color: rgb(9, 63, 107); 16 | } 17 | 18 | main { 19 | padding: 1rem; 20 | max-width: 700px; 21 | margin: 0 auto; 22 | min-height: 88vh; 23 | } 24 | 25 | .header { 26 | background: lightblue; 27 | padding: 1rem; 28 | } 29 | 30 | .title { 31 | font-size: 2rem; 32 | font-weight: 600; 33 | margin: 10px 0; 34 | text-align: center; 35 | } 36 | 37 | .subtitle { 38 | text-align: center; 39 | } 40 | 41 | .content { 42 | padding: 0.5rem 1rem; 43 | } 44 | 45 | .pagination { 46 | margin-top: 1.5rem; 47 | display: flex; 48 | justify-content: center; 49 | } 50 | 51 | .pagination__link { 52 | padding: 5px 10px; 53 | cursor: pointer; 54 | border: 1px solid lightgray; 55 | transform: all 0.3s ease; 56 | } 57 | .pagination__link:not(:last-child) { 58 | border-right: none; 59 | } 60 | .pagination__link:hover { 61 | transform: scale(1.1); 62 | } 63 | 64 | footer { 65 | text-align: center; 66 | } 67 | -------------------------------------------------------------------------------- /password-generator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Password Generator 8 | 9 | 10 | 11 |
12 |
13 |

Password Generator

14 |
15 |
16 | 17 |
Copy
18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 | 40 | 41 |
42 |
43 |
44 |
45 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /password-generator/script.js: -------------------------------------------------------------------------------- 1 | const result = document.querySelector('#result'), 2 | copyBtn = document.querySelector('.result-copy'), 3 | length = document.querySelector('#length'), 4 | uppercase = document.querySelector('#uppercase'), 5 | lowercase = document.querySelector('#lowercase'), 6 | numbers = document.querySelector('#numbers'), 7 | symbols = document.querySelector('#symbols'), 8 | generate = document.querySelector('#generate'), 9 | message = document.querySelector('.message'); 10 | 11 | 12 | generate.addEventListener('click', function(e) { 13 | e.preventDefault(); 14 | let passwordLength = length.value; 15 | if(passwordLength > 4 && passwordLength <= 25) { 16 | let password = ''; 17 | for(let i = 0; i < passwordLength; i++) { 18 | password += generateRandomCharacter(); 19 | } 20 | result.value = password; 21 | message.textContent = ""; 22 | } 23 | else { 24 | message.textContent = "Select a length between 5 and 25"; 25 | if(message.classList.contains('message-success')) { 26 | message.classList.remove('message-success'); 27 | } 28 | result.value = ""; 29 | hideMessage(); 30 | } 31 | }); 32 | 33 | copyBtn.addEventListener('click', function() { 34 | const textarea = document.createElement('textarea'); 35 | const password = result.value; 36 | 37 | if(!password) { return; } 38 | 39 | textarea.value = password; 40 | document.body.appendChild(textarea); 41 | textarea.select(); 42 | document.execCommand('copy'); 43 | textarea.remove(); 44 | message.textContent = "Password copied to clipboard"; 45 | message.classList.add('message-success'); 46 | hideMessage(); 47 | }); 48 | 49 | 50 | function getRandomUppercase() { 51 | // (65 to 90) ascii values for (A to Z) 52 | return String.fromCharCode(Math.floor(Math.random() * 26) + 65); 53 | } 54 | 55 | function getRandomLowercase() { 56 | // (97 to 122) ascii values for (a to z) 57 | return String.fromCharCode(Math.floor(Math.random() * 26) + 97); 58 | } 59 | 60 | function getRandomNumber() { 61 | return Math.floor(Math.random() * 10).toString(); 62 | } 63 | 64 | function getRandomSymbol() { 65 | const str = "!@#$%^&*()_+<>{}?:"; 66 | return str[Math.floor(Math.random() * str.length)]; 67 | } 68 | 69 | 70 | // calling a random function from the above 4 functions 71 | function generateRandomCharacter() { 72 | if(uppercase.checked && lowercase.checked && numbers.checked && symbols.checked) { 73 | const functions = { 74 | 0: getRandomUppercase, 75 | 1: getRandomLowercase, 76 | 2: getRandomNumber, 77 | 3: getRandomSymbol 78 | } 79 | return functions[Math.floor(Math.random() * 4)](); 80 | } 81 | if(uppercase.checked && lowercase.checked && numbers.checked && !symbols.checked) { 82 | const functions = { 83 | 0: getRandomUppercase, 84 | 1: getRandomLowercase, 85 | 2: getRandomNumber 86 | } 87 | return functions[Math.floor(Math.random() * 3)](); 88 | } 89 | if(uppercase.checked && lowercase.checked && !numbers.checked && symbols.checked) { 90 | const functions = { 91 | 0: getRandomUppercase, 92 | 1: getRandomLowercase, 93 | 2: getRandomSymbol 94 | } 95 | return functions[Math.floor(Math.random() * 3)](); 96 | } 97 | if(uppercase.checked && lowercase.checked && !numbers.checked && !symbols.checked) { 98 | const functions = { 99 | 0: getRandomUppercase, 100 | 1: getRandomLowercase 101 | } 102 | return functions[Math.floor(Math.random() * 2)](); 103 | } 104 | } 105 | 106 | function hideMessage() { 107 | setTimeout(function() { 108 | message.textContent = ""; 109 | }, 3000) 110 | } 111 | 112 | -------------------------------------------------------------------------------- /password-generator/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: sans-serif; 4 | background: rgba(0,0,0,0.8); 5 | } 6 | main { 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | height: 100vh; 11 | } 12 | .box { 13 | box-shadow: 1px 2px 4px #9e9e9e; 14 | padding: 1rem; 15 | width: 350px; 16 | background: #f1f1f1; 17 | border-radius: 3px; 18 | } 19 | form { 20 | margin-bottom: 0; 21 | } 22 | .title { 23 | text-align: center; 24 | font-size: 1.75rem; 25 | } 26 | .result-row { 27 | position: relative; 28 | } 29 | .result-copy { 30 | position: absolute; 31 | padding: 7px 10px; 32 | background: #464646; 33 | color: white; 34 | top: 9px; 35 | right: 3px; 36 | cursor: pointer; 37 | font-family: consolas; 38 | border-radius: 25px; 39 | } 40 | .result-copy:hover { 41 | background: #2a2a2b; 42 | } 43 | .result-copy:active { 44 | box-shadow: 1px 2px 2px gray; 45 | } 46 | .form-row { 47 | display: flex; 48 | justify-content: space-between; 49 | margin: 10px 0; 50 | } 51 | .length-label { 52 | line-height: 2; 53 | } 54 | #length { 55 | width: 80px; 56 | text-align: left; 57 | outline: none; 58 | } 59 | #result { 60 | width: 331px; 61 | height: 42px; 62 | outline: none; 63 | padding-left: 1rem; 64 | font-size: 16px; 65 | } 66 | input { 67 | padding: 4px 8px; 68 | font-size: 15px; 69 | color: #555; 70 | background-color: #dedede;; 71 | border: 1px solid #ccc; 72 | border-radius: 25px; 73 | } 74 | button { 75 | width: 100%; 76 | padding: 8px; 77 | font-size: 1.1rem; 78 | letter-spacing: 1px; 79 | background: linear-gradient(45deg, #009688, #3F51B5); 80 | border: none; 81 | color: white; 82 | border-radius: 25px; 83 | margin-top: 5px; 84 | outline: none; 85 | } 86 | button:hover { 87 | background: linear-gradient(220deg, #009688, #3F51B5); 88 | } 89 | button:active { 90 | box-shadow: 1px 2px 2px gray; 91 | } 92 | .message { 93 | color: #e63333; 94 | margin-top: 5px; 95 | position: relative; 96 | top: 5px; 97 | height: 16px; 98 | text-align: center; 99 | } 100 | .message-success { 101 | color: #0c8990; 102 | } 103 | 104 | /* 105 | FOOTER 106 | ---------------*/ 107 | footer { 108 | position: absolute; 109 | bottom: 0; 110 | text-align: center; 111 | left: 50%; 112 | transform: translateX(-50%); 113 | color: #808080; 114 | font-size: 0.95rem; 115 | } 116 | footer p { 117 | letter-spacing: 1px; 118 | } 119 | footer a { 120 | color: #0c8990; 121 | text-decoration: none; 122 | } 123 | footer a:hover { 124 | color: #3a56b1; 125 | } 126 | 127 | /* 128 | Mobile View 129 | -----------------*/ 130 | @media(max-width: 576px) { 131 | footer { 132 | font-size: 0.8rem; 133 | } 134 | .box { 135 | width: 320px; 136 | } 137 | #result { 138 | width: 303px; 139 | } 140 | } -------------------------------------------------------------------------------- /pet-finder/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Pet Finder 8 | 9 | 10 | 11 |
12 |
13 |

Pet Finder

14 |

Not sure which dog breed to select?

15 |

Select a dog breed from the dropdown below to get their pictures

16 |
17 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /pet-finder/pet-finder-v2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Pet Selector 8 | 9 | 10 | 11 |
12 |
13 |

Pet Finder

14 |
15 | 18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pet-finder/pet-finder-v2/script.js: -------------------------------------------------------------------------------- 1 | const slideshow = document.querySelector(".slideshow") 2 | const breedsContainer = document.querySelector(".breeds") 3 | 4 | async function start() { 5 | try { 6 | const response = await fetch("https://dog.ceo/api/breeds/list/all") 7 | const data = await response.json() 8 | createBreedList(data) 9 | } catch (e) { 10 | console.log("There was a problem fetching the data.") 11 | } 12 | } 13 | 14 | start() 15 | 16 | function createBreedList(data) { 17 | const breedsArray = Object.keys(data.message) 18 | breedsArray.forEach(item => { 19 | breedsContainer.innerHTML += `` 20 | }) 21 | } 22 | 23 | breedsContainer.addEventListener("change", loadByBreed) 24 | 25 | async function loadByBreed(e) { 26 | const breed = e.target.value 27 | if (breed != "Select") { 28 | const response = await fetch(`https://dog.ceo/api/breed/${breed}/images`) 29 | const data = await response.json() 30 | //console.log(data) 31 | createSlideshow(data.message) 32 | } 33 | } 34 | 35 | function createSlideshow(images) { 36 | let randomImage = Math.floor(Math.random() * images.length) 37 | slideshow.innerHTML = ` 38 |
` 39 | 40 | /* 41 | setInterval(() => { 42 | randomImage = Math.floor(Math.random() * images.length) 43 | slideshow.innerHTML = ` 44 |
` 45 | }, 3000) 46 | */ 47 | } 48 | -------------------------------------------------------------------------------- /pet-finder/pet-finder-v2/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Kumbh+Sans:wght@400;700&display=swap"); 2 | 3 | body { 4 | font-family: "Kumbh Sans", sans-serif; 5 | color: #333; 6 | } 7 | 8 | * { 9 | margin: 0; 10 | padding: 0; 11 | box-sizing: border-box; 12 | } 13 | .app { 14 | height: 100vh; 15 | width: 100vw; 16 | display: flex; 17 | flex-direction: column; 18 | } 19 | .header { 20 | text-align: center; 21 | padding: 2rem 0; 22 | } 23 | .title { 24 | margin: 1rem 0; 25 | } 26 | .breed-container { 27 | margin-top: 1rem; 28 | } 29 | .breeds { 30 | padding: 5px 10px; 31 | font-size: 1rem; 32 | } 33 | .slideshow { 34 | background: #333; 35 | flex: 1; 36 | overflow: hidden; 37 | } 38 | .slide { 39 | height: 100%; 40 | width: 100%; 41 | background-repeat: no-repeat; 42 | background-position: top center; 43 | background-size: contain; 44 | } 45 | -------------------------------------------------------------------------------- /pet-finder/script.js: -------------------------------------------------------------------------------- 1 | let dropDown = document.querySelector(".breeds") 2 | let imageContainer = document.querySelector(".slideshow") 3 | 4 | async function getPetName() { 5 | let response = await fetch("https://dog.ceo/api/breeds/list/all") 6 | let data = await response.json() 7 | return data 8 | } 9 | 10 | async function getPetImage(pet) { 11 | let response = await fetch(`https://dog.ceo/api/breed/${pet}/images/random`) 12 | let data = await response.json() 13 | return data 14 | } 15 | 16 | function addPetNamesToHtml() { 17 | getPetName().then(data => { 18 | let breeds = Object.keys(data.message) 19 | breeds.forEach(item => { 20 | dropDown.innerHTML += `` 21 | }) 22 | }) 23 | } 24 | 25 | addPetNamesToHtml() 26 | 27 | dropDown.addEventListener("change", e => { 28 | imageContainer.innerHTML = `

Loading...

` 29 | getPetImage(e.target.value).then(data => { 30 | let url = data.message 31 | imageContainer.innerHTML = `` 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /pet-finder/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Kumbh+Sans:wght@400;700&display=swap"); 2 | 3 | body { 4 | font-family: "Kumbh Sans", sans-serif; 5 | color: #333; 6 | } 7 | 8 | * { 9 | margin: 0; 10 | padding: 0; 11 | box-sizing: border-box; 12 | } 13 | .app { 14 | height: 95vh; 15 | } 16 | .footer { 17 | text-align: center; 18 | } 19 | .header { 20 | text-align: center; 21 | padding: 2rem 0; 22 | } 23 | .title { 24 | margin: 1rem 0; 25 | } 26 | .subtitle { 27 | margin: 10px 0; 28 | } 29 | .subtitle--muted { 30 | color: gray; 31 | font-weight: 400; 32 | } 33 | .breed-container { 34 | margin-top: 1rem; 35 | } 36 | .breeds { 37 | padding: 5px 10px; 38 | font-size: 1rem; 39 | } 40 | .slideshow p { 41 | text-align: center; 42 | } 43 | .slideshow img { 44 | max-height: 500px; 45 | max-width: 900px; 46 | margin: 0 auto; 47 | display: block; 48 | } 49 | -------------------------------------------------------------------------------- /quiz-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Quiz App 8 | 9 | 10 | 11 | 12 |
13 |

JavaScript Quiz

14 |
15 |
16 |
17 |
18 |
19 | Score: 0 20 |
21 |

22 |
    23 |
24 |

25 |
26 |
27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /quiz-app/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | VARIABLES 3 | -------------------*/ 4 | const question = document.querySelector(".question") 5 | const options = document.querySelector(".options") 6 | const score = document.querySelector(".score") 7 | const message = document.querySelector(".message") 8 | const indicator = document.querySelector(".indicator") 9 | const questions = { 10 | "Which of the following is used to request and load data Asynchronously?": [["SQL", "Ajax", "JSON", "Bootstrap"], "Ajax"], 11 | "Which of the following is not an inbuilt array function in JavaScript?": [["filter", "forEach", "map", "set"], "set"], 12 | "You want to store an Array called 'items' to local storage. How will you convert it?": [["JSON.stringify(items)", "items.indexOf()", "Object.keys(items)", "item.toString()"], "JSON.stringify(items)"], 13 | "Which property references the DOM object that dispatched an event?": [["self", "object", "target", "source"], "target"], 14 | "How does a function create a closure?": [["It reloads the document whenever the value changes", "It returns a reference to a variable in its parent scope", "It completes execution without returning", "It copies a local variable to the global scope"], "It returns a reference to a variable in its parent scope"], 15 | "How is a forEach statement different from a for statement?": [["Only a for statement uses a callback function", "A for statement is generic, but a forEach statement can be used only with an array", "Only a forEach statement lets you specify your own iterator.", "A forEach statement is generic, but a for statement ca be used only with an array"], "A for statement is generic, but a forEach statement can be used only with an array"] 16 | } 17 | let scoreValue = 0 18 | let indicatorWidth = 0 19 | 20 | /* 21 | EVENT LISTENERS 22 | ----------------------*/ 23 | document.addEventListener("DOMContentLoaded", loadFirstQuestion) 24 | options.addEventListener("click", onOptionSelect) 25 | 26 | /* 27 | FUNCTIONS 28 | -----------------*/ 29 | 30 | // Load the first question on page load 31 | function loadFirstQuestion() { 32 | // load question 33 | let firstQuestion = Object.keys(questions)[0] 34 | question.textContent = firstQuestion 35 | // load options 36 | let firstOptions = Object.values(questions)[0][0] 37 | firstOptions.forEach(function(item) { 38 | let li = document.createElement("li") 39 | li.textContent = item 40 | options.appendChild(li) 41 | }) 42 | } 43 | 44 | // compare the clicked option with correct answer & update the score 45 | function onOptionSelect(e) { 46 | let optionSelected = e.target.textContent 47 | let questionDisplayed = e.target.parentElement.previousElementSibling.textContent 48 | let correctAnswer = questions[questionDisplayed][1] 49 | if (optionSelected == correctAnswer) { 50 | incrementScore() 51 | loadNextQuestion() 52 | } else { 53 | loadNextQuestion() 54 | } 55 | } 56 | 57 | // load next question 58 | function loadNextQuestion() { 59 | let questionDisplayed = question.textContent 60 | let questionsArray = Object.keys(questions) 61 | let currentQuestionIndex = questionsArray.indexOf(questionDisplayed) 62 | let nextIndex = currentQuestionIndex + 1 63 | if (nextIndex < questionsArray.length) { 64 | let nextQuestion = questionsArray[nextIndex] 65 | question.textContent = nextQuestion 66 | loadOptions(nextQuestion) 67 | // updating the progress bar 68 | indicatorWidth += 100 / (questionsArray.length - 1) 69 | if (indicatorWidth <= 100) { 70 | indicator.style.width = indicatorWidth.toString() + "%" 71 | } 72 | } else { 73 | // if all questions loaded - load the first question and display the final score 74 | let lastScore = scoreValue 75 | resetScore() 76 | options.innerHTML = "" 77 | loadFirstQuestion() 78 | let messageString = "You scored: " + `${lastScore}` + "/" + `${questionsArray.length}` 79 | message.textContent = messageString 80 | hideMessage() 81 | // resetting the progress bar 82 | indicatorWidth = 0 83 | indicator.style.width = indicatorWidth.toString() + "%" 84 | } 85 | } 86 | 87 | // load options for a given question 88 | function loadOptions(ques) { 89 | let quesOptions = questions[ques][0] 90 | options.innerHTML = "" 91 | quesOptions.forEach(function(item) { 92 | let li = document.createElement("li") 93 | li.textContent = item 94 | options.appendChild(li) 95 | }) 96 | } 97 | 98 | // increment and update the score 99 | function incrementScore() { 100 | scoreValue++ 101 | score.textContent = scoreValue 102 | } 103 | 104 | //reset score 105 | function resetScore() { 106 | scoreValue = 0 107 | score.textContent = scoreValue 108 | } 109 | 110 | // hide message 111 | function hideMessage() { 112 | setTimeout(function() { 113 | message.textContent = "" 114 | }, 3500) 115 | } 116 | -------------------------------------------------------------------------------- /quiz-app/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | body { 6 | margin: 0; 7 | font-size: 18px; 8 | background: #be93c5; 9 | background: linear-gradient(to right, #7bc6cc, #be93c5); 10 | font-family: "Nunito", sans-serif; 11 | } 12 | * { 13 | box-sizing: border-box; 14 | } 15 | header p { 16 | text-align: center; 17 | font-size: 3rem; 18 | font-weight: 700; 19 | background: linear-gradient(to right, #000046, #1cb5e0); 20 | background-clip: text; 21 | -webkit-background-clip: text; 22 | -webkit-text-fill-color: transparent; 23 | } 24 | main { 25 | height: 70vh; 26 | display: flex; 27 | justify-content: center; 28 | align-items: center; 29 | } 30 | .main-container { 31 | width: 650px; 32 | padding: 1rem; 33 | position: relative; 34 | } 35 | .score-container { 36 | width: 100px; 37 | padding: 5px; 38 | margin-left: auto; 39 | background: #fff1a9; 40 | display: none; 41 | } 42 | .question { 43 | background: #36556d; 44 | color: white; 45 | padding: 1rem; 46 | margin: 10px 0; 47 | line-height: 1.3; 48 | height: 70px; 49 | display: flex; 50 | align-items: center; 51 | border-radius: 2px; 52 | font-weight: 600; 53 | } 54 | .options { 55 | list-style: none; 56 | padding: 0; 57 | margin: 5px 0; 58 | } 59 | .options li { 60 | margin: 5px 0; 61 | padding: 5px; 62 | background: #efefef; 63 | padding-left: 1rem; 64 | cursor: pointer; 65 | border-radius: 2px; 66 | } 67 | .options li:hover { 68 | background: lightyellow; 69 | } 70 | .options li:active { 71 | outline: 2px solid #36556d; 72 | } 73 | .message { 74 | height: 25px; 75 | text-align: center; 76 | color: rgb(8, 85, 49); 77 | font-weight: bold; 78 | } 79 | .indicator { 80 | height: 5px; 81 | width: 0%; 82 | background: white; 83 | border-radius: 10px; 84 | } 85 | footer { 86 | text-align: center; 87 | font-size: 16px; 88 | } 89 | footer a { 90 | text-decoration: none; 91 | color: #36556d; 92 | } 93 | footer a:hover { 94 | text-decoration: underline; 95 | } 96 | -------------------------------------------------------------------------------- /quotes-machine/.prettierignore: -------------------------------------------------------------------------------- 1 | script.js -------------------------------------------------------------------------------- /quotes-machine/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Quote Machine 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /quotes-machine/script.js: -------------------------------------------------------------------------------- 1 | let quotes = [ "Little by little, one travels far.", 2 | "Whatever you are, be a good one.", 3 | "Be prepared, someday your chance will come.", 4 | "Be the change you want to see in the world.", 5 | "I don't think of all the misery but of the beauty that still remains.", 6 | "Happiness only real when shared.", 7 | "The core of man's spirit comes from new experiences.", 8 | "The virture lies in the struggle not in the prize.", 9 | "Our life is what our thoughts make of it.", 10 | "How we spend our days is, of course, how we spend our lives.", 11 | "Happiness does not consist in pastimes and amusements but in virtuous activities.", 12 | "There are two things to aim at in life: first, to get what you want; and, after that, to enjoy it. Only the wisest of mankind achieve the second.", 13 | "Nobody made a greater mistake than he who did nothing because he could do only a little.", 14 | "Action is the antidoe to despair.", 15 | "If you want to achieve widespread impact and lasting value, be bold.", 16 | "Our main business is not to see what lies dimly at a distance but to do what lies clearly at hand.", 17 | "With the new day comes new strength and new thoughts.", 18 | "A strong positive self-image, is the best possible preparation for success.", 19 | "What one can be one must be.", 20 | "It is never too late to be what you might have been.", 21 | "Even the smallest victory is never to be taken for granted. Each victory must be applauded, because it is so easy not to battle at all, to just accept and call that acceptance inevitable.", 22 | "You can't ever reach perfection, but you can believe in an asymptote toward which you are ceaselessly striving.", 23 | "The greatest discovery of any generation is that a human can alter his life by altering his attitude.", 24 | "The most important thing about art is to work. Nothing else matters except sitting down every day and trying.", 25 | "Forget about being an expert or a professional, and wear your amateurism (your heart, your love) on your sleeve. Share what you love, and the people who love the same things will find you.", 26 | "Doubt kills more dreams than failure ever will.", 27 | "If it's important to you, you will find a way. If not, you will find an excuse.", 28 | "It is not the mountains we conquer but ourselves.", 29 | "There is no greater agony than bearing an untold story inside you.", 30 | "Don't wait. The time will never be just right.", 31 | "Most people work hard and spend their health trying to achieve wealth and once they retire, they spend their wealth trying to get back their health.", 32 | "One man practicing sportsmanship is better than a hundred teaching it.", 33 | "Success each day should be judged by the seeds sown, not the harvest reaped.", 34 | "We are the creative force of our life, and through our own decisions rather than our conditions, if we carefully learn to do certain things, we can accomplish those goals.", 35 | "He whose life has a why can bear almost any how.", 36 | "Set your house in perfect order before you criticize the world.", 37 | "Pursue what is meaningful (not what is expedient)." 38 | ]; 39 | let authors = ["J.R.R. Tolkien", "Abraham Lincoln", "Abraham Lincoln", "Mahatma Gandhi", "Anne Frank", "Christopher Mcandles", 40 | "Christopher Mcandles", "Richard Monckton Milnes", "Marcus Aurelius", "Annie Dillard", "Aristrotle", 41 | "Logan Pearsall Smith", "Edmund Burke", "Joan Baez", "Howard Schultz", "Thomas Carlyle", 42 | "Eleanor Roosevelt", "Kamal Ravikant", "Abraham Maslow", "George Eliot", "Audre Lorde", "Paul Kalanithi", 43 | "William James", "Steven Pressfield", "Austin Kleon", "Suzy Kassem", "Ryan Blair", "Edmund Hillary", 44 | "Maya Angelou", "Napoleon Hill", "Unknown", "Knute Rockne", "John C. Maxwell", "Stephen Covey", 45 | "Friedrich Nietzshe", "Jordan B. Peterson", "Jordan B. Peterson" 46 | ]; 47 | 48 | // console.log(quotes.length, authors.length) 49 | 50 | let newQuote = document.getElementById('new-quote'), 51 | quoteDiv = document.getElementById('text'), 52 | authorDiv = document.getElementById('author'), 53 | random, quote, author; 54 | 55 | 56 | document.addEventListener('DOMContentLoaded', loadQuote); 57 | newQuote.addEventListener('click', loadQuote); 58 | 59 | function loadQuote() { 60 | random = Math.floor(Math.random() * quotes.length); 61 | quote = quotes[random]; 62 | author = authors[random]; 63 | author = "- "+author; 64 | quoteDiv.textContent = quote; 65 | authorDiv.textContent = author; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /quotes-machine/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=PT+Sans:ital@1&display=swap'); 2 | @import url('https://fonts.googleapis.com/css2?family=PT+Sans+Narrow:wght@700&display=swap'); 3 | body { 4 | margin: 0; 5 | font-family: 'PT Sans', sans-serif; 6 | background-color: #eec0c6; 7 | background-image: linear-gradient(315deg, #eec0c6 0%, #7ee8fa 74%); 8 | color: #323B3C; 9 | } 10 | * { 11 | box-sizing: border-box; 12 | } 13 | main { 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | height: 100vh; 18 | } 19 | #quote-box { 20 | min-height: 300px; 21 | width: 500px; 22 | box-shadow: 1px 2px 4px #9e9e9e; 23 | padding: 1rem; 24 | background: white; 25 | } 26 | #text { 27 | font-family: 'PT Sans Narrow', sans-serif; 28 | min-height: 150px; 29 | margin-bottom: 1rem; 30 | padding: 1rem; 31 | text-align: left; 32 | font-size: 1.5rem; 33 | line-height: 1.4; 34 | } 35 | #author { 36 | text-align: right; 37 | margin-bottom: 2rem; 38 | height: 20px; 39 | padding-right: 2rem; 40 | font-size: 1.15rem; 41 | } 42 | .button-row { 43 | display: flex; 44 | justify-content: space-around; 45 | line-height: 2; 46 | } 47 | #tweet-quote { 48 | font-size: 1rem; 49 | color: #0558a2; 50 | } 51 | #new-quote { 52 | font-size: 1rem; 53 | border: none; 54 | padding: 10px 20px; 55 | cursor: pointer; 56 | border-radius: 2px; 57 | outline: none; 58 | background: #CCE8E5; 59 | color: #516163; 60 | text-transform: uppercase; 61 | letter-spacing: 1px; 62 | 63 | } 64 | #new-quote:active { 65 | box-shadow: 1px 1px 2px gray; 66 | } 67 | 68 | @media (max-width: 576px) { 69 | #quote-box { 70 | width: 336px; 71 | } 72 | 73 | } 74 | 75 | /* 76 | FOOTER 77 | ---------------*/ 78 | footer { 79 | position: absolute; 80 | bottom: 0; 81 | left: 50%; 82 | transform: translateX(-50%); 83 | } 84 | footer p { 85 | font-family: consolas; 86 | color: #6f6f6f; 87 | } 88 | footer a { 89 | text-decoration: none; 90 | color: #0087ff; 91 | } 92 | footer a:hover { 93 | color: #0558a2; 94 | text-decoration: underline; 95 | } 96 | 97 | @media (max-width: 576px) { 98 | footer p { 99 | font-size: 0.9rem; 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /scroll-progress/script.js: -------------------------------------------------------------------------------- 1 | const progressBar = document.querySelector('.progress') 2 | let windowHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight 3 | 4 | 5 | window.addEventListener('scroll', () => { 6 | let windowScroll = document.documentElement.scrollTop 7 | let scroll = (windowScroll / windowHeight) * 100 8 | progressBar.style.width = scroll + '%' 9 | }) -------------------------------------------------------------------------------- /scroll-progress/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap'); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | body { 9 | height: 100vh; 10 | font-family: "Raleway", sans-serif; 11 | background: white; 12 | color: black; 13 | } 14 | 15 | .content { 16 | padding: 4rem; 17 | } 18 | 19 | @media (max-width: 576px) { 20 | .content { 21 | padding: 2rem; 22 | } 23 | } 24 | 25 | section { 26 | box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px; 27 | margin: 2rem 0; 28 | padding: 20px; 29 | border-radius: 5px; 30 | 31 | p { 32 | line-height: 1.5; 33 | } 34 | } 35 | 36 | section h1 { 37 | margin: 10px 0; 38 | } 39 | 40 | .scroll-indicator { 41 | position: fixed; 42 | top: 0; 43 | left: 0; 44 | height: 8px; 45 | width: 100%; 46 | } 47 | 48 | .scroll-indicator .progress { 49 | height: 100%; 50 | width: 0; 51 | border-radius: 3px; 52 | background: linear-gradient(30deg, #21d4fd, #b721ff); 53 | transition: width 0.1s; 54 | } 55 | -------------------------------------------------------------------------------- /social-media-counter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Social Media Counter 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 17 |
0
18 |

Likes

19 |
20 |
21 | 22 |
0
23 |

Followers

24 |
25 |
26 | 27 |
0
28 |

Connections

29 |
30 |
31 |
32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /social-media-counter/script.js: -------------------------------------------------------------------------------- 1 | const counters = document.querySelectorAll('.counter'); 2 | const speed = 250; 3 | 4 | counters.forEach(item => { 5 | function updateCount() { 6 | // + will convert the string (data-attribute and innerText(0)) to number 7 | const target = +item.getAttribute('data-target'); 8 | const count = +item.innerText; 9 | 10 | const inc = target / speed; 11 | 12 | if(count < target) { 13 | item.innerText = count + inc; 14 | // run the updateCount every 1ms 15 | setTimeout(updateCount, 1); 16 | } 17 | else { 18 | item.innerText = target; 19 | } 20 | } 21 | updateCount(); 22 | }) -------------------------------------------------------------------------------- /social-media-counter/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Quicksand', sans-serif; 3 | background: #161819; 4 | color: #8EF2FF; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | * { 11 | box-sizing: border-box; 12 | margin: 0; 13 | padding: 0; 14 | } 15 | .counters { 16 | border: 2px solid #E36E40; 17 | border-radius: 2px; 18 | padding: 1rem; 19 | } 20 | .container { 21 | display: flex; 22 | } 23 | .counter-block { 24 | padding: 1rem; 25 | text-align: center; 26 | min-width: 200px; 27 | } 28 | .counter-block:not(:last-child) { 29 | border-right: 2px solid rgb(90, 90, 90); 30 | } 31 | .counter { 32 | margin: 10px 0; 33 | font-size: 1.5rem; 34 | } 35 | @media (max-width: 576px) { 36 | .container { 37 | flex-direction: column; 38 | } 39 | .counter-block:not(:last-child) { 40 | border: 0; 41 | border-bottom: 2px solid rgb(90, 90, 90); 42 | } 43 | } 44 | footer { 45 | position: absolute; 46 | bottom: 10px; 47 | color: white; 48 | } 49 | footer a { 50 | color: #8EF2FF; 51 | text-decoration: none; 52 | } 53 | footer a:hover { 54 | color: rgb(90, 149, 156); 55 | } -------------------------------------------------------------------------------- /speed-typing-game/README.md: -------------------------------------------------------------------------------- 1 | > Click on the link below to view the App 2 | 3 | # [Speed Typing Lite](https://aman-maharshi.github.io/vanilla-js/speed-typing-game/) 4 | 5 | ### A typing game to improve users typing speed and accuracy, written in JavaScript and laid out using the CSS library Bootstrap. 6 | 7 | Features: 8 | * Ability to select among three different difficulty levels - Easy, Medium, Hard 9 | * Score counter which increments after every successful word match 10 | * Result indicator showing current game status 11 | * Time counter to keep track of passed time in seconds 12 | * Ability to resest the score and restart the game -------------------------------------------------------------------------------- /speed-typing-game/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Speed Typing 9 | 10 | 11 |
12 |

Speed Typing Lite

13 |
14 |
15 |
16 |
17 |

Type the following word in seconds:

18 |

19 |
20 | 21 | 26 |
27 | 28 |

29 | 30 | 31 |
32 |
33 |

Time Left: 0

34 |
35 |
36 |

Score: 0

37 |
38 | 41 |
42 | 43 | 44 |
45 |
46 |
47 |
Instructions
48 |

Type each word in the given amount of time to score. To play again, just type the current word. Your score will reset.

49 |
50 |
51 |
52 |
53 |
54 |
55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /speed-typing-game/script.js: -------------------------------------------------------------------------------- 1 | /* TODO: 2 | - show high score, store it in local storage 3 | - load random words from an API 4 | */ 5 | 6 | // VARIABLES 7 | let words = ['magic', 'journey', 'travel', 'explore', 'life', 8 | 'experience', 'happiness', 'gratitude', 'discipline', 'exercise', 9 | 'workout', 'friendship', 'practice', 'routine', 'morning', 'reading', 10 | 'books', 'education', 'amour', 'delibrate', 'protein', 'partner', 11 | 'empathy', 'concert', 'patience', 'humor', 'resilience', 'confidence', 12 | 'consistency', 'appreciation', 'literature', 'meaning', 'humble', 13 | 'province', 'flight', 'alchemy', 'intense', 'adorable', 'swoon', 'stunning', 14 | 'sensational', 'provocative', 'apocalypse', 'compliance', 'meticulous', 15 | 'replicate', 'relentless', 'pursuit', 'proactive', 'astounding', 16 | 'delightful', 'legitimate', 'mesmerizing', 'polarizing', 'validate']; 17 | 18 | const levels = { 19 | easy: 5, 20 | medium: 3, 21 | hard: 2 22 | }; 23 | let currentLevel = levels.easy; 24 | let timeCount = currentLevel + 1, scoreCount = 0, isPlaying, wordDisplayed; 25 | 26 | let currentWord = document.querySelector('#current-word'), 27 | inputWord = document.querySelector('#input-word'), 28 | time = document.querySelector('#seconds'), 29 | timeLeft = document.querySelector('#time-left'), 30 | score = document.querySelector('#score'), 31 | message = document.querySelector('#message'), 32 | difficultyLevel = document.querySelector('#difficulty'); 33 | 34 | 35 | // EVENT LISTENERS 36 | window.addEventListener('load', init); 37 | inputWord.addEventListener('input', startMatch); 38 | difficultyLevel.addEventListener('change', changeLevel); 39 | 40 | 41 | // FUCNTIONS 42 | function init() { 43 | time.textContent = currentLevel; 44 | showWord(); 45 | // call the countdown function every second 46 | setInterval(countdown, 1000); 47 | // cheking the game status every 0.1s 48 | setInterval(checkStatus, 100); 49 | } 50 | function showWord() { 51 | let randomIndex = Math.floor(Math.random() * words.length); 52 | wordDisplayed = words[randomIndex]; 53 | currentWord.textContent = wordDisplayed; 54 | } 55 | function countdown() { 56 | if (timeCount > 0) { 57 | // console.log(timeCount); 58 | timeCount--; 59 | timeLeft.textContent = timeCount; 60 | } else if(timeCount === 0) { 61 | isPlaying = false; 62 | } 63 | } 64 | function checkStatus() { 65 | if(!isPlaying && timeCount === 0) { 66 | message.textContent = 'Time Up!!'; 67 | scoreCount = 0 68 | message.className = 'mt-3 text-danger'; 69 | } 70 | } 71 | function startMatch() { 72 | if (this.value === wordDisplayed) { 73 | isPlaying = true; 74 | message.textContent = 'Correct!!' 75 | message.className = 'mt-3 text-success' 76 | this.value = '' 77 | scoreCount++; 78 | score.textContent = scoreCount; 79 | timeCount = currentLevel + 1; // will reset the clock to start the countdown again 80 | showWord(); 81 | } 82 | } 83 | function changeLevel() { 84 | let level = this.options[this.selectedIndex].value; 85 | if (level === 'Medium') { 86 | inputWord.focus(); 87 | scoreCount = 0; 88 | message.textContent = ''; 89 | isPlaying = true; 90 | currentLevel = levels.medium; 91 | time.textContent = currentLevel; 92 | timeCount = currentLevel + 1; 93 | startMatch(); 94 | } 95 | if (level === 'Hard') { 96 | inputWord.focus(); 97 | scoreCount = 0; 98 | message.textContent = ''; 99 | isPlaying = true; 100 | currentLevel = levels.hard; 101 | time.textContent = currentLevel; 102 | timeCount = currentLevel + 1; 103 | startMatch(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tags-drag-and-drop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tags Drag and Drop 8 | 9 | 10 | 11 |
12 |
13 |

Create tags and place them in different boxes by dragging

14 |

Add new tag

15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |

Box One

26 |
27 |
28 |
29 |

Box Two

30 |
31 |
32 |
33 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tags-drag-and-drop/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | INITIALIZAITON 3 | */ 4 | let tags = ["HTML", "CSS", "JavaScript"] 5 | 6 | let tagsContainer = document.querySelector(".tags__container") 7 | let addTagInput = document.querySelector(".add-tag__input") 8 | let addTagBtn = document.querySelector(".add-tag__btn") 9 | 10 | let draggables = document.querySelectorAll(".tag") 11 | let containers = document.querySelectorAll(".box") 12 | 13 | addTagBtn.addEventListener("click", addNewTag) 14 | 15 | // add new tag to the array 16 | function addNewTag() { 17 | let newTagContent = addTagInput.value 18 | if (newTagContent != "") { 19 | tags.push(newTagContent) 20 | showTags() 21 | } 22 | addTagInput.value = "" 23 | } 24 | 25 | // build tag DOM Elements from the array and show them 26 | function showTags() { 27 | let tagString = "" 28 | tags.forEach(item => { 29 | tagString += `
${item}
` 30 | }) 31 | tagsContainer.innerHTML = tagString 32 | draggables = document.querySelectorAll(".tag") 33 | implementDrag() 34 | } 35 | 36 | // show the initial tags 37 | showTags() 38 | 39 | /* 40 | DRAG FUNCTIONALITY 41 | */ 42 | 43 | function implementDrag() { 44 | let draggedFrom, draggedTo 45 | draggables.forEach(item => { 46 | item.addEventListener("dragstart", () => { 47 | item.classList.add("dragging") 48 | draggedFrom = item.parentElement 49 | }) 50 | item.addEventListener("dragend", () => { 51 | item.classList.remove("dragging") 52 | draggedTo = item.parentElement 53 | if (draggedFrom != draggedTo) { 54 | // removing the moved item from the array 55 | tags.splice(tags.indexOf(item.textContent), 1) 56 | } 57 | }) 58 | }) 59 | 60 | containers.forEach(item => { 61 | item.addEventListener("dragover", e => { 62 | e.preventDefault 63 | const draggingItem = document.querySelector(".dragging") 64 | item.appendChild(draggingItem) 65 | }) 66 | }) 67 | } 68 | 69 | // reload btn 70 | document.querySelector(".refresh").addEventListener("click", () => { 71 | location.reload() 72 | }) 73 | -------------------------------------------------------------------------------- /tags-drag-and-drop/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Ysabeau:wght@400;600&display=swap'); 2 | 3 | body { 4 | font-family: 'Ysabeau', sans-serif; 5 | } 6 | * { 7 | margin: 0; 8 | padding: 0; 9 | box-sizing: border-box; 10 | } 11 | body { 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | flex-direction: column; 16 | } 17 | main { 18 | margin-top: 1rem; 19 | width: 80%; 20 | max-width: 800px; 21 | height: 90vh; 22 | } 23 | .add-tag { 24 | border-radius: 3px; 25 | padding: 1rem; 26 | border-bottom: 2px dashed lightgray; 27 | position: relative; 28 | } 29 | .add-tag h1 { 30 | margin-bottom: 1rem; 31 | } 32 | .add-tag button { 33 | padding: 7px 16px; 34 | font-size: 14px; 35 | font-weight: bold; 36 | background-color: #16243C; 37 | color: white; 38 | border: none; 39 | outline: none; 40 | cursor: pointer; 41 | border-radius: 5px; 42 | } 43 | 44 | .add-tag__input { 45 | padding: 7px; 46 | margin-top: 1rem; 47 | font-size: 14px; 48 | margin-right: 10px; 49 | border: 1px solid #16243C; 50 | border-radius: 5px; 51 | } 52 | .tags { 53 | padding: 1rem; 54 | border-bottom: 2px dashed lightgray; 55 | } 56 | .tags__container { 57 | padding: 1rem; 58 | display: flex; 59 | justify-content: space-around; 60 | min-height: 72px; 61 | } 62 | .boxes { 63 | padding: 2rem 1rem; 64 | display: flex; 65 | justify-content: space-around; 66 | text-align: center; 67 | } 68 | .box-one, 69 | .box-two { 70 | background: rgb(238, 238, 238); 71 | height: 250px; 72 | width: 200px; 73 | margin-bottom: 10px; 74 | border: 5px solid gray; 75 | border-top: none; 76 | display: flex; 77 | flex-direction: column; 78 | justify-content: flex-end; 79 | } 80 | .refresh { 81 | position: absolute; 82 | right: 1rem; 83 | bottom: 1rem; 84 | } 85 | 86 | footer a { 87 | color: #16243C; 88 | } 89 | 90 | /* JavaScript Triggered Classes */ 91 | .tag { 92 | background: lightblue; 93 | padding: 10px; 94 | box-shadow: 1px 1px 3px gray; 95 | cursor: move; 96 | } 97 | .dragging { 98 | opacity: 0.5; 99 | } 100 | -------------------------------------------------------------------------------- /text-to-speech/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /text-to-speech/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Text to Speech 10 | 11 | 12 | 13 |
14 |

15 | wave 16 | VoiceOver 17 |

18 |

19 | This app allows you to convert text to speech. Simply type your text, select a voice, and click play to hear the translation. 20 |

21 | 22 | 25 | 26 | 34 | 35 |
36 | 37 | 41 |
42 | 43 | 49 | 50 |

51 |
52 | 53 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /text-to-speech/script.js: -------------------------------------------------------------------------------- 1 | const voiceSelect = document.querySelector('#voiceSelect') 2 | const playButton = document.querySelector('#playButton') 3 | const textInput = document.querySelector('textarea') 4 | const error = document.querySelector('#error') 5 | 6 | let activeVoice = '' 7 | 8 | /* 9 | FUNCTIONS 10 | --------------*/ 11 | 12 | const populateVoiceList = () => { 13 | let voices = speechSynthesis.getVoices() 14 | // voices = voices.filter(item => item.lang === "en-GB") 15 | // console.log(voices) 16 | voices.forEach(voice => { 17 | const option = document.createElement('option') 18 | option.textContent = `${voice.name} (${voice.lang})` 19 | option.setAttribute('data-lang', voice.lang) 20 | option.setAttribute('data-name', voice.name) 21 | voiceSelect.appendChild(option) 22 | }) 23 | } 24 | 25 | const playText = () => { 26 | const text = textInput.value 27 | if (text) { 28 | const utterance = new SpeechSynthesisUtterance(text) 29 | if (activeVoice) { 30 | utterance.voice = activeVoice 31 | } 32 | speechSynthesis.speak(utterance) 33 | } else { 34 | error.textContent = 'Please enter text to read' 35 | setTimeout(() => { 36 | error.textContent = '' 37 | }, 3000) 38 | } 39 | } 40 | 41 | const handleVoiceChange = () => { 42 | const selectedVoice = voiceSelect.selectedOptions[0].getAttribute('data-name') 43 | const voices = speechSynthesis.getVoices() 44 | const voice = voices.find(voice => voice.name === selectedVoice) 45 | activeVoice = voice 46 | // const utterance = new SpeechSynthesisUtterance(textInput.value) 47 | // utterance.voice = voice 48 | // speechSynthesis.speak(utterance) 49 | } 50 | 51 | /* 52 | EVENT LISTENERS 53 | -------------------*/ 54 | document.addEventListener('DOMContentLoaded', () => { 55 | if (speechSynthesis.onvoiceschanged !== undefined) { 56 | speechSynthesis.onvoiceschanged = populateVoiceList 57 | } 58 | populateVoiceList() 59 | }) 60 | 61 | playButton.addEventListener('click', playText) 62 | 63 | voiceSelect.addEventListener('change', handleVoiceChange) 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /text-to-speech/speakerIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /text-to-speech/styles.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@200..1000&display=swap'); 2 | 3 | * { 4 | box-sizing: border-box; 5 | font-family: "Nunito", serif; 6 | } 7 | 8 | .main-container { 9 | width: 500px; 10 | } 11 | 12 | @media (max-width: 600px) { 13 | .main-container { 14 | width: 90vw; 15 | height: 95vh; 16 | } 17 | } 18 | 19 | .gradient-bg { 20 | background-image: linear-gradient(to top, #a8edea 0%, #fed6e3 100%); 21 | } 22 | 23 | .cta-bg { 24 | background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 25 | } -------------------------------------------------------------------------------- /text-to-speech/waveIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todos/README.md: -------------------------------------------------------------------------------- 1 | > Click on the link below to view the App 2 | 3 | # [toDos](https://aman-maharshi.github.io/vanilla-js/todos) 4 | ### simple todo list app with a lot of functionalities 5 | 6 | Current Features: 7 | * Add todo items 8 | * Remove selected item 9 | * Search through the todo list 10 | * Save todo items to loacal storage 11 | * Load items from local storage on page load 12 | 13 | Features to add: 14 | * Edit a list item without removing it 15 | * Rearrange todo items -------------------------------------------------------------------------------- /todos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | toDos 11 | 12 | 13 |
14 |
15 |
16 |
17 |

toDos

18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 |

What tasks are in your mind?

29 |
30 | 31 | 32 |
33 |
34 |
35 |

My Tasks

36 |
    37 |
    38 |
    39 | 40 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /todos/script.js: -------------------------------------------------------------------------------- 1 | // VARIABLES 2 | let todoItems = document.getElementById('todo-items'), 3 | todoInput = document.getElementById('todo-input'), 4 | todoForm = document.getElementById('todo-form'), 5 | todoSearch = document.getElementById('todo-search'); 6 | 7 | 8 | // EVENT LISTENERS 9 | todoItems.addEventListener('click', removeItem); 10 | todoForm.addEventListener('submit', addTodo); 11 | todoSearch.addEventListener('keyup', searchItem); 12 | document.addEventListener('DOMContentLoaded', loadItems); 13 | 14 | 15 | // FUNCTIONS 16 | function removeItem(e) { 17 | if(e.target.classList.contains('btn')) { 18 | let item = e.target.parentElement; 19 | // remove item from local storage 20 | removeFromLocalStorage(item); 21 | // remove item from DOM 22 | item.remove(); 23 | } 24 | } 25 | 26 | function addTodo(e) { 27 | e.preventDefault(); 28 | let todo = todoInput.value; 29 | 30 | if(todo != '') { 31 | // creating a list item 32 | let li = document.createElement('li'); 33 | li.className = 'list-group-item'; 34 | li.textContent = todo; 35 | 36 | // creating the 'X' button and appending it to 'li' 37 | let xBtn = document.createElement('button'); 38 | xBtn.className = 'btn btn-dark btn-sm float-right'; 39 | xBtn.textContent = 'X'; 40 | li.appendChild(xBtn); 41 | 42 | // appending the todo item to the list 43 | todoItems.appendChild(li); 44 | todoForm.reset(); 45 | 46 | // append the todo item to local storage array 47 | addToLocalStorage(todo); 48 | checkLocalStorage(); 49 | } 50 | } 51 | 52 | function searchItem() { 53 | let searchInput = todoSearch.value.toUpperCase(); 54 | let items = todoItems.getElementsByTagName('li'); 55 | for(let i = 0; i < items.length; i++) { 56 | let val = items[i].textContent.toUpperCase(); 57 | if(val.indexOf(searchInput) != -1) { 58 | items[i].style.display = 'block'; 59 | } 60 | else{ 61 | items[i].style.display = 'none'; 62 | } 63 | } 64 | } 65 | 66 | function addToLocalStorage(todo) { 67 | let storageItems = getFromLocalStorage(); 68 | storageItems.push(todo); 69 | localStorage.setItem('todo', JSON.stringify(storageItems)); 70 | } 71 | 72 | function getFromLocalStorage() { 73 | let items = localStorage.getItem('todo') 74 | if(items === null) { 75 | items = []; 76 | } 77 | else { 78 | items = JSON.parse(items); 79 | } 80 | return items; 81 | } 82 | 83 | function removeFromLocalStorage(todo) { 84 | let liValue = todo.firstChild.textContent; 85 | let storageArray = getFromLocalStorage(); 86 | storageArray.forEach(function(item, index) { 87 | if(item === liValue) { 88 | storageArray.splice(index,1); 89 | localStorage.setItem('todo', JSON.stringify(storageArray)); 90 | } 91 | checkLocalStorage(); 92 | }) 93 | } 94 | // load todo items from local storage 95 | function loadItems() { 96 | todoInput.focus(); 97 | let items = getFromLocalStorage(); 98 | checkLocalStorage(); 99 | items.forEach(function(item){ 100 | if(item != ""){ 101 | // create li 102 | let li = document.createElement('li'); 103 | li.className = 'list-group-item'; 104 | li.textContent = item; 105 | 106 | //create 'X' button 107 | let btn = document.createElement('button'); 108 | btn.className = 'btn btn-dark btn-sm float-right'; 109 | btn.textContent = 'X'; 110 | 111 | li.appendChild(btn); 112 | todoItems.appendChild(li); 113 | } 114 | }); 115 | } 116 | function checkLocalStorage() { 117 | let arr = getFromLocalStorage(); 118 | if(arr.length != 0) { 119 | todoItems.parentElement.style.display = 'block'; 120 | } 121 | else { 122 | todoItems.parentElement.style.display = 'none'; 123 | } 124 | } -------------------------------------------------------------------------------- /todos/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Karla|Roboto|Lato'); 2 | 3 | body { 4 | font-family: 'Karla', sans-serif; 5 | } 6 | @media (max-width: 575px) { 7 | input[type="submit"] { 8 | margin-top: 1em; 9 | width: 20%; 10 | } 11 | } 12 | @media (min-width: 1000px) { 13 | input[type="submit"] { 14 | width: 10%; 15 | } 16 | } 17 | h3 { 18 | font-size: 1.4rem; 19 | } 20 | #todo-items { 21 | max-height: 47vh; 22 | overflow-y: scroll; 23 | } 24 | footer { 25 | position: absolute; 26 | bottom: 0; 27 | width: 100%; 28 | } 29 | @media (max-width: 770px) { 30 | footer { 31 | display: none; 32 | } 33 | } 34 | @media (max-height: 700px) { 35 | footer { 36 | display: none; 37 | } 38 | } -------------------------------------------------------------------------------- /typing-effect/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Typing Effect 10 | 11 | 12 |
    13 |

    I am a  

    14 |
    15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /typing-effect/script.js: -------------------------------------------------------------------------------- 1 | const typedTextSpan = document.querySelector('.typed-text'); 2 | const cursorSpan = document.querySelector('.cursor'); 3 | 4 | const textArray = ['Web Developer', 'Musician', 'Youtuber']; 5 | const typingDelay = 150; 6 | const erasingDelay = 90; 7 | const newTextDelay = 2000; 8 | let textArrayIndex = 0; 9 | let charIndex = 0; 10 | 11 | function type() { 12 | if(!(cursorSpan.classList.contains('typing'))) { 13 | cursorSpan.classList.add('typing'); 14 | } 15 | // if last letter of a word is not typed 16 | if(charIndex < textArray[textArrayIndex].length) { 17 | typedTextSpan.textContent += textArray[textArrayIndex].charAt(charIndex); 18 | charIndex++; 19 | setTimeout(type, typingDelay); 20 | } 21 | else { 22 | cursorSpan.classList.remove('typing'); 23 | setTimeout(erase, newTextDelay); 24 | } 25 | } 26 | 27 | function erase() { 28 | if(!(cursorSpan.classList.contains('typing'))) { 29 | cursorSpan.classList.add('typing'); 30 | } 31 | // if string is not entirely erased 32 | if(charIndex > 0) { 33 | typedTextSpan.textContent = textArray[textArrayIndex].substring(0, charIndex - 1); 34 | charIndex--; 35 | setTimeout(erase, erasingDelay); 36 | } 37 | else { 38 | cursorSpan.classList.remove('typing'); 39 | textArrayIndex++; 40 | if(textArrayIndex >= textArray.length) { 41 | textArrayIndex = 0; 42 | } 43 | setTimeout(type, typingDelay + 1100); 44 | } 45 | } 46 | 47 | document.addEventListener('DOMContentLoaded', () =>{ 48 | if(textArray.length) { 49 | setTimeout(type, newTextDelay + 250); 50 | } 51 | }) -------------------------------------------------------------------------------- /typing-effect/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --cream: #EAECEF; 3 | --black: #3E4147; 4 | --orange: #CC4425; 5 | } 6 | * { 7 | margin: 0; 8 | padding: 0; 9 | box-sizing: border-box; 10 | } 11 | body { 12 | font-family: 'Jost', sans-serif; 13 | background: var(--cream); 14 | color: var(--black); 15 | } 16 | section { 17 | height: 90vh; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | } 22 | section p { 23 | font-size: 4rem; 24 | font-weight: 500; 25 | } 26 | .typed-text { 27 | color: var(--orange); 28 | } 29 | .cursor { 30 | width: 3px; 31 | background: var(--black); 32 | display: inline-block; 33 | margin-left: 2px; 34 | animation: blink 1s infinite; 35 | } 36 | .cursor.typing { 37 | animation: none; 38 | } 39 | @keyframes blink { 40 | 0% { background: var(--black); } 41 | 50% { background: var(--cream); } 42 | 100% { background: var(--black); } 43 | } 44 | @media (max-width: 576px) { 45 | section p { 46 | font-size: 2rem; 47 | } 48 | } 49 | footer { 50 | display: flex; 51 | justify-content: center; 52 | align-items: center; 53 | height: 10vh; 54 | } 55 | footer a { 56 | color: var(--orange); 57 | text-decoration: none; 58 | } -------------------------------------------------------------------------------- /typing-suggestions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Typing Suggestions 9 | 10 | 11 |

    Start Typing to get Suggestions

    12 |
    13 | 14 | 15 |
    16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /typing-suggestions/script.js: -------------------------------------------------------------------------------- 1 | const inputBox = document.querySelector(".input-box") 2 | const results = document.querySelector(".results") 3 | let suggestedItems 4 | 5 | async function getData() { 6 | const response = await fetch("https://dog.ceo/api/breeds/list/all") 7 | const data = await response.json() 8 | return data 9 | } 10 | 11 | function findMatchingBreed(breedToMatch, inputBreeds) { 12 | return inputBreeds.filter(place => { 13 | const regex = new RegExp(breedToMatch, "gi") 14 | return place.match(regex) 15 | }) 16 | } 17 | 18 | inputBox.addEventListener("keyup", e => { 19 | getData().then(item => { 20 | const breeds = Object.keys(item.message) 21 | let matchingResult = [] 22 | let userInput = e.target.value 23 | 24 | if (userInput.length) { 25 | matchingResult = findMatchingBreed(userInput, breeds) 26 | } else { 27 | matchingResult = [] 28 | results.style.display = "block" 29 | } 30 | let suggestionsHTML = matchingResult 31 | .map(item => { 32 | return `
  • ${item}
  • ` 33 | }) 34 | .join(" ") 35 | results.innerHTML = suggestionsHTML 36 | handleSuggestionClick(document.querySelectorAll(".results li")) 37 | }) 38 | }) 39 | 40 | function handleSuggestionClick(element) { 41 | element.forEach(item => { 42 | item.addEventListener("click", e => { 43 | const clickedText = e.target.innerText 44 | inputBox.value = clickedText 45 | results.style.display = "none" 46 | }) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /typing-suggestions/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Arvo&display=swap"); 2 | body { 3 | margin: 0; 4 | font-family: "Arvo", serif; 5 | background: #eaecef; 6 | } 7 | .title { 8 | text-align: center; 9 | font-size: 1.5rem; 10 | color: #cc4425; 11 | } 12 | .container { 13 | padding: 1rem; 14 | text-align: center; 15 | max-width: 500px; 16 | margin: 1rem auto; 17 | display: flex; 18 | flex-direction: column; 19 | position: relative; 20 | } 21 | .input-box { 22 | font-size: 1rem; 23 | padding: 7px 10px; 24 | outline: none; 25 | border: 1px solid lightgray; 26 | color: #3e4147; 27 | } 28 | .results { 29 | position: absolute; 30 | top: 50px; 31 | left: 20px; 32 | padding: 0; 33 | margin: 0; 34 | z-index: 5; 35 | list-style: none; 36 | text-align: left; 37 | width: 180px; 38 | background: white; 39 | } 40 | .results li { 41 | line-height: 1.5; 42 | margin: 5px 0; 43 | padding-left: 10px; 44 | border-bottom: 1px solid rgb(211, 211, 211); 45 | cursor: pointer; 46 | } 47 | .results li:hover { 48 | background: rgb(236, 236, 236); 49 | } 50 | footer { 51 | text-align: center; 52 | position: absolute; 53 | bottom: 1.5rem; 54 | left: 0; 55 | width: 100%; 56 | font-size: 0.9rem; 57 | color: #3e4147; 58 | } 59 | footer a { 60 | color: #cc4425; 61 | text-decoration: none; 62 | } 63 | @media (max-width: 768px) { 64 | footer { 65 | display: none; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /weather-app/README.md: -------------------------------------------------------------------------------- 1 | ### Weather App 2 | 3 | > serach for a city to live weather details 4 | 5 | API Used - [Weather API - OpenWeatherMap](https://openweathermap.org/api) 6 | -------------------------------------------------------------------------------- /weather-app/app.js: -------------------------------------------------------------------------------- 1 | const apiKey = "16f00dd6edca31d9934c7fd9cdfba77c" 2 | const baseUrl = "https://api.openweathermap.org/data/2.5/weather?" 3 | 4 | const form = document.querySelector(".inputForm") 5 | 6 | form.addEventListener("submit", handleSubmit) 7 | 8 | function handleSubmit(e) { 9 | e.preventDefault() 10 | const city = document.querySelector(".inputCity").value 11 | fetchWeatherData(city) 12 | } 13 | 14 | async function fetchWeatherData(city) { 15 | const apiResponse = await fetch(`${baseUrl}q=${city}&units=metric&APPID=${apiKey}`) 16 | const weatherData = apiResponse.json() 17 | weatherData.then(data => { 18 | //console.log(data) 19 | document.querySelector(".city").textContent = data.name 20 | document.querySelector(".country").textContent = `, ${data.sys.country}` 21 | document.querySelector(".temprature").textContent = `${Math.round(data.main.temp)}°c` 22 | document.querySelector(".sky").textContent = data.weather[0].main 23 | document.querySelector(".min-max").textContent = `${data.main.temp_min}°c / ${data.main.temp_max}°c` 24 | }) 25 | } 26 | 27 | document.addEventListener("DOMContentLoaded", function() { 28 | fetchWeatherData("Delhi") 29 | }) 30 | -------------------------------------------------------------------------------- /weather-app/img/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-maharshi/vanilla-js/d0fadc7e5b025b27ca583b6b6530d143d02baf70/weather-app/img/sky.jpg -------------------------------------------------------------------------------- /weather-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Weather App 8 | 9 | 10 | 11 |
    12 |
    13 | 14 | 15 |
    16 |
    17 |

    18 |

    19 |

    20 |

    21 |
    22 |
    23 | 24 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /weather-app/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700&display=swap"); 2 | * { 3 | box-sizing: border-box; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | body { 8 | color: #2c3e50; 9 | font-family: "Nanum Gothic", sans-serif; 10 | background: #8eaab6 url(./img/sky.jpg) no-repeat; 11 | height: 100vh; 12 | } 13 | main { 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | flex-direction: column; 18 | padding: 1rem; 19 | margin-top: 3rem; 20 | } 21 | .inputForm { 22 | display: flex; 23 | flex-direction: column; 24 | width: 85%; 25 | } 26 | .inputCity, 27 | .formBtn { 28 | font-size: 1rem; 29 | padding: 7px 14px; 30 | border: 1px solid rgb(187, 187, 187); 31 | border-radius: 25px; 32 | outline: none; 33 | font-family: "Nanum Gothic", sans-serif; 34 | margin-bottom: 1rem; 35 | } 36 | .formBtn { 37 | background: #22a6b3; 38 | color: #ecf0f1; 39 | cursor: pointer; 40 | border: none; 41 | transition: 0.3s all ease; 42 | } 43 | .formBtn:hover { 44 | background: #16818c; 45 | } 46 | .result { 47 | padding: 1rem; 48 | margin-top: 2rem; 49 | } 50 | .result p { 51 | margin: 1rem 0; 52 | text-align: center; 53 | } 54 | .city, 55 | .country { 56 | font-size: 1.5rem; 57 | } 58 | .temprature { 59 | font-size: 6rem; 60 | font-weight: 700; 61 | } 62 | .sky { 63 | font-size: 1.3rem; 64 | } 65 | .min-max { 66 | font-size: 1.1rem; 67 | } 68 | footer { 69 | position: absolute; 70 | bottom: 1rem; 71 | width: 100%; 72 | text-align: center; 73 | } 74 | @media (min-width: 756px) { 75 | .inputForm { 76 | display: block; 77 | text-align: center; 78 | } 79 | .inputCity { 80 | margin-right: 1rem; 81 | width: 375px; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /weekly-budget-app/README.md: -------------------------------------------------------------------------------- 1 | # Weekly Budget App 2 | > App is hosted with GitHub Pages, click on the link below to access the App 3 | 4 | 5 | ## [Weekly Budget Tracker](https://aman-maharshi.github.io/udemy-modernjs-projects/weekly-budget-app-v2/) 6 | 7 | ### Features 8 | * Add a weekly budget 9 | * Add daily expences to the list and track your spendings 10 | * Once the user spends 50% of the budget, the remaining budget will be shown in orange color 11 | * Once the user spends 75% of the budget, the remaining budget will be shown in red color 12 | * Shows a warning when the entire budget is used up 13 | 14 | ### Future Updates 15 | * Funtionality to remove expences from the list 16 | -------------------------------------------------------------------------------- /weekly-budget-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Weekly Budget 10 | 11 | 12 |
    13 |

    My Weekly Budget

    14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 | 21 |
    22 | 23 |
    24 |
    25 |
    26 |
    27 |

    Add your daily expences:

    28 |
    29 |
    30 | 31 | 32 |
    33 |
    34 | 35 | 36 |
    37 | 38 |
    39 |
    40 |
    41 |
    42 |
    43 |

    Budget: 0

    44 |

    Left: 0

    45 |
    46 |

    My Expences:

    47 |
      48 | 49 | 50 |
    51 |
    52 |
    53 |
    54 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /weekly-budget-app/script.js: -------------------------------------------------------------------------------- 1 | // VARIABLES 2 | const enterBudget = document.querySelector('#enter-budget'), 3 | formFirst = document.querySelector('#form-first'), 4 | expenceName = document.querySelector('#name'), 5 | expenceAmount = document.querySelector('#amount'), 6 | formSecond = document.querySelector('#form-second'), 7 | myBudget = document.querySelector('#budget'), 8 | amountLeft = document.querySelector('#left'), 9 | expenceList = document.querySelector('#list'); 10 | 11 | let budget; 12 | 13 | 14 | // EVENT LISTENERS 15 | document.addEventListener('DOMContentLoaded', function() { 16 | enterBudget.focus(); 17 | }) 18 | formFirst.addEventListener('submit', init); 19 | formSecond.addEventListener('submit', addExpence); 20 | 21 | 22 | // FUNCTIONS 23 | function init(e) { 24 | e.preventDefault(); 25 | budget = enterBudget.value; 26 | if (budget !== '') { 27 | enableForm(); 28 | expenceName.focus(); 29 | myBudget.textContent = budget; 30 | amountLeft.textContent = budget; 31 | } 32 | else { 33 | document.getElementById('message-first').textContent = 'This field cant\'t be left empty'; 34 | setTimeout(function() { 35 | document.getElementById('message-first').style.display = 'none'; 36 | }, 2000) 37 | } 38 | } 39 | function enableForm() { 40 | expenceName.disabled = false; 41 | expenceAmount.disabled = false; 42 | document.getElementById('add-btn').disabled = false; 43 | // enterBudget.disabled = true; 44 | // document.getElementById('button-one').disabled = true; 45 | formFirst.parentElement.style.display = 'none'; 46 | } 47 | function addExpence(e) { 48 | e.preventDefault(); 49 | let name = expenceName.value, 50 | amount = expenceAmount.value; 51 | formSecond.reset(); 52 | expenceName.focus(); 53 | addToList(name, amount); 54 | updateBudget(amount); 55 | } 56 | function addToList(item, value) { 57 | if(item != '' && value != '') { 58 | let li = document.createElement('li'); 59 | let span = document.createElement('span'); 60 | li.className = 'list-group-item'; 61 | span.className = 'badge'; 62 | span.textContent = value; 63 | li.textContent = item; 64 | li.appendChild(span); 65 | expenceList.appendChild(li); 66 | } 67 | } 68 | function updateBudget(expence) { 69 | let total = budget - expence; 70 | budget = total; 71 | amountLeft.textContent = budget; 72 | checkBudget(budget); 73 | } 74 | function checkBudget(budget) { 75 | let initialBudget = Number(myBudget.textContent); 76 | 77 | if (budget <= 0.5 * initialBudget) { 78 | amountLeft.parentElement.className = 'bg-warning'; 79 | } 80 | if (budget <= 0.25 * initialBudget) { 81 | amountLeft.parentElement.className = 'bg-danger'; 82 | } 83 | if (budget <= 0) { 84 | amountLeft.parentElement.className = 'bg-danger'; 85 | document.getElementById('message-second').textContent = 'You have used up your entire budget for the week!' 86 | } 87 | } -------------------------------------------------------------------------------- /weekly-budget-app/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Ubuntu'); 2 | 3 | body { 4 | font-family: Ubuntu, sans-serif; 5 | min-height: 100vh; 6 | padding: 3% 0; 7 | background-image: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%); 8 | } 9 | header { 10 | margin-bottom: 1rem; 11 | } 12 | main { 13 | padding-top: 2rem; 14 | } 15 | main > .container { 16 | background-color: rgba(255,255,255,0.7); 17 | padding-top: 1em; 18 | padding-bottom: 1em; 19 | border-radius: 4px; 20 | margin: 1em auto; 21 | } 22 | .list-group { 23 | max-height: 30vh; 24 | overflow: scroll; 25 | } 26 | h1 { 27 | color: white; 28 | } 29 | h4 { 30 | padding: 0.5em; 31 | } 32 | 33 | /* STICKY FOOTER */ 34 | footer { 35 | position: fixed; 36 | width: 100%; 37 | bottom: 1em; 38 | color: rgb(117, 117, 117); 39 | letter-spacing: 1px; 40 | } 41 | footer a { 42 | color: rgb(59, 140, 172); 43 | } 44 | 45 | /* COLOR CLASSES */ 46 | .bg-info {background-color: lightskyblue;} 47 | .bg-success {background-color: lightgreen;} 48 | .bg-warning {background-color: orange;} 49 | .bg-danger {background-color: lightcoral;} 50 | 51 | /* FORMS AND MESSAGES */ 52 | #form-first { 53 | text-align: center; 54 | padding: 2rem 0; 55 | margin-bottom: 1rem; 56 | border-bottom: 1px solid #cecece; 57 | } 58 | #enter-budget, #button-one { 59 | height: 2em; 60 | text-align: center; 61 | font-size: 2rem; 62 | } 63 | #message-first, #message-second { 64 | color: red; 65 | font-size: 1.75rem; 66 | margin-top: 5px; 67 | } 68 | 69 | /* MEDIA QUERIES */ 70 | @media (max-width: 991px) { 71 | footer { 72 | display: none; 73 | } 74 | } 75 | @media (max-height: 650px) { 76 | footer { 77 | display: none; 78 | } 79 | } 80 | @media (min-width: 768px) { 81 | #enter-budget { 82 | width: 400px; 83 | } 84 | #button-one { 85 | margin-left: 1em; 86 | } 87 | } 88 | @media (min-width: 1200px) { 89 | .container { 90 | width: 1000px; 91 | } 92 | } -------------------------------------------------------------------------------- /weight-converter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Weight Converter 10 | 11 | 12 |
    13 |

    Weight Converter

    14 |
    15 |
    16 | 17 | 21 |
    22 | 23 |
    24 |
    25 |
    26 |
    27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /weight-converter/script.js: -------------------------------------------------------------------------------- 1 | 2 | let dropdown = document.getElementById('units'); 3 | let inputWeight = document.getElementById('weight'); 4 | let output = document.getElementById('outputbox'); 5 | 6 | 7 | inputWeight.addEventListener('input', convertPounds); 8 | dropdown.addEventListener('input', convertKg); 9 | 10 | 11 | // default behaviour 12 | function convertPounds() { 13 | if(dropdown.value ==='kg') { 14 | let weight = inputWeight.value; 15 | weight = (weight*2.2).toFixed(2) 16 | output.innerHTML = `${weight} pounds` 17 | } 18 | if (dropdown.value === 'pounds') { 19 | let weight = inputWeight.value; 20 | weight = (weight/2.2).toFixed(2) 21 | output.innerHTML = `${(weight)} kilograms` 22 | } 23 | } 24 | 25 | // when drop down is toggled 26 | function convertKg() { 27 | if (dropdown.value === 'pounds') { 28 | let weight = inputWeight.value; 29 | weight = (weight/2.2).toFixed(2) 30 | output.innerHTML = `${(weight)} kilograms` 31 | } 32 | if(dropdown.value ==='kg') { 33 | let weight = inputWeight.value; 34 | weight = (weight*2.2).toFixed(2) 35 | output.innerHTML = `${weight} pounds` 36 | } 37 | } 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /weight-converter/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Poppins:300,600'); 2 | 3 | body {font-family: 'Poppins', sans-serif; } 4 | 5 | .container { 6 | max-width: 600px; 7 | min-height: 300px; 8 | margin: 0 auto; 9 | background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%); 10 | padding: 1em; 11 | text-align: center; 12 | } 13 | h1 {font-size: 3em;} 14 | form > * { 15 | font-family: 'Poppins', sans-serif; 16 | font-size: 1.1em; 17 | } 18 | form input { 19 | margin-right: 1em; 20 | border: 2px solid gray; 21 | border-radius: 5px; 22 | width: 40%; 23 | } 24 | #outputbox { 25 | padding: 1em; 26 | } 27 | footer { 28 | font-size: 0.8rem; 29 | text-align: center; 30 | color: gray; 31 | } --------------------------------------------------------------------------------