├── 73-date-picker ├── after │ ├── .gitignore │ ├── package.json │ ├── styles.css │ ├── script.js │ └── index.html └── before │ ├── styles.css │ └── index.html ├── 74-shopping-cart ├── after │ ├── .gitignore │ ├── script.js │ ├── util │ │ ├── formatCurrency.js │ │ └── addGlobalEventListener.js │ ├── package.json │ ├── items.json │ ├── store.js │ ├── shoppingCart.js │ ├── store.html │ ├── index.html │ └── team.html └── before │ ├── items.json │ ├── index.html │ ├── team.html │ └── store.html ├── 43-simple-list ├── after │ ├── styles.css │ ├── index.html │ └── script.js └── before │ ├── styles.css │ └── index.html ├── 56-57-form-validation ├── after │ ├── thank-you.html │ ├── styles.css │ ├── index.html │ └── script.js └── before │ ├── thank-you.html │ ├── styles.css │ ├── index.html │ └── script.js ├── 44-45-modal ├── after │ ├── styles.css │ ├── index.html │ └── script.js └── before │ ├── styles.css │ ├── index.html │ └── script.js ├── 66-67-expand-collapse ├── after │ ├── script.js │ ├── styles.css │ └── index.html └── before │ ├── styles.css │ └── index.html ├── 53-midi-piano ├── before │ ├── script.js │ ├── styles.css │ └── index.html └── after │ ├── styles.css │ ├── index.html │ └── script.js ├── 65-advanced-todo-list ├── after │ ├── styles.css │ ├── index.html │ └── script.js └── before │ ├── styles.css │ └── index.html ├── 54-55-quiz ├── after │ ├── styles.css │ ├── script.js │ └── index.html └── before │ ├── styles.css │ ├── script.js │ └── index.html └── 68-google-maps-clone ├── script.js └── index.html /73-date-picker/after/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .cache -------------------------------------------------------------------------------- /74-shopping-cart/after/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .cache -------------------------------------------------------------------------------- /74-shopping-cart/after/script.js: -------------------------------------------------------------------------------- 1 | import { setupStore } from "./store.js" 2 | import { setupShoppingCart } from "./shoppingCart.js" 3 | 4 | setupStore() 5 | setupShoppingCart() 6 | -------------------------------------------------------------------------------- /43-simple-list/after/styles.css: -------------------------------------------------------------------------------- 1 | .list-item { 2 | cursor: pointer; 3 | width: min-content; 4 | } 5 | 6 | .list-item:hover { 7 | color: red; 8 | text-decoration: line-through; 9 | } -------------------------------------------------------------------------------- /43-simple-list/before/styles.css: -------------------------------------------------------------------------------- 1 | .list-item { 2 | cursor: pointer; 3 | width: min-content; 4 | } 5 | 6 | .list-item:hover { 7 | color: red; 8 | text-decoration: line-through; 9 | } -------------------------------------------------------------------------------- /74-shopping-cart/after/util/formatCurrency.js: -------------------------------------------------------------------------------- 1 | const formatter = new Intl.NumberFormat(undefined, { 2 | style: "currency", 3 | currency: "USD" 4 | }) 5 | 6 | export default function formatCurrency(amount) { 7 | return formatter.format(amount) 8 | } 9 | -------------------------------------------------------------------------------- /74-shopping-cart/after/util/addGlobalEventListener.js: -------------------------------------------------------------------------------- 1 | export default function addGlobalEventListener(type, selector, callback) { 2 | document.addEventListener(type, e => { 3 | if (e.target.matches(selector)) { 4 | callback(e) 5 | } 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /56-57-form-validation/after/thank-you.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Thank You 5 | 6 | 7 |

Thank you for creating a new account

8 | Back to sign up page 9 | 10 | -------------------------------------------------------------------------------- /56-57-form-validation/before/thank-you.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Thank You 5 | 6 | 7 |

Thank you for creating a new account

8 | Back to sign up page 9 | 10 | -------------------------------------------------------------------------------- /43-simple-list/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple List 5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /74-shopping-cart/after/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "current-project", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "parcel index.html team.html store.html", 8 | "build": "parcel build index.html team.html store.html" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "parcel-bundler": "^1.12.4" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /44-45-modal/after/styles.css: -------------------------------------------------------------------------------- 1 | #modal { 2 | display: none; 3 | position: absolute; 4 | top: 50%; 5 | left: 50%; 6 | transform: translate(-50%, -50%); 7 | border: 1px solid black; 8 | padding: 1rem; 9 | border-radius: .25rem; 10 | background-color: white; 11 | } 12 | 13 | #overlay { 14 | display: none; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | bottom: 0; 20 | background-color: rgba(0, 0, 0, .75); 21 | } -------------------------------------------------------------------------------- /44-45-modal/before/styles.css: -------------------------------------------------------------------------------- 1 | #modal { 2 | display: none; 3 | position: absolute; 4 | top: 50%; 5 | left: 50%; 6 | transform: translate(-50%, -50%); 7 | border: 1px solid black; 8 | padding: 1rem; 9 | border-radius: .25rem; 10 | background-color: white; 11 | } 12 | 13 | #overlay { 14 | display: none; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | bottom: 0; 20 | background-color: rgba(0, 0, 0, .75); 21 | } -------------------------------------------------------------------------------- /73-date-picker/after/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "current-project", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "parcel index.html", 8 | "build": "parcel build index.html" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "date-fns": "^2.16.1" 15 | }, 16 | "devDependencies": { 17 | "parcel-bundler": "^1.12.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /43-simple-list/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple List 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 | -------------------------------------------------------------------------------- /66-67-expand-collapse/after/script.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("click", e => { 2 | if (!e.target.matches(".expand-button")) return 3 | 4 | const card = e.target.closest(".card") 5 | const cardBody = card.querySelector(".card-body") 6 | 7 | cardBody.classList.toggle("show") 8 | if (e.target.innerText === "Expand") { 9 | e.target.innerText = "Collapse" 10 | } else { 11 | e.target.innerText = "Expand" 12 | } 13 | // e.target.innerText = e.target.innerText === "Expand" ? "Collapse" : "Expand" 14 | }) 15 | -------------------------------------------------------------------------------- /56-57-form-validation/after/styles.css: -------------------------------------------------------------------------------- 1 | .errors { 2 | background-color: hsl(0, 100%, 80%); 3 | border-radius: .5rem; 4 | padding: 10px; 5 | color: hsl(0, 50%, 20%); 6 | margin-bottom: 1rem; 7 | } 8 | 9 | #form { 10 | margin: 1rem 0; 11 | } 12 | 13 | .error-title { 14 | margin: 0; 15 | margin-bottom: .5rem; 16 | } 17 | 18 | .errors-list { 19 | margin: 0; 20 | padding-left: 15px; 21 | } 22 | 23 | .form-group { 24 | margin-bottom: 1rem; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: flex-start; 28 | } -------------------------------------------------------------------------------- /56-57-form-validation/before/styles.css: -------------------------------------------------------------------------------- 1 | .errors { 2 | background-color: hsl(0, 100%, 80%); 3 | border-radius: .5rem; 4 | padding: 10px; 5 | color: hsl(0, 50%, 20%); 6 | margin-bottom: 1rem; 7 | } 8 | 9 | #form { 10 | margin: 1rem 0; 11 | } 12 | 13 | .error-title { 14 | margin: 0; 15 | margin-bottom: .5rem; 16 | } 17 | 18 | .errors-list { 19 | margin: 0; 20 | padding-left: 15px; 21 | } 22 | 23 | .form-group { 24 | margin-bottom: 1rem; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: flex-start; 28 | } -------------------------------------------------------------------------------- /44-45-modal/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modal 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 26 | 27 | -------------------------------------------------------------------------------- /44-45-modal/before/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | TODO: 2. Select the elements with the following IDs 3 | * modal 4 | * open-modal-btn 5 | * close-modal-btn 6 | * BONUS: overlay 7 | */ 8 | 9 | // TODO: 3. Create a click event listener for the open-modal-btn that adds the class "open" to the modal 10 | // BONUS: Also add the class "open" to the overlay 11 | 12 | // TODO: 4. Create a click event listener for the close-modal-btn that removes the class "open" from the modal 13 | // BONUS: Also remove the class "open" from the overlay 14 | 15 | // BONUS: Add a click event listener to the overlay that removes the class "open" from the modal and the overlay 16 | -------------------------------------------------------------------------------- /44-45-modal/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modal 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 27 | 28 | -------------------------------------------------------------------------------- /53-midi-piano/before/script.js: -------------------------------------------------------------------------------- 1 | const NOTE_DETAILS = [ 2 | { note: "C", key: "Z", frequency: 261.626 }, 3 | { note: "Db", key: "S", frequency: 277.183 }, 4 | { note: "D", key: "X", frequency: 293.665 }, 5 | { note: "Eb", key: "D", frequency: 311.127 }, 6 | { note: "E", key: "C", frequency: 329.628 }, 7 | { note: "F", key: "V", frequency: 349.228 }, 8 | { note: "Gb", key: "G", frequency: 369.994 }, 9 | { note: "G", key: "B", frequency: 391.995 }, 10 | { note: "Ab", key: "H", frequency: 415.305 }, 11 | { note: "A", key: "N", frequency: 440 }, 12 | { note: "Bb", key: "J", frequency: 466.164 }, 13 | { note: "B", key: "M", frequency: 493.883 } 14 | ] 15 | -------------------------------------------------------------------------------- /65-advanced-todo-list/after/styles.css: -------------------------------------------------------------------------------- 1 | #new-todo-form { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | #new-todo-form > * { 7 | margin: .15rem; 8 | } 9 | 10 | #list { 11 | padding: 0; 12 | } 13 | 14 | .list-item { 15 | list-style: none; 16 | } 17 | 18 | .list-item-label:hover, 19 | [data-list-item-checkbox]:hover { 20 | cursor: pointer; 21 | } 22 | 23 | .list-item-label:hover > [data-list-item-text] { 24 | color: #333; 25 | text-decoration: line-through; 26 | } 27 | 28 | [data-list-item-checkbox]:checked ~ [data-list-item-text] { 29 | text-decoration: line-through; 30 | color: #AAA; 31 | } 32 | 33 | [data-button-delete] { 34 | margin-left: .5rem; 35 | } 36 | 37 | button { 38 | cursor: pointer; 39 | } -------------------------------------------------------------------------------- /65-advanced-todo-list/before/styles.css: -------------------------------------------------------------------------------- 1 | #new-todo-form { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | #new-todo-form > * { 7 | margin: .15rem; 8 | } 9 | 10 | #list { 11 | padding: 0; 12 | } 13 | 14 | .list-item { 15 | list-style: none; 16 | } 17 | 18 | .list-item-label:hover, 19 | [data-list-item-checkbox]:hover { 20 | cursor: pointer; 21 | } 22 | 23 | .list-item-label:hover > [data-list-item-text] { 24 | color: #333; 25 | text-decoration: line-through; 26 | } 27 | 28 | [data-list-item-checkbox]:checked ~ [data-list-item-text] { 29 | text-decoration: line-through; 30 | color: #AAA; 31 | } 32 | 33 | [data-button-delete] { 34 | margin-left: .5rem; 35 | } 36 | 37 | button { 38 | cursor: pointer; 39 | } -------------------------------------------------------------------------------- /66-67-expand-collapse/before/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #DCDCDC; 3 | padding: 1rem; 4 | margin: 0; 5 | } 6 | 7 | .card { 8 | border: 1px solid black; 9 | margin-bottom: 1rem; 10 | background-color: white; 11 | } 12 | 13 | .card:last-child { 14 | margin-bottom: 0; 15 | } 16 | 17 | .card-header { 18 | display: flex; 19 | justify-content: space-between; 20 | border-bottom: 1px solid black; 21 | padding: .5rem; 22 | font-size: 2rem; 23 | align-items: center; 24 | margin-bottom: -1px; 25 | } 26 | 27 | .card-body { 28 | padding: 0 .5rem; 29 | height: 0; 30 | overflow: hidden; 31 | } 32 | 33 | .card-body.show { 34 | height: auto; 35 | padding: .5rem; 36 | } 37 | 38 | button { 39 | cursor: pointer; 40 | } -------------------------------------------------------------------------------- /43-simple-list/after/script.js: -------------------------------------------------------------------------------- 1 | // 1. Select all elements 2 | const form = document.querySelector("#new-item-form") 3 | const list = document.querySelector("#list") 4 | const input = document.querySelector("#item-input") 5 | 6 | // 2. When I submit the form add a new element 7 | form.addEventListener("submit", e => { 8 | e.preventDefault() 9 | 10 | // 1. Create a new item 11 | const item = document.createElement("div") 12 | item.innerText = input.value 13 | item.classList.add("list-item") 14 | 15 | // 2. Add that item to the list 16 | list.appendChild(item) 17 | 18 | // 3. Clear input 19 | input.value = "" 20 | 21 | // 4. Setup event listener to delete item when clicked 22 | item.addEventListener("click", () => { 23 | item.remove() 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /66-67-expand-collapse/after/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #DCDCDC; 3 | padding: 1rem; 4 | margin: 0; 5 | } 6 | 7 | .card { 8 | border: 1px solid black; 9 | margin-bottom: 1rem; 10 | background-color: white; 11 | } 12 | 13 | .card:last-child { 14 | margin-bottom: 0; 15 | } 16 | 17 | .card-header { 18 | display: flex; 19 | justify-content: space-between; 20 | border-bottom: 1px solid black; 21 | padding: .5rem; 22 | font-size: 2rem; 23 | align-items: center; 24 | margin-bottom: -1px; 25 | } 26 | 27 | .card-body { 28 | padding: 0 .5rem; 29 | height: 0; 30 | overflow: hidden; 31 | transition: height 150ms; 32 | } 33 | 34 | .card-body.show { 35 | height: auto; 36 | padding: .5rem; 37 | } 38 | 39 | button { 40 | cursor: pointer; 41 | } -------------------------------------------------------------------------------- /65-advanced-todo-list/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Advanced Todo List 5 | 6 | 7 | 8 | 11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 | 27 | 28 | -------------------------------------------------------------------------------- /54-55-quiz/after/styles.css: -------------------------------------------------------------------------------- 1 | .answer-item { 2 | list-style: lower-alpha; 3 | } 4 | 5 | .question-item { 6 | margin-bottom: 2rem; 7 | } 8 | 9 | .question-item.correct { 10 | color: green; 11 | } 12 | 13 | .question-item.incorrect { 14 | color: red; 15 | } 16 | 17 | .question-item:last-child { 18 | margin-bottom: 0; 19 | } 20 | 21 | .alert-title { 22 | font-weight: bold; 23 | margin-bottom: .5rem; 24 | font-size: 2rem; 25 | } 26 | 27 | #alert { 28 | font-size: 1.25rem; 29 | position: absolute; 30 | display: none; 31 | top: 50%; 32 | left: 50%; 33 | transform: translate(-50%, -50%); 34 | text-align: center; 35 | padding: 1rem; 36 | background-color: hsl(100, 80%, 80%); 37 | border-radius: .5rem; 38 | color: hsl(100, 80%, 20%); 39 | } 40 | 41 | #alert.active { 42 | display: block; 43 | } -------------------------------------------------------------------------------- /53-midi-piano/after/styles.css: -------------------------------------------------------------------------------- 1 | *, *::before, *::after { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | background-color: #143F6B; 7 | margin: 0; 8 | min-height: 100vh; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | 14 | .piano { 15 | display: flex; 16 | } 17 | 18 | .key { 19 | height: calc(var(--width) * 4); 20 | width: var(--width); 21 | } 22 | 23 | .white { 24 | --width: 100px; 25 | background-color: white; 26 | border: 1px solid #333; 27 | } 28 | 29 | .white.active { 30 | background-color: #CCC; 31 | } 32 | 33 | .black { 34 | --width: 60px; 35 | background-color: black; 36 | margin-left: calc(var(--width) / -2); 37 | margin-right: calc(var(--width) / -2); 38 | z-index: 2; 39 | } 40 | 41 | .black.active { 42 | background-color: #333; 43 | } -------------------------------------------------------------------------------- /54-55-quiz/before/styles.css: -------------------------------------------------------------------------------- 1 | .answer-item { 2 | list-style: lower-alpha; 3 | } 4 | 5 | .question-item { 6 | margin-bottom: 2rem; 7 | } 8 | 9 | .question-item.correct { 10 | color: green; 11 | } 12 | 13 | .question-item.incorrect { 14 | color: red; 15 | } 16 | 17 | .question-item:last-child { 18 | margin-bottom: 0; 19 | } 20 | 21 | .alert-title { 22 | font-weight: bold; 23 | margin-bottom: .5rem; 24 | font-size: 2rem; 25 | } 26 | 27 | #alert { 28 | font-size: 1.25rem; 29 | position: absolute; 30 | display: none; 31 | top: 50%; 32 | left: 50%; 33 | transform: translate(-50%, -50%); 34 | text-align: center; 35 | padding: 1rem; 36 | background-color: hsl(100, 80%, 80%); 37 | border-radius: .5rem; 38 | color: hsl(100, 80%, 20%); 39 | } 40 | 41 | #alert.active { 42 | display: block; 43 | } -------------------------------------------------------------------------------- /53-midi-piano/before/styles.css: -------------------------------------------------------------------------------- 1 | *, *::before, *::after { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | background-color: #143F6B; 7 | margin: 0; 8 | min-height: 100vh; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | 14 | .piano { 15 | display: flex; 16 | } 17 | 18 | .key { 19 | height: calc(var(--width) * 4); 20 | width: var(--width); 21 | } 22 | 23 | .white { 24 | --width: 100px; 25 | background-color: white; 26 | border: 1px solid #333; 27 | } 28 | 29 | .white.active { 30 | background-color: #CCC; 31 | } 32 | 33 | .black { 34 | --width: 60px; 35 | background-color: black; 36 | margin-left: calc(var(--width) / -2); 37 | margin-right: calc(var(--width) / -2); 38 | z-index: 2; 39 | } 40 | 41 | .black.active { 42 | background-color: #333; 43 | } -------------------------------------------------------------------------------- /65-advanced-todo-list/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Advanced Todo List 5 | 6 | 7 | 8 | 9 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 | 28 | 29 | -------------------------------------------------------------------------------- /53-midi-piano/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Midi Piano 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 | -------------------------------------------------------------------------------- /53-midi-piano/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Midi Piano 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 | -------------------------------------------------------------------------------- /68-google-maps-clone/script.js: -------------------------------------------------------------------------------- 1 | const MAPBOX_ACCESS_TOKEN = 2 | "pk.eyJ1Ijoid2ViZGV2c2ltcGxpZmllZCIsImEiOiJja2dyYTRqbW0weWl1MnJxaWF2dGR0ZHMwIn0.lU-OINCILi52P5N98qMbtA" 3 | 4 | navigator.geolocation.getCurrentPosition(successLocation, errorLocation, { 5 | enableHighAccuracy: true 6 | }) 7 | 8 | function setupMap(centerPosition) { 9 | const map = new mapboxgl.Map({ 10 | accessToken: MAPBOX_ACCESS_TOKEN, 11 | container: "map", 12 | style: "mapbox://styles/mapbox/streets-v11", 13 | center: centerPosition, 14 | zoom: 15 15 | }) 16 | 17 | const navigationControls = new mapboxgl.NavigationControl() 18 | map.addControl(navigationControls) 19 | 20 | const directionControls = new MapboxDirections({ 21 | accessToken: MAPBOX_ACCESS_TOKEN 22 | }) 23 | map.addControl(directionControls, "top-left") 24 | } 25 | 26 | function successLocation(position) { 27 | setupMap([position.coords.longitude, position.coords.latitude]) 28 | } 29 | 30 | function errorLocation() { 31 | setupMap([-2.24, 53.48]) 32 | } 33 | -------------------------------------------------------------------------------- /68-google-maps-clone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | Document 16 | 26 | 27 | 28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /44-45-modal/after/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | TODO: 2. Select the elements with the following IDs 3 | * modal 4 | * open-modal-btn 5 | * close-modal-btn 6 | * BONUS: overlay 7 | */ 8 | const modal = document.querySelector("#modal") 9 | const openModalButton = document.querySelector("#open-modal-btn") 10 | const closeModalButton = document.querySelector("#close-modal-btn") 11 | const overlay = document.querySelector("#overlay") 12 | 13 | // TODO: 3. Create a click event listener for the open-modal-btn that adds the class "open" to the modal 14 | // BONUS: Also add the class "open" to the overlay 15 | openModalButton.addEventListener("click", () => { 16 | modal.classList.add("open") 17 | overlay.classList.add("open") 18 | }) 19 | 20 | // TODO: 4. Create a click event listener for the close-modal-btn that removes the class "open" from the modal 21 | // BONUS: Also remove the class "open" from the overlay 22 | closeModalButton.addEventListener("click", closeModal) 23 | 24 | // BONUS: Add a click event listener to the overlay that removes the class "open" from the modal and the overlay 25 | overlay.addEventListener("click", closeModal) 26 | 27 | function closeModal() { 28 | modal.classList.remove("open") 29 | overlay.classList.remove("open") 30 | } 31 | -------------------------------------------------------------------------------- /74-shopping-cart/after/items.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Red", 5 | "category": "Primary Color", 6 | "priceCents": 1600, 7 | "imageColor": "F00" 8 | }, 9 | { 10 | "id": 2, 11 | "name": "Yellow", 12 | "category": "Primary Color", 13 | "priceCents": 2100, 14 | "imageColor": "FF0" 15 | }, 16 | { 17 | "id": 3, 18 | "name": "Blue", 19 | "category": "Primary Color", 20 | "priceCents": 1200, 21 | "imageColor": "00F" 22 | }, 23 | { 24 | "id": 4, 25 | "name": "Orange", 26 | "category": "Secondary Color", 27 | "priceCents": 1800, 28 | "imageColor": "F60" 29 | }, 30 | { 31 | "id": 5, 32 | "name": "Green", 33 | "category": "Secondary Color", 34 | "priceCents": 1600, 35 | "imageColor": "0F0" 36 | }, 37 | { 38 | "id": 6, 39 | "name": "Purple", 40 | "category": "Secondary Color", 41 | "priceCents": 2100, 42 | "imageColor": "60F" 43 | }, 44 | { 45 | "id": 7, 46 | "name": "Light Gray", 47 | "category": "Grayscale", 48 | "priceCents": 1200, 49 | "imageColor": "AAA" 50 | }, 51 | { 52 | "id": 8, 53 | "name": "Dark Gray", 54 | "category": "Grayscale", 55 | "priceCents": 1600, 56 | "imageColor": "333" 57 | } 58 | ] -------------------------------------------------------------------------------- /74-shopping-cart/before/items.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Red", 5 | "category": "Primary Color", 6 | "priceCents": 1600, 7 | "imageColor": "F00" 8 | }, 9 | { 10 | "id": 2, 11 | "name": "Yellow", 12 | "category": "Primary Color", 13 | "priceCents": 2100, 14 | "imageColor": "FF0" 15 | }, 16 | { 17 | "id": 3, 18 | "name": "Blue", 19 | "category": "Primary Color", 20 | "priceCents": 1200, 21 | "imageColor": "00F" 22 | }, 23 | { 24 | "id": 4, 25 | "name": "Orange", 26 | "category": "Secondary Color", 27 | "priceCents": 1800, 28 | "imageColor": "F60" 29 | }, 30 | { 31 | "id": 5, 32 | "name": "Green", 33 | "category": "Secondary Color", 34 | "priceCents": 1600, 35 | "imageColor": "0F0" 36 | }, 37 | { 38 | "id": 6, 39 | "name": "Purple", 40 | "category": "Secondary Color", 41 | "priceCents": 2100, 42 | "imageColor": "60F" 43 | }, 44 | { 45 | "id": 7, 46 | "name": "Light Gray", 47 | "category": "Grayscale", 48 | "priceCents": 1200, 49 | "imageColor": "AAA" 50 | }, 51 | { 52 | "id": 8, 53 | "name": "Dark Gray", 54 | "category": "Grayscale", 55 | "priceCents": 1600, 56 | "imageColor": "333" 57 | } 58 | ] -------------------------------------------------------------------------------- /54-55-quiz/before/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | TODO: 2. Select all elements needed 3 | * The form element (has the id `quiz-form`) 4 | * The answer inputs (have the class `answer`) 5 | * BONUS: The questions (have the class `question-item`) 6 | * BONUS: The alert (has the id `alert`) 7 | */ 8 | 9 | // TODO: 3. Create a submit event listener for the form that does the following. 10 | // 1. Prevent the default behaviour 11 | // 2. Get all selected answers (use the `checked` property on the input to determine if it is selected or not) 12 | // 3. Loop through the selected answer to see if they are correct or not (Check the value of the answer to see if it is the string "true") 13 | // 4. For each correct answer add the class `correct` to the parent with the class `question-item` and remove the class `incorrect`. 14 | // 5. For each incorrect answer add the class `incorrect` to the parent with the class `question-item` and remove the class `correct`. 15 | // 6. BONUS: Make sure unanswered questions show up as incorrect. The easiest way to do this is to add the incorrect class and removing the correct class from all question items before checking the correct answers 16 | // 7. BONUS: If all answers are correct show the element with the id `alert` and hide it after one second (look into setTimeout) (use the class active to show the alert and remove the class to hide it) 17 | -------------------------------------------------------------------------------- /56-57-form-validation/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Form Validation 5 | 6 | 15 | 16 | 17 | 18 |
19 |
20 |

Error

21 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | 45 |
46 | 47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /56-57-form-validation/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Form Validation 5 | 6 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |

Error

22 | 24 |
25 | 26 |
27 | 28 | 29 |
30 | 31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 |
42 | 46 |
47 | 48 | 49 |
50 | 51 | -------------------------------------------------------------------------------- /74-shopping-cart/after/store.js: -------------------------------------------------------------------------------- 1 | import items from "./items.json" 2 | import formatCurrency from "./util/formatCurrency.js" 3 | import addGlobalEventListener from "./util/addGlobalEventListener.js" 4 | import { addToCart } from "./shoppingCart.js" 5 | 6 | const storeItemTemplate = document.querySelector("#store-item-template") 7 | const storeItemContainer = document.querySelector("[data-store-container]") 8 | const IMAGE_URL = "https://dummyimage.com/420x260" 9 | 10 | export function setupStore() { 11 | if (storeItemContainer == null) return 12 | 13 | addGlobalEventListener("click", "[data-add-to-cart-button]", e => { 14 | const id = e.target.closest("[data-store-item]").dataset.itemId 15 | addToCart(parseInt(id)) 16 | }) 17 | 18 | items.forEach(renderStoreItem) 19 | } 20 | 21 | function renderStoreItem(item) { 22 | const storeItem = storeItemTemplate.content.cloneNode(true) 23 | 24 | const container = storeItem.querySelector("[data-store-item]") 25 | container.dataset.itemId = item.id 26 | 27 | const name = storeItem.querySelector("[data-name]") 28 | name.innerText = item.name 29 | 30 | const category = storeItem.querySelector("[data-category]") 31 | category.innerText = item.category 32 | 33 | const image = storeItem.querySelector("[data-image]") 34 | image.src = `${IMAGE_URL}/${item.imageColor}/${item.imageColor}` 35 | 36 | const price = storeItem.querySelector("[data-price]") 37 | price.innerText = formatCurrency(item.priceCents / 100) 38 | 39 | storeItemContainer.appendChild(storeItem) 40 | } 41 | -------------------------------------------------------------------------------- /56-57-form-validation/before/script.js: -------------------------------------------------------------------------------- 1 | // TODO: Select all elements needed 2 | // Use the HTML to figure out what classes/ids will work best for selecting each element 3 | 4 | // TODO: Create an event listener for when the form is submitted and do the following inside of it. 5 | // TODO: Create an array to store all error messages and clear any old error messages 6 | // TODO: Define the following validation checks with appropriate error messages 7 | // 1. Ensure the username is at least 6 characters long 8 | // 2. Ensure the password is at least 10 characters long 9 | // 3. Ensure the password and confirmation password match 10 | // 4. Ensure the terms checkbox is checked 11 | // TODO: If there are any errors then prevent the form from submitting and show the error messages 12 | 13 | // TODO: Define this function 14 | function clearErrors() { 15 | // Loop through all the children of the error-list element and remove them 16 | // IMPORTANT: This cannot be done with a forEach loop or a normal for loop since as you remove children it will modify the list you are looping over which will not work 17 | // I recommend using a while loop to accomplish this task 18 | // This is the trickiest part of this exercise so if you get stuck and are unable to progress you can also set the innerHTML property of the error-list to an empty string and that will also clear the children. I recommend trying to accomplish this with a while loop, though, for practice. 19 | // Also, make sure you remove the show class to the errors container 20 | } 21 | 22 | // TODO: Define this function 23 | function showErrors(errorMessages) { 24 | // Add each error to the error-list element 25 | // Make sure to use an li as the element for each error 26 | // Also, make sure you add the show class to the errors container 27 | } 28 | -------------------------------------------------------------------------------- /66-67-expand-collapse/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Expand/Collapse 5 | 6 | 7 | 8 |
9 |
10 | Title 1 11 | 12 |
13 |
14 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Id voluptate dolorem reprehenderit nisi doloribus? Dolor officiis praesentium aperiam dolorem sequi obcaecati iste corporis voluptatum accusamus. 15 |
16 |
17 |
18 |
19 | Title 2 20 | 21 |
22 |
23 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Id voluptate dolorem reprehenderit nisi doloribus? Dolor officiis praesentium aperiam dolorem sequi obcaecati iste corporis voluptatum accusamus. 24 |
25 |
26 |
27 |
28 | Title 3 29 | 30 |
31 |
32 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Id voluptate dolorem reprehenderit nisi doloribus? Dolor officiis praesentium aperiam dolorem sequi obcaecati iste corporis voluptatum accusamus. 33 |
34 |
35 |
36 |
37 | Title 4 38 | 39 |
40 |
41 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Id voluptate dolorem reprehenderit nisi doloribus? Dolor officiis praesentium aperiam dolorem sequi obcaecati iste corporis voluptatum accusamus. 42 |
43 |
44 | 45 | -------------------------------------------------------------------------------- /73-date-picker/after/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | margin-top: 1rem; 4 | display: flex; 5 | justify-content: center; 6 | } 7 | 8 | .date-picker-container { 9 | position: relative; 10 | display: inline-block; 11 | } 12 | 13 | .date-picker-button { 14 | cursor: pointer; 15 | } 16 | 17 | .date-picker { 18 | display: none; 19 | position: absolute; 20 | margin-top: 1rem; 21 | top: 100%; 22 | transform: translateX(-50%); 23 | left: 50%; 24 | padding: .5rem; 25 | border-radius: .5rem; 26 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06), 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); 27 | background-color: white; 28 | } 29 | 30 | .date-picker-header { 31 | display: flex; 32 | justify-content: space-between; 33 | font-weight: bold; 34 | font-size: .8rem; 35 | align-items: center; 36 | } 37 | 38 | .date-picker-grid { 39 | display: grid; 40 | gap: .5rem; 41 | grid-template-columns: repeat(7, 2rem); 42 | grid-auto-rows: 2rem; 43 | } 44 | 45 | .date-picker-grid > * { 46 | display: flex; 47 | justify-content: center; 48 | align-items: center; 49 | width: 100%; 50 | height: 100%; 51 | } 52 | 53 | .date-picker-grid-header { 54 | font-weight: bold; 55 | font-size: .75rem; 56 | color: #333; 57 | } 58 | 59 | .date-picker-grid-dates { 60 | color: #555; 61 | } 62 | 63 | .date-picker-other-month-date { 64 | color: #AAA; 65 | } 66 | 67 | .date-picker-grid-dates > .date { 68 | cursor: pointer; 69 | border-radius: .25rem; 70 | border: none; 71 | background: none; 72 | } 73 | 74 | .date-picker-grid-dates > *:hover { 75 | background-color: hsl(200, 100%, 80%); 76 | color: black; 77 | } 78 | 79 | .month-button { 80 | background: none; 81 | border: none; 82 | cursor: pointer; 83 | } 84 | 85 | .month-button:hover { 86 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); 87 | border-radius: .5rem; 88 | } -------------------------------------------------------------------------------- /73-date-picker/before/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | margin-top: 1rem; 4 | display: flex; 5 | justify-content: center; 6 | } 7 | 8 | .date-picker-container { 9 | position: relative; 10 | display: inline-block; 11 | } 12 | 13 | .date-picker-button { 14 | cursor: pointer; 15 | } 16 | 17 | .date-picker { 18 | display: none; 19 | position: absolute; 20 | margin-top: 1rem; 21 | top: 100%; 22 | transform: translateX(-50%); 23 | left: 50%; 24 | padding: .5rem; 25 | border-radius: .5rem; 26 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06), 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); 27 | background-color: white; 28 | } 29 | 30 | .date-picker-header { 31 | display: flex; 32 | justify-content: space-between; 33 | font-weight: bold; 34 | font-size: .8rem; 35 | align-items: center; 36 | } 37 | 38 | .date-picker-grid { 39 | display: grid; 40 | gap: .5rem; 41 | grid-template-columns: repeat(7, 2rem); 42 | grid-auto-rows: 2rem; 43 | } 44 | 45 | .date-picker-grid > * { 46 | display: flex; 47 | justify-content: center; 48 | align-items: center; 49 | width: 100%; 50 | height: 100%; 51 | } 52 | 53 | .date-picker-grid-header { 54 | font-weight: bold; 55 | font-size: .75rem; 56 | color: #333; 57 | } 58 | 59 | .date-picker-grid-dates { 60 | color: #555; 61 | } 62 | 63 | .date-picker-other-month-date { 64 | color: #AAA; 65 | } 66 | 67 | .date-picker-grid-dates > .date { 68 | cursor: pointer; 69 | border-radius: .25rem; 70 | border: none; 71 | background: none; 72 | } 73 | 74 | .date-picker-grid-dates > *:hover { 75 | background-color: hsl(200, 100%, 80%); 76 | color: black; 77 | } 78 | 79 | .month-button { 80 | background: none; 81 | border: none; 82 | cursor: pointer; 83 | } 84 | 85 | .month-button:hover { 86 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); 87 | border-radius: .5rem; 88 | } -------------------------------------------------------------------------------- /66-67-expand-collapse/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Expand/Collapse 5 | 6 | 7 | 8 | 9 |
10 |
11 | Title 1 12 | 13 |
14 |
15 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Id voluptate dolorem reprehenderit nisi doloribus? Dolor officiis praesentium aperiam dolorem sequi obcaecati iste corporis voluptatum accusamus. 16 |
17 |
18 |
19 |
20 | Title 2 21 | 22 |
23 |
24 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Id voluptate dolorem reprehenderit nisi doloribus? Dolor officiis praesentium aperiam dolorem sequi obcaecati iste corporis voluptatum accusamus. 25 |
26 |
27 |
28 |
29 | Title 3 30 | 31 |
32 |
33 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Id voluptate dolorem reprehenderit nisi doloribus? Dolor officiis praesentium aperiam dolorem sequi obcaecati iste corporis voluptatum accusamus. 34 |
35 |
36 |
37 |
38 | Title 4 39 | 40 |
41 |
42 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Id voluptate dolorem reprehenderit nisi doloribus? Dolor officiis praesentium aperiam dolorem sequi obcaecati iste corporis voluptatum accusamus. 43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /65-advanced-todo-list/after/script.js: -------------------------------------------------------------------------------- 1 | const form = document.querySelector("#new-todo-form") 2 | const todoInput = document.querySelector("#todo-input") 3 | const list = document.querySelector("#list") 4 | const template = document.querySelector("#list-item-template") 5 | const LOCAL_STORAGE_PREFIX = "ADVANCED_TODO_LIST" 6 | const TODOS_STORAGE_KEY = `${LOCAL_STORAGE_PREFIX}-todos` 7 | let todos = loadTodos() 8 | todos.forEach(renderTodo) 9 | 10 | list.addEventListener("change", e => { 11 | if (!e.target.matches("[data-list-item-checkbox]")) return 12 | 13 | const parent = e.target.closest(".list-item") 14 | const todoId = parent.dataset.todoId 15 | const todo = todos.find(t => t.id === todoId) 16 | todo.complete = e.target.checked 17 | saveTodos() 18 | }) 19 | 20 | list.addEventListener("click", e => { 21 | if (!e.target.matches("[data-button-delete]")) return 22 | 23 | const parent = e.target.closest(".list-item") 24 | const todoId = parent.dataset.todoId 25 | parent.remove() 26 | todos = todos.filter(todo => todo.id !== todoId) 27 | saveTodos() 28 | }) 29 | 30 | form.addEventListener("submit", e => { 31 | e.preventDefault() 32 | 33 | const todoName = todoInput.value 34 | if (todoName === "") return 35 | const newTodo = { 36 | name: todoName, 37 | complete: false, 38 | id: new Date().valueOf().toString() 39 | } 40 | todos.push(newTodo) 41 | renderTodo(newTodo) 42 | saveTodos() 43 | todoInput.value = "" 44 | }) 45 | 46 | function renderTodo(todo) { 47 | const templateClone = template.content.cloneNode(true) 48 | const listItem = templateClone.querySelector(".list-item") 49 | listItem.dataset.todoId = todo.id 50 | const textElement = templateClone.querySelector("[data-list-item-text]") 51 | textElement.innerText = todo.name 52 | const checkbox = templateClone.querySelector("[data-list-item-checkbox]") 53 | checkbox.checked = todo.complete 54 | list.appendChild(templateClone) 55 | } 56 | 57 | function loadTodos() { 58 | const todosString = localStorage.getItem(TODOS_STORAGE_KEY) 59 | return JSON.parse(todosString) || [] 60 | } 61 | 62 | function saveTodos() { 63 | localStorage.setItem(TODOS_STORAGE_KEY, JSON.stringify(todos)) 64 | } 65 | -------------------------------------------------------------------------------- /53-midi-piano/after/script.js: -------------------------------------------------------------------------------- 1 | const audioContext = new AudioContext() 2 | 3 | const NOTE_DETAILS = [ 4 | { note: "C", key: "Z", frequency: 261.626, active: false }, 5 | { note: "Db", key: "S", frequency: 277.183, active: false }, 6 | { note: "D", key: "X", frequency: 293.665, active: false }, 7 | { note: "Eb", key: "D", frequency: 311.127, active: false }, 8 | { note: "E", key: "C", frequency: 329.628, active: false }, 9 | { note: "F", key: "V", frequency: 349.228, active: false }, 10 | { note: "Gb", key: "G", frequency: 369.994, active: false }, 11 | { note: "G", key: "B", frequency: 391.995, active: false }, 12 | { note: "Ab", key: "H", frequency: 415.305, active: false }, 13 | { note: "A", key: "N", frequency: 440, active: false }, 14 | { note: "Bb", key: "J", frequency: 466.164, active: false }, 15 | { note: "B", key: "M", frequency: 493.883, active: false } 16 | ] 17 | 18 | document.addEventListener("keydown", e => { 19 | if (e.repeat) return 20 | const keyboardKey = e.code 21 | const noteDetail = getNoteDetail(keyboardKey) 22 | 23 | if (noteDetail == null) return 24 | 25 | noteDetail.active = true 26 | playNotes() 27 | }) 28 | 29 | document.addEventListener("keyup", e => { 30 | const keyboardKey = e.code 31 | const noteDetail = getNoteDetail(keyboardKey) 32 | 33 | if (noteDetail == null) return 34 | 35 | noteDetail.active = false 36 | playNotes() 37 | }) 38 | 39 | function getNoteDetail(keyboardKey) { 40 | return NOTE_DETAILS.find(n => `Key${n.key}` === keyboardKey) 41 | } 42 | 43 | function playNotes() { 44 | NOTE_DETAILS.forEach(n => { 45 | const keyElement = document.querySelector(`[data-note="${n.note}"]`) 46 | keyElement.classList.toggle("active", n.active) 47 | if (n.oscillator != null) { 48 | n.oscillator.stop() 49 | n.oscillator.disconnect() 50 | } 51 | }) 52 | 53 | const activeNotes = NOTE_DETAILS.filter(n => n.active) 54 | const gain = 1 / activeNotes.length 55 | activeNotes.forEach(n => { 56 | startNote(n, gain) 57 | }) 58 | } 59 | 60 | function startNote(noteDetail, gain) { 61 | const gainNode = audioContext.createGain() 62 | gainNode.gain.value = gain 63 | const oscillator = audioContext.createOscillator() 64 | oscillator.frequency.value = noteDetail.frequency 65 | oscillator.type = "sine" 66 | oscillator.connect(gainNode).connect(audioContext.destination) 67 | oscillator.start() 68 | noteDetail.oscillator = oscillator 69 | } 70 | -------------------------------------------------------------------------------- /54-55-quiz/after/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | TODO: 2. Select all elements needed 3 | * The form element (has the id `quiz-form`) 4 | * The answer inputs (have the class `answer`) 5 | * BONUS: The questions (have the class `question-item`) 6 | * BONUS: The alert (has the id `alert`) 7 | */ 8 | const form = document.getElementById("quiz-form") 9 | const answers = Array.from(document.querySelectorAll(".answer")) 10 | const questionItems = document.querySelectorAll(".question-item") 11 | const alert = document.querySelector("#alert") 12 | 13 | // TODO: 3. Create a submit event listener for the form that does the following. 14 | form.addEventListener("submit", e => { 15 | // 1. Prevent the default behaviour 16 | e.preventDefault() 17 | 18 | // 6. BONUS: Make sure unanswered questions show up as incorrect. The easiest way to do this is to add the incorrect class and removing the correct class from all question items before checking the correct answers 19 | questionItems.forEach(questionItem => { 20 | questionItem.classList.add("incorrect") 21 | questionItem.classList.remove("correct") 22 | }) 23 | 24 | // 2. Get all selected answers (use the `checked` property on the input to determine if it is selected or not) 25 | const checkedAnswers = answers.filter(answer => answer.checked) 26 | // 3. Loop through the selected answer to see if they are correct or not (Check the value of the answer to see if it is the string "true") 27 | checkedAnswers.forEach(answer => { 28 | const isCorrect = answer.value === "true" 29 | const questionItem = answer.closest(".question-item") 30 | 31 | // 4. For each correct answer add the class `correct` to the parent with the class `question-item` and remove the class `incorrect`. 32 | if (isCorrect) { 33 | questionItem.classList.add("correct") 34 | questionItem.classList.remove("incorrect") 35 | } else { 36 | // 5. For each incorrect answer add the class `incorrect` to the parent with the class `question-item` and remove the class `correct`. 37 | questionItem.classList.add("incorrect") 38 | questionItem.classList.remove("correct") 39 | } 40 | // 7. BONUS: If all answers are correct show the element with the id `alert` and hide it after one second (look into setTimeout) (use the class active to show the alert and remove the class to hide it) 41 | 42 | const allTrue = checkedAnswers.every(answer => answer.value === "true") 43 | const allAnswered = checkedAnswers.length === questionItems.length 44 | 45 | if (allTrue && allAnswered) { 46 | alert.classList.add("active") 47 | setTimeout(() => { 48 | alert.classList.remove("active") 49 | }, 1000) 50 | } 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /73-date-picker/after/script.js: -------------------------------------------------------------------------------- 1 | import { 2 | format, 3 | getUnixTime, 4 | fromUnixTime, 5 | addMonths, 6 | subMonths, 7 | startOfWeek, 8 | startOfMonth, 9 | endOfWeek, 10 | endOfMonth, 11 | eachDayOfInterval, 12 | isSameMonth, 13 | isSameDay 14 | } from "date-fns" 15 | 16 | const datePickerButton = document.querySelector(".date-picker-button") 17 | const datePicker = document.querySelector(".date-picker") 18 | const datePickerHeaderText = document.querySelector(".current-month") 19 | const previousMonthButton = document.querySelector(".prev-month-button") 20 | const nextMonthButton = document.querySelector(".next-month-button") 21 | const dateGrid = document.querySelector(".date-picker-grid-dates") 22 | let currentDate = new Date() 23 | 24 | datePickerButton.addEventListener("click", () => { 25 | datePicker.classList.toggle("show") 26 | const selectedDate = fromUnixTime(datePickerButton.dataset.selectedDate) 27 | currentDate = selectedDate 28 | setupDatePicker(selectedDate) 29 | }) 30 | 31 | function setDate(date) { 32 | datePickerButton.innerText = format(date, "MMMM do, yyyy") 33 | datePickerButton.dataset.selectedDate = getUnixTime(date) 34 | } 35 | 36 | function setupDatePicker(selectedDate) { 37 | datePickerHeaderText.innerText = format(currentDate, "MMMM - yyyy") 38 | setupDates(selectedDate) 39 | } 40 | 41 | function setupDates(selectedDate) { 42 | const firstWeekStart = startOfWeek(startOfMonth(currentDate)) 43 | const lastWeekEnd = endOfWeek(endOfMonth(currentDate)) 44 | const dates = eachDayOfInterval({ start: firstWeekStart, end: lastWeekEnd }) 45 | dateGrid.innerHTML = "" 46 | 47 | dates.forEach(date => { 48 | const dateElement = document.createElement("button") 49 | dateElement.classList.add("date") 50 | dateElement.innerText = date.getDate() 51 | if (!isSameMonth(date, currentDate)) { 52 | dateElement.classList.add("date-picker-other-month-date") 53 | } 54 | if (isSameDay(date, selectedDate)) { 55 | dateElement.classList.add("selected") 56 | } 57 | console.log(selectedDate) 58 | dateElement.addEventListener("click", () => { 59 | setDate(date) 60 | datePicker.classList.remove("show") 61 | }) 62 | 63 | dateGrid.appendChild(dateElement) 64 | }) 65 | } 66 | 67 | nextMonthButton.addEventListener("click", () => { 68 | const selectedDate = fromUnixTime(datePickerButton.dataset.selectedDate) 69 | currentDate = addMonths(currentDate, 1) 70 | setupDatePicker(selectedDate) 71 | }) 72 | 73 | previousMonthButton.addEventListener("click", () => { 74 | const selectedDate = fromUnixTime(datePickerButton.dataset.selectedDate) 75 | currentDate = subMonths(currentDate, 1) 76 | setupDatePicker(selectedDate) 77 | }) 78 | 79 | setDate(new Date()) 80 | -------------------------------------------------------------------------------- /73-date-picker/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Date Picker 5 | 6 | 16 | 17 | 18 |
19 | 20 |
21 |
22 | 23 |
October - 2020
24 | 25 |
26 |
27 |
Sun
28 |
Mon
29 |
Tue
30 |
Wed
31 |
Thu
32 |
Fri
33 |
Sat
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 |
74 | 75 | -------------------------------------------------------------------------------- /73-date-picker/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Date Picker 5 | 6 | 16 | 17 | 18 | 19 |
20 | 21 |
22 |
23 | 24 |
25 | 26 |
27 |
28 |
Sun
29 |
Mon
30 |
Tue
31 |
Wed
32 |
Thu
33 |
Fri
34 |
Sat
35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 |
74 |
75 | 76 | -------------------------------------------------------------------------------- /56-57-form-validation/after/script.js: -------------------------------------------------------------------------------- 1 | // TODO: Select all elements needed 2 | // Use the HTML to figure out what classes/ids will work best for selecting each element 3 | const form = document.querySelector("#form") 4 | const usernameInput = document.querySelector("#username") 5 | const passwordInput = document.querySelector("#password") 6 | const passwordConfirmInput = document.querySelector("#password-confirmation") 7 | const termsInput = document.querySelector("#terms") 8 | const errorsContainer = document.querySelector(".errors") 9 | const errorsList = document.querySelector(".errors-list") 10 | 11 | // TODO: Create an event listener for when the form is submitted and do the following inside of it. 12 | form.addEventListener("submit", e => { 13 | // TODO: Create an array to store all error messages and clear any old error messages 14 | const errorMessages = [] 15 | clearErrors() 16 | // TODO: Define the following validation checks with appropriate error messages 17 | // 1. Ensure the username is at least 6 characters long 18 | if (usernameInput.value.length < 6) { 19 | errorMessages.push("Username must be at least 6 characters") 20 | } 21 | // 2. Ensure the password is at least 10 characters long 22 | if (passwordInput.value.length < 10) { 23 | errorMessages.push("Password must be at least 10 characters") 24 | } 25 | // 3. Ensure the password and confirmation password match 26 | if (passwordConfirmInput.value !== passwordInput.value) { 27 | errorMessages.push("Passwords must match") 28 | } 29 | // 4. Ensure the terms checkbox is checked 30 | if (!termsInput.checked) { 31 | errorMessages.push("You must accept the terms") 32 | } 33 | // TODO: If there are any errors then prevent the form from submitting and show the error messages 34 | if (errorMessages.length > 0) { 35 | showErrors(errorMessages) 36 | e.preventDefault() 37 | } 38 | }) 39 | 40 | // TODO: Define this function 41 | function clearErrors() { 42 | // Loop through all the children of the error-list element and remove them 43 | // IMPORTANT: This cannot be done with a forEach loop or a normal for loop since as you remove children it will modify the list you are looping over which will not work 44 | // I recommend using a while loop to accomplish this task 45 | // This is the trickiest part of this exercise so if you get stuck and are unable to progress you can also set the innerHTML property of the error-list to an empty string and that will also clear the children. I recommend trying to accomplish this with a while loop, though, for practice. 46 | // Also, make sure you remove the show class to the errors container 47 | while (errorsList.children[0] != null) { 48 | errorsList.removeChild(errorsList.children[0]) 49 | } 50 | errorsContainer.classList.remove("show") 51 | } 52 | 53 | // TODO: Define this function 54 | function showErrors(errorMessages) { 55 | // Add each error to the error-list element 56 | // Make sure to use an li as the element for each error 57 | // Also, make sure you add the show class to the errors container 58 | errorMessages.forEach(errorMessage => { 59 | const li = document.createElement("li") 60 | li.innerText = errorMessage 61 | errorsList.appendChild(li) 62 | }) 63 | errorsContainer.classList.add("show") 64 | } 65 | -------------------------------------------------------------------------------- /74-shopping-cart/after/shoppingCart.js: -------------------------------------------------------------------------------- 1 | import items from "./items.json" 2 | import formatCurrency from "./util/formatCurrency.js" 3 | import addGlobalEventListener from "./util/addGlobalEventListener.js" 4 | 5 | const cartButton = document.querySelector("[data-cart-button]") 6 | const cartItemsWrapper = document.querySelector("[data-cart-items-wrapper]") 7 | let shoppingCart = [] 8 | const IMAGE_URL = "https://dummyimage.com/210x130" 9 | const cartItemTemplate = document.querySelector("#cart-item-template") 10 | const cartItemContainer = document.querySelector("[data-cart-items]") 11 | const cartQuantity = document.querySelector("[data-cart-quantity]") 12 | const cartTotal = document.querySelector("[data-cart-total]") 13 | const cart = document.querySelector("[data-cart]") 14 | const SESSION_STORAGE_KEY = "SHOPPING_CART-cart" 15 | 16 | export function setupShoppingCart() { 17 | addGlobalEventListener("click", "[data-remove-from-cart-button]", e => { 18 | const id = parseInt(e.target.closest("[data-item]").dataset.itemId) 19 | removeFromCart(id) 20 | }) 21 | 22 | shoppingCart = loadCart() 23 | renderCart() 24 | 25 | cartButton.addEventListener("click", () => [ 26 | cartItemsWrapper.classList.toggle("invisible") 27 | ]) 28 | } 29 | 30 | function saveCart() { 31 | sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(shoppingCart)) 32 | } 33 | 34 | function loadCart() { 35 | const cart = sessionStorage.getItem(SESSION_STORAGE_KEY) 36 | return JSON.parse(cart) || [] 37 | } 38 | 39 | export function addToCart(id) { 40 | const existingItem = shoppingCart.find(entry => entry.id === id) 41 | if (existingItem) { 42 | existingItem.quantity++ 43 | } else { 44 | shoppingCart.push({ id: id, quantity: 1 }) 45 | } 46 | renderCart() 47 | saveCart() 48 | } 49 | 50 | function removeFromCart(id) { 51 | const existingItem = shoppingCart.find(entry => entry.id === id) 52 | if (existingItem == null) return 53 | shoppingCart = shoppingCart.filter(entry => entry.id !== id) 54 | renderCart() 55 | saveCart() 56 | } 57 | 58 | function renderCart() { 59 | if (shoppingCart.length === 0) { 60 | hideCart() 61 | } else { 62 | showCart() 63 | renderCartItems() 64 | } 65 | } 66 | 67 | function hideCart() { 68 | cart.classList.add("invisible") 69 | cartItemsWrapper.classList.add("invisible") 70 | } 71 | 72 | function showCart() { 73 | cart.classList.remove("invisible") 74 | } 75 | 76 | function renderCartItems() { 77 | cartQuantity.innerText = shoppingCart.length 78 | 79 | const totalCents = shoppingCart.reduce((sum, entry) => { 80 | const item = items.find(i => entry.id === i.id) 81 | return sum + item.priceCents * entry.quantity 82 | }, 0) 83 | 84 | cartTotal.innerText = formatCurrency(totalCents / 100) 85 | 86 | cartItemContainer.innerHTML = "" 87 | shoppingCart.forEach(entry => { 88 | const item = items.find(i => entry.id === i.id) 89 | const cartItem = cartItemTemplate.content.cloneNode(true) 90 | 91 | const container = cartItem.querySelector("[data-item]") 92 | container.dataset.itemId = item.id 93 | 94 | const name = cartItem.querySelector("[data-name]") 95 | name.innerText = item.name 96 | 97 | const image = cartItem.querySelector("[data-image]") 98 | image.src = `${IMAGE_URL}/${item.imageColor}/${item.imageColor}` 99 | 100 | if (entry.quantity > 1) { 101 | const quantity = cartItem.querySelector("[data-quantity]") 102 | quantity.innerText = `x${entry.quantity}` 103 | } 104 | 105 | const price = cartItem.querySelector("[data-price]") 106 | price.innerText = formatCurrency((item.priceCents * entry.quantity) / 100) 107 | 108 | cartItemContainer.appendChild(cartItem) 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /74-shopping-cart/after/store.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Store 9 | 10 | 11 | 12 |
13 |
14 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 43 | 58 | 73 | 74 | -------------------------------------------------------------------------------- /54-55-quiz/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Quiz 5 | 6 | 7 | 8 | 9 |
10 |
Congratulations!!
11 | You got them all right! 12 |
13 |
14 |
    15 |
  1. 16 |
    What does this code return? (0 || '' || false || "Hi")
    17 |
    18 |
      19 |
    1. 20 | 24 |
    2. 25 |
    3. 26 | 30 |
    4. 31 |
    5. 32 | 36 |
    6. 37 |
    7. 38 | 42 |
    8. 43 |
    44 |
    45 |
  2. 46 |
  3. 47 |
    What is it called when a function calls itself?
    48 |
    49 |
      50 |
    1. 51 | 55 |
    2. 56 |
    3. 57 | 61 |
    4. 62 |
    5. 63 | 67 |
    6. 68 |
    7. 69 | 73 |
    8. 74 |
    75 |
    76 |
  4. 77 |
  5. 78 |
    How do you access the value of the data-count data attribute in JavaScript?
    79 |
    80 |
      81 |
    1. 82 | 86 |
    2. 87 |
    3. 88 | 92 |
    4. 93 |
    5. 94 | 98 |
    6. 99 |
    7. 100 | 104 |
    8. 105 |
    106 |
    107 |
  6. 108 |
109 | 110 |
111 | 112 | -------------------------------------------------------------------------------- /54-55-quiz/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Quiz 5 | 6 | 7 | 8 | 9 | 10 |
11 |
Congratulations!!
12 | You got them all right! 13 |
14 |
15 |
    16 |
  1. 17 |
    What does this code return? (0 || '' || false || "Hi")
    18 |
    19 |
      20 |
    1. 21 | 25 |
    2. 26 |
    3. 27 | 31 |
    4. 32 |
    5. 33 | 37 |
    6. 38 |
    7. 39 | 43 |
    8. 44 |
    45 |
    46 |
  2. 47 |
  3. 48 |
    What is it called when a function calls itself?
    49 |
    50 |
      51 |
    1. 52 | 56 |
    2. 57 |
    3. 58 | 62 |
    4. 63 |
    5. 64 | 68 |
    6. 69 |
    7. 70 | 74 |
    8. 75 |
    76 |
    77 |
  4. 78 |
  5. 79 |
    How do you access the value of the data-count data attribute in JavaScript?
    80 |
    81 |
      82 |
    1. 83 | 87 |
    2. 88 |
    3. 89 | 93 |
    4. 94 |
    5. 95 | 99 |
    6. 100 |
    7. 101 | 105 |
    8. 106 |
    107 |
    108 |
  6. 109 |
110 | 111 |
112 | 113 | -------------------------------------------------------------------------------- /74-shopping-cart/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Home 9 | 10 | 11 | 12 |
13 |
14 | 19 |
20 |
21 |
22 |
23 |
24 |

Some Of Our Amazing Products

25 |

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Reprehenderit tempore nisi dolores ipsum veritatis, rerum excepturi numquam vitae perferendis recusandae possimus deserunt quas aut perspiciatis, qui architecto fuga molestias! Dolorem veritatis quasi cum quod deserunt sit recusandae blanditiis debitis voluptatum.

26 |
27 |
28 |
29 |
30 | gallery 31 |
32 |
33 | gallery 34 |
35 |
36 | gallery 37 |
38 |
39 |
40 |
41 | gallery 42 |
43 |
44 | gallery 45 |
46 |
47 | gallery 48 |
49 |
50 |
51 |
52 |
53 | 70 | 85 | 86 | -------------------------------------------------------------------------------- /74-shopping-cart/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Home 9 | 10 | 11 |
12 |
13 | 18 |
19 |
20 |
21 |
22 |
23 |

Some Of Our Amazing Products

24 |

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Reprehenderit tempore nisi dolores ipsum veritatis, rerum excepturi numquam vitae perferendis recusandae possimus deserunt quas aut perspiciatis, qui architecto fuga molestias! Dolorem veritatis quasi cum quod deserunt sit recusandae blanditiis debitis voluptatum.

25 |
26 |
27 |
28 |
29 | gallery 30 |
31 |
32 | gallery 33 |
34 |
35 | gallery 36 |
37 |
38 |
39 |
40 | gallery 41 |
42 |
43 | gallery 44 |
45 |
46 | gallery 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | ecommerce 59 | 60 |
61 |
62 |
63 |

64 | Red 65 |

66 | x2 67 |
68 |
$32.00
69 |
70 |
71 |
72 |
73 | ecommerce 74 | 75 |
76 |
77 |
78 |

79 | Blue 80 |

81 |
82 |
$12.00
83 |
84 |
85 |
86 |
87 | Total 88 | $44.00 89 |
90 |
91 |
92 | 98 |
99 | 100 | -------------------------------------------------------------------------------- /74-shopping-cart/after/team.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | About 9 | 10 | 11 | 12 |
13 |
14 | 19 |
20 |
21 |
22 |
23 |
24 |

Our Team

25 |

Whatever cardigan tote bag tumblr hexagon brooklyn asymmetrical gentrify, subway tile poke farm-to-table. Franzen you probably haven't heard of them.

26 |
27 |
28 |
29 |
30 | team 31 |
32 |

Holden Caulfield

33 |

UI Designer

34 |
35 |
36 |
37 |
38 |
39 | team 40 |
41 |

Henry Letham

42 |

CTO

43 |
44 |
45 |
46 |
47 |
48 | team 49 |
50 |

Oskar Blinde

51 |

Founder

52 |
53 |
54 |
55 |
56 |
57 | team 58 |
59 |

John Doe

60 |

DevOps

61 |
62 |
63 |
64 |
65 |
66 | team 67 |
68 |

Martin Eden

69 |

Software Engineer

70 |
71 |
72 |
73 |
74 |
75 | team 76 |
77 |

Boris Kitua

78 |

UX Researcher

79 |
80 |
81 |
82 |
83 |
84 | team 85 |
86 |

Atticus Finch

87 |

QA Engineer

88 |
89 |
90 |
91 |
92 |
93 | team 94 |
95 |

Alper Kamu

96 |

System

97 |
98 |
99 |
100 |
101 |
102 | team 103 |
104 |

Rodrigo Monchi

105 |

Product Manager

106 |
107 |
108 |
109 |
110 |
111 |
112 | 129 | 144 | 145 | -------------------------------------------------------------------------------- /74-shopping-cart/before/team.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | About 9 | 10 | 11 |
12 |
13 | 18 |
19 |
20 |
21 |
22 |
23 |

Our Team

24 |

Whatever cardigan tote bag tumblr hexagon brooklyn asymmetrical gentrify, subway tile poke farm-to-table. Franzen you probably haven't heard of them.

25 |
26 |
27 |
28 |
29 | team 30 |
31 |

Holden Caulfield

32 |

UI Designer

33 |
34 |
35 |
36 |
37 |
38 | team 39 |
40 |

Henry Letham

41 |

CTO

42 |
43 |
44 |
45 |
46 |
47 | team 48 |
49 |

Oskar Blinde

50 |

Founder

51 |
52 |
53 |
54 |
55 |
56 | team 57 |
58 |

John Doe

59 |

DevOps

60 |
61 |
62 |
63 |
64 |
65 | team 66 |
67 |

Martin Eden

68 |

Software Engineer

69 |
70 |
71 |
72 |
73 |
74 | team 75 |
76 |

Boris Kitua

77 |

UX Researcher

78 |
79 |
80 |
81 |
82 |
83 | team 84 |
85 |

Atticus Finch

86 |

QA Engineer

87 |
88 |
89 |
90 |
91 |
92 | team 93 |
94 |

Alper Kamu

95 |

System

96 |
97 |
98 |
99 |
100 |
101 | team 102 |
103 |

Rodrigo Monchi

104 |

Product Manager

105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | ecommerce 118 | 119 |
120 |
121 |
122 |

123 | Red 124 |

125 | x2 126 |
127 |
$32.00
128 |
129 |
130 |
131 |
132 | ecommerce 133 | 134 |
135 |
136 |
137 |

138 | Blue 139 |

140 |
141 |
$12.00
142 |
143 |
144 |
145 |
146 | Total 147 | $44.00 148 |
149 |
150 |
151 | 157 |
158 | 159 | -------------------------------------------------------------------------------- /74-shopping-cart/before/store.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Store 9 | 10 | 11 |
12 |
13 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ecommerce 26 |
27 |
28 |
29 |

Primary Color

30 |

Red

31 |

$16.00

32 |
33 | 34 |
35 |
36 |
37 |
38 | ecommerce 39 |
40 |
41 |
42 |

Primary Color

43 |

Yellow

44 |

$21.00

45 |
46 | 47 |
48 |
49 |
50 |
51 | ecommerce 52 |
53 |
54 |
55 |

Primary Color

56 |

Blue

57 |

$12.00

58 |
59 | 60 |
61 |
62 |
63 |
64 | ecommerce 65 |
66 |
67 |
68 |

Secondary Color

69 |

Orange

70 |

$18.00

71 |
72 | 73 |
74 |
75 |
76 |
77 | ecommerce 78 |
79 |
80 |
81 |

Secondary Color

82 |

Green

83 |

$16.00

84 |
85 | 86 |
87 |
88 |
89 |
90 | ecommerce 91 |
92 |
93 |
94 |

Secondary Color

95 |

Purple

96 |

$21.00

97 |
98 | 99 |
100 |
101 |
102 |
103 | ecommerce 104 |
105 |
106 |
107 |

Grayscale

108 |

Light Gray

109 |

$12.00

110 |
111 | 112 |
113 |
114 |
115 |
116 | ecommerce 117 |
118 |
119 |
120 |

Grayscale

121 |

Dark Gray

122 |

$18.00

123 |
124 | 125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | ecommerce 137 | 138 |
139 |
140 |
141 |

142 | Red 143 |

144 | x2 145 |
146 |
$32.00
147 |
148 |
149 |
150 |
151 | ecommerce 152 | 153 |
154 |
155 |
156 |

157 | Blue 158 |

159 |
160 |
$12.00
161 |
162 |
163 |
164 |
165 | Total 166 | $44.00 167 |
168 |
169 |
170 | 176 |
177 | 178 | --------------------------------------------------------------------------------