Fighting GameTip Calculator
- Weather App
+ Weather App
+ Advanced Weather App
- Create Netflix
+ Create Netflix
diff --git a/playground.js b/playground.js
index 3795cf8..fb1ca0f 100644
--- a/playground.js
+++ b/playground.js
@@ -1,3 +1,7 @@
+/* ***** DO NOT CODE HERE!!! *****
+CODE in the file yourPlayground.js
+*/
+
// console.log('hello world')
// console.log('Rafeh Qazi')
@@ -90,33 +94,33 @@ function greeting(name) {
// greeting('Johnny Depp')
-function sum(a, b) {
- // return
- return a + b
-}
+// function sum(a, b) {
+// // return
+// return a + b
+// }
// num1 = sum(1, 2)
// console.log(num1)
-function calculateFoodTotal(food, tip) {
- const tipPercentage = tip / 100
- const tipAmount = food * tipPercentage
- const total = sum(food, tipAmount)
- return total
-}
+// function calculateFoodTotal(food, tip) {
+// const tipPercentage = tip / 100
+// const tipAmount = food * tipPercentage
+// const total = sum(food, tipAmount)
+// return total
+// }
// console.log(calculateFoodTotal(300, 20))
// ES6
// Arrow Functions =>
// arrow function with explicit return
-const sumArrow = (a, b) => {
- return a + b
-}
+// const sumArrow = (a, b) => {
+// return a + b
+// }
// arrow function with implicit return
// IMPORTANT: For implicit return, remove curly braces
-const sumArrow2 = (a, b) => a + b
+// const sumArrow2 = (a, b) => a + b
// console.log(sumArrow2(10, 50))
@@ -178,26 +182,26 @@ const sumArrow2 = (a, b) => a + b
// object
// template literals
// methods Math.floor()
-const introducer = (name, shirt) => {
- const person = {
- name: name,
- shirt: shirt,
- assets: 100000,
- liabilities: 50000,
- netWorth: function() {
- return this.assets - this.liabilities
- }
- }
-
- const intro = `Hi, my name is ${person.name} and the color of my shirt is ${person.shirt} and my net worth is $${person.netWorth()} USD`
-
- return intro
-}
+// const introducer = (name, shirt) => {
+// const person = {
+// name: name,
+// shirt: shirt,
+// assets: 100000,
+// liabilities: 50000,
+// netWorth: function() {
+// return this.assets - this.liabilities
+// }
+// }
+
+// const intro = `Hi, my name is ${person.name} and the color of my shirt is ${person.shirt} and my net worth is $${person.netWorth()} USD`
+
+// return intro
+// }
// console.log(introducer('Qazi', 'black'))
// console.log(introducer('Leonardo', 'white'))
-let fruits = ['🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐']
+// let fruits = ['🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐']
// console.log(fruits[0])
// console.log(fruits[1])
@@ -212,35 +216,35 @@ let fruits = ['🍌', '🍎', '🍊', '🍐', '🍌', '🍎', '🍊', '🍐', '
// console.log(fruit)
// }
-const numbers = [1, 2, 3, 4, 5, 6]
-// for (let i=0; i < numbers.length; i++) {
-// console.log(numbers[i])
-// }
+// const numbers = [1, 2, 3, 4, 5, 6]
+// // for (let i=0; i < numbers.length; i++) {
+// // console.log(numbers[i])
+// // }
-const double = (numbers) => {
- let result = []
- for (const number of numbers) {
- result.push(number * 2)
- }
+// const double = (numbers) => {
+// let result = []
+// for (const number of numbers) {
+// result.push(number * 2)
+// }
- return result
-}
+// return result
+// }
// console.log(double([1, 2, 3, 4, 5, 6]))
// [2, 4, 6, 8, 10, 12]
-const howManyLetters = (phrase) => {
- // counter
- let result = 0;
+// const howManyLetters = (phrase) => {
+// // counter
+// let result = 0;
- for (const index in phrase) {
- console.log(Number(index) + 1)
- result = Number(index) + 1
- }
+// for (const index in phrase) {
+// console.log(Number(index) + 1)
+// result = Number(index) + 1
+// }
- return { result }
-}
+// return { result }
+// }
// const phrase = prompt('write your phrase')
@@ -253,60 +257,60 @@ const howManyLetters = (phrase) => {
// result = 6
// result = 10
-const sumArray = (numbers) => {
- let result = 0;
- // for loop
- for (const number of numbers) {
- console.log(number)
- result += number
- }
- return { result }
-}
+// const sumArray = (numbers) => {
+// let result = 0;
+// // for loop
+// for (const number of numbers) {
+// console.log(number)
+// result += number
+// }
+// return { result }
+// }
// const nums = [1, 2, 3, 4, 5]
// console.log(sumArray(nums))
// sum up all numbers in array
-const max = (numbers) => {
- let result = numbers[0]
+// const max = (numbers) => {
+// let result = numbers[0]
- // loop
- for (const number of numbers) {
- if (number > result) {
- result = number
- }
- }
+// // loop
+// for (const number of numbers) {
+// if (number > result) {
+// result = number
+// }
+// }
- return { result }
-}
+// return { result }
+// }
// console.log(max([1, 2, 3, 4, 5]))
-const letterFrequency = (phrase) => {
- // letterFrequency('haha') 👉 {'h': 2, 'a': 2}
- console.log(phrase)
- // make a `frequency` object {}
- let frequency = {}
- for (const letter of phrase) {
- // check if letter exists in frequency
- if (letter in frequency) {
- // increment the value by +1
- frequency[letter] += 1
- // otherwise, set the value to 1
- } else {
- frequency[letter] = 1
- }
- }
- return frequency
-}
+// const letterFrequency = (phrase) => {
+// // letterFrequency('haha') 👉 {'h': 2, 'a': 2}
+// console.log(phrase)
+// // make a `frequency` object {}
+// let frequency = {}
+// for (const letter of phrase) {
+// // check if letter exists in frequency
+// if (letter in frequency) {
+// // increment the value by +1
+// frequency[letter] += 1
+// // otherwise, set the value to 1
+// } else {
+// frequency[letter] = 1
+// }
+// }
+// return frequency
+// }
// console.log(letterFrequency('lol, what are you doing later tonight lol, haha!'))
// wordFrequency('lol what lol') 👉 {'lol': 2, 'what': 1}
-const wordFrequency = (phrase) => {
- const words = phrase.split(' ')
- return letterFrequency(words)
-}
+// const wordFrequency = (phrase) => {
+// const words = phrase.split(' ')
+// return letterFrequency(words)
+// }
// const userInput = prompt('Write your sentence')
// console.log(wordFrequency(userInput))
@@ -319,34 +323,34 @@ const wordFrequency = (phrase) => {
// reduce
// MAP
-const doubleMap = (numbers) => {
- return numbers.map(number => number * 2)
-}
+// const doubleMap = (numbers) => {
+// return numbers.map(number => number * 2)
+// }
// console.log(doubleMap([1, 2, 3]))
// filter([1,2,3,4,5,6], 3) 👉 [4, 5, 6]
-const filter = (numbers, greaterThan) => {
- let result = []
- for (const number of numbers) {
- if (number > greaterThan) {
- result.push(number)
- }
- }
- return result
-}
+// const filter = (numbers, greaterThan) => {
+// let result = []
+// for (const number of numbers) {
+// if (number > greaterThan) {
+// result.push(number)
+// }
+// }
+// return result
+// }
// console.log(filter([1, 2, 3, 4, 5, 6], 2))
// const nums = [1, 2, 3, 4, 5, 6]
// console.log(nums.filter(num => num > 4 || num < 2))
-const actors = [
- { name: 'johnny', netWorth: 2000000 },
- { name: 'amber', netWorth: 10 },
- { name: 'matt', netWorth: 170000000 },
- { name: 'brad', netWorth: 300000000 },
- { name: 'leonardo', netWorth: 10000000 },
-]
+// const actors = [
+// { name: 'johnny', netWorth: 2000000 },
+// { name: 'amber', netWorth: 10 },
+// { name: 'matt', netWorth: 170000000 },
+// { name: 'brad', netWorth: 300000000 },
+// { name: 'leonardo', netWorth: 10000000 },
+// ]
// let result = actors.filter(actor => actor.netWorth > 10)
// console.log(result)
@@ -363,34 +367,34 @@ const actors = [
// select a random element from an array
// randomFruit([1, 2]) 👉 2
// randomFruit([1, 2]) 👉 1
-const randomFruit = (fruits) => {
- const randomNumber = Math.floor(Math.random() * fruits.length)
+// const randomFruit = (fruits) => {
+// const randomNumber = Math.floor(Math.random() * fruits.length)
- console.log(randomNumber)
+// console.log(randomNumber)
- return fruits[randomNumber]
-}
+// return fruits[randomNumber]
+// }
// fruits = ['🍌', '🍎', '🍊', '🍐']
// console.log(randomFruit(fruits))
// if else if else
// rainy (1), sunny (-1), overcast (0)
-const weatherScorer = (weather, weather2) => {
- let score
-
- if (weather == 'rainy' && weather2 == 'overcast') {
- score = 2
- } else if (weather == 'rainy') {
- score = 1
- } else if (weather == 'sunny') {
- score = -1
- } else {
- score = 0
- }
-
- return score
-}
+// const weatherScorer = (weather, weather2) => {
+// let score
+
+// if (weather == 'rainy' && weather2 == 'overcast') {
+// score = 2
+// } else if (weather == 'rainy') {
+// score = 1
+// } else if (weather == 'sunny') {
+// score = -1
+// } else {
+// score = 0
+// }
+
+// return score
+// }
// console.log(weatherScorer('rainy', 'sunny'))
diff --git a/projects/advanced-weather-app/exercise/index.html b/projects/advanced-weather-app/exercise/index.html
new file mode 100644
index 0000000..0b9fdc5
--- /dev/null
+++ b/projects/advanced-weather-app/exercise/index.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+ Weather App
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Today Overview
+
+
+
+
+
+
+
Wind
+
+
+
+ mph
+
+
+
+
+
+
+
+
+
+
+
Lowest / Highest
+
+
+
+ ˚F
+ /
+
+ ˚F
+
+
+
+
+
+
+
+
+
+
+
+
Pressure
+
+
+
+ hpa
+
+
+
+
+
+
+
+
+
+
+
Humidity
+
+
+
+ %
+
+
+
+
+
+
+
+
Temperature Forecast
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ˚F
+
+
+
+
Sunrise & Sunset
+
+
+
+
Sunrise
+
+
+
+
+
+
+
+
Sunset
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/advanced-weather-app/exercise/script.js b/projects/advanced-weather-app/exercise/script.js
new file mode 100644
index 0000000..0a05aee
--- /dev/null
+++ b/projects/advanced-weather-app/exercise/script.js
@@ -0,0 +1,228 @@
+/*
+ 🔥 APP: Weather App
+
+ These are the promises you'll need to create
+ =============================================
+ 1. currentWeather - Gets the current weather
+ 2. forecast - Gets 5 day forecast
+
+These are all the functions you'll need to build
+================================================
+ 1. getWeatherData() - Runs both promises then updates the DOM by running updateDom().
+ 2. updateDom() - Updates the DOM with the data from the promises and runs the renderChart() function.
+ 3. renderChart() - Renders the chart with the data from the promises.
+
+ 4. getDirection() - Returns a cardinal direction based on the degree passed in
+ - this will be a helper function only
+ */
+
+// Get DOM Elements
+// Hint: All required elements have an ID attribute in the HTML file (a total of 17 elements)
+const currentTemperature = document.getElementById('currentTemp')
+const weatherIcon = document.getElementById('weatherIcon')
+const weatherDescription = document.getElementById('weatherDescription')
+const windSpeed = document.getElementById('wind')
+const windDirection = document.getElementById('windDir')
+const lowestToday = document.getElementById('lowestToday')
+const highestToday = document.getElementById('highestToday')
+const pressure = document.getElementById('pressure')
+const humidity = document.getElementById('humidity')
+const sunrise = document.getElementById('sunrise')
+const sunset = document.getElementById('sunset')
+const sunriseRelative = document.getElementById('sunriseRelative')
+const sunsetRelative = document.getElementById('sunsetRelative')
+const userLocation = document.getElementById('location')
+const time = document.getElementById('time')
+const date = document.getElementById('date')
+const searchInput = document.getElementById('searchInput')
+
+// Create an array of month names
+const monthNames = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+]
+
+const getWeatherData = async () => {
+ // Use the try-catch block to handle errors
+ try {
+ // Create a const that stores the user input from the searchbar or defaults back to 'Los Angeles' if left blank
+ const city = searchInput.value || 'Los Angeles'
+
+ // Create 2 promises that call the APIs and pass in the city name
+ // If the user haven't typed anything, use Los Angeles as default
+ const currentWeather = new Promise(async (resolve, reject) => {
+ try {
+ const weatherApiData = await fetch(
+ `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=8109965e7254a469d08a746e8b210e1e&units=imperial`,
+ )
+
+ resolve(await weatherApiData.json())
+ } catch (error) {
+ reject()
+ }
+ })
+
+ const forecast = new Promise(async (resolve, reject) => {
+ try {
+ const forecastApiData = await fetch(
+ `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=8109965e7254a469d08a746e8b210e1e&units=imperial&cnt=10`,
+ )
+
+ resolve(await forecastApiData.json())
+ } catch (error) {
+ reject()
+ }
+ })
+
+ // Using the Promise.all method, wait for both promises to resolve, and save the returned data in a variable
+ const data = await Promise.all([currentWeather, forecast])
+
+ // Now pass that data into the updateDom() function
+ updateDom(data)
+ } catch (error) {
+ console.log(error)
+ }
+}
+
+// Create a function that returns a cardinal direction based on the degree passed in
+// Hint: Draw a Circle and Visualize each Direction First. It will help... A ton!
+const getDirection = deg => {
+ switch (true) {
+ case deg < 22.5:
+ return 'N'
+ case deg < 67.5:
+ return 'NE'
+ case deg < 112.5:
+ return 'E'
+ case deg < 157.5:
+ return 'SE'
+ case deg < 202.5:
+ return 'S'
+ case deg < 247.5:
+ return 'SW'
+ case deg < 292.5:
+ return 'W'
+ case deg < 337.5:
+ return 'NW'
+ }
+}
+
+/**
+ * Update each DOM element with the API data
+ */
+const updateDom = data => {
+ console.log('🔥 updating', data)
+ // Current temperature
+ currentTemperature.innerText = data[0].main.temp.toFixed(1)
+
+ // Weather Icon
+ // Use template literals to insert the in the below link, then set it as image source:
+ // https://openweathermap.org/img/wn/API_RESPONSE_DATA@2x.png
+ weatherIcon.src = `https://openweathermap.org/img/wn/${data[0].weather[0].icon}@2x.png`
+
+ // Description of the Current Weather
+ weatherDescription.innerText = data[0].weather[0].main
+
+ // Wind Speed
+ windSpeed.innerText = data[0].wind.speed.toFixed(1)
+
+ // Wind Direction (Use the getDirection function)
+ windDirection.innerText = getDirection(data[0].wind.deg)
+
+ // Lowest Temperature of the Day
+ lowestToday.innerText = Math.round(data[0].main.temp_min)
+
+ // Highest Temperature of the Day
+ highestToday.innerText = Math.round(data[0].main.temp_max)
+
+ // Pressure
+ pressure.innerText = data[0].main.pressure
+
+ // Humidity
+ humidity.innerText = data[0].main.humidity
+
+ // Save both Sunrise and Sunset time in a variable as Milliseconds
+ // Hint: the data from the API is in seconds
+ const sunriseTs = new Date(data[0].sys.sunrise * 1000)
+ const sunsetTs = new Date(data[0].sys.sunset * 1000)
+
+ // Use the Sunrise Time in Milliseconds to get Sunrise Time
+ // use the .toLocaleString() method to get the time in a readable format
+ sunrise.innerText = sunriseTs.toLocaleTimeString('en-US', {
+ hour: 'numeric',
+ minute: 'numeric',
+ })
+
+ // Do the same for Sunset
+ sunset.innerText = sunsetTs.toLocaleTimeString('en-US', {
+ hour: 'numeric',
+ minute: 'numeric',
+ })
+
+ // Using timeago.js, create relative timestamps for both sunrise and sunset
+ sunriseRelative.innerText = timeago.format(sunriseTs)
+ sunsetRelative.innerText = timeago.format(sunsetTs)
+
+ // Get the location of the user from the API (When you type, it's probably not formatted)
+ userLocation.innerText = data[0].name
+
+ // Get and format Current Time
+ time.innerText = new Date(Date.now()).toLocaleString('en-US', {
+ hour: 'numeric',
+ minute: 'numeric',
+ })
+
+ // Get and format Current Date
+ date.innerText = new Date(Date.now()).toLocaleString('en-US', {
+ weekday: 'long',
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ })
+
+ // Call the renderChart function and pass in the list array of the 2nd object in the data array
+ renderChart(data[1].list)
+}
+
+// Create a function that renders the chart
+const renderChart = data => {
+ // Store the DOM element that will hold the chart
+ const myChart = echarts.init(document.getElementById('chart'))
+
+ const option = {
+ legend: {
+ data: ['temperature'],
+ },
+ tooltip: {},
+ xAxis: {
+ data: data.map(item => item.dt_txt),
+ },
+ yAxis: {},
+ series: [
+ {
+ type: 'line',
+ smooth: true,
+ areaStyle: {
+ opacity: 0.5,
+ },
+ data: data.map(item => item.main.temp),
+ },
+ ],
+ }
+
+ // Using the given function from the documentation, generate the chart using the options above
+ myChart.setOption(option)
+}
+
+// Call the getWeatherData function
+getWeatherData()
diff --git a/projects/advanced-weather-app/exercise/style.css b/projects/advanced-weather-app/exercise/style.css
new file mode 100644
index 0000000..4ef376b
--- /dev/null
+++ b/projects/advanced-weather-app/exercise/style.css
@@ -0,0 +1,256 @@
+* {
+ font-family: Avenir Next;
+ padding: 0;
+ margin: 0;
+}
+
+.wrapper {
+ display: flex;
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+}
+
+.left {
+ flex: 1;
+ color: #091f39;
+ font-weight: 600;
+ padding: 1.6rem 2.4rem;
+ display: flex;
+ justify-content: center;
+ height: 100vh;
+ overflow: scroll;
+}
+
+/* Hide scrollbar for Chrome, Safari and Opera */
+.left::-webkit-scrollbar {
+ display: none;
+}
+
+/* Hide scrollbar for IE, Edge and Firefox */
+.left {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+}
+
+.container {
+ flex: 1;
+ max-width: 44rem;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+}
+
+.date {
+ font-size: 1.6rem;
+}
+
+.searchBar {
+ flex: 1;
+ background-color: #eef3f3;
+ border-radius: 0.5rem;
+ display: flex;
+ align-items: center;
+ padding: 0.8rem 1.2rem;
+ margin-left: 4.6rem;
+}
+
+.searchInput {
+ background: none;
+ outline: none;
+ border: none;
+ flex: 1;
+ margin-left: 0.6rem;
+}
+
+.searchButton {
+ border: none;
+ background-color: #a8b7e2;
+ padding: 0.4rem 0.8rem;
+ border-radius: 0.5rem;
+ margin: -0.4rem 0;
+ cursor: pointer;
+}
+
+.searchButton:hover {
+ background-color: #8e9dd3;
+}
+
+.sectionTitle {
+ font-size: 1.2rem;
+ font-weight: 500;
+ margin-top: 3rem;
+ margin-bottom: 1.6rem;
+}
+
+.row {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+}
+
+.overviewProp {
+ height: 6rem;
+ min-width: 16rem;
+ width: 100%;
+ margin: 0.4rem 1.4rem;
+ background-color: #eef3f3;
+ display: flex;
+ align-items: center;
+ border-radius: 0.6rem;
+ padding: 0 1rem;
+}
+
+.propIconContainer {
+ padding-right: 1rem;
+}
+
+.propIcon {
+ color: #4771da;
+}
+
+.propValueContainer {
+ width: 100%;
+}
+
+.propTitle {
+ color: #98989b;
+ font-weight: 400;
+ font-size: 1.1rem;
+}
+
+.propMain {
+ display: flex;
+ align-items: flex-end;
+ width: 100%;
+}
+
+.primaryData {
+ flex: 3;
+}
+
+.propValue {
+ font-weight: 500;
+ font-size: 1.4rem;
+}
+
+.secondaryData {
+ flex: 1;
+ font-weight: 400;
+ color: #98989b;
+}
+
+.right {
+ width: 28rem;
+ background-color: #112b50;
+ background-size: 200% 400%;
+ color: #fafcfe;
+}
+
+.rightContainer {
+ padding: 1.6rem 1.4rem;
+}
+
+.top {
+ display: flex;
+ justify-content: space-between;
+}
+
+.location {
+ font-size: 1.8rem;
+ font-weight: 500;
+}
+
+.time {
+ font-size: 1.4rem;
+ font-weight: 400;
+}
+
+.currentWeather {
+ display: flex;
+ align-items: center;
+ margin-top: 2.6rem;
+}
+
+.weatherIconContainer {
+ height: 5.4rem;
+ width: 5.4rem;
+ margin-right: 1rem;
+}
+
+.weatherIconContainer > img {
+ height: 100%;
+ width: 100%;
+}
+
+.currentTemperatureValueContainer {
+ flex: 1;
+}
+
+.currentTemperature {
+ margin: 0.4rem 1.2rem;
+}
+
+.currentTemperature {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.currentTemperatureValue {
+ font-size: 3rem;
+ font-weight: 400;
+}
+
+.currentTemperatureMetric {
+ font-size: 2rem;
+ font-weight: 400;
+}
+
+.temperatureDescription {
+ font-size: 1.4rem;
+ font-weight: 400;
+}
+
+.horizontalLine {
+ width: 100%;
+ height: 1px;
+ background-color: #445977;
+ margin: 1.6rem 0;
+}
+
+.sunStageContainer {
+ display: flex;
+ align-items: center;
+ margin-top: 1.6rem;
+ background-color: #294269;
+ padding: 1rem 1.2rem;
+ border-radius: 0.6rem;
+ border: 1px #6180b9 solid;
+}
+
+.sunIcon {
+ margin-right: 1.2rem;
+ font-size: 1.2rem;
+}
+
+.col {
+ flex: 1;
+}
+
+.sunStageTitle {
+ color: #98989b;
+ font-weight: 400;
+ font-size: 0.8rem;
+}
+
+.sunTime {
+ font-weight: 500;
+}
+
+.sunTimeRelative {
+ font-size: 0.8rem;
+ font-weight: 400;
+}
diff --git a/projects/advanced-weather-app/solution/index.html b/projects/advanced-weather-app/solution/index.html
new file mode 100644
index 0000000..291bf44
--- /dev/null
+++ b/projects/advanced-weather-app/solution/index.html
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+ Weather App
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Today Overview
+
+
+
+
+
+
+
Wind
+
+
+
+ mph
+
+
+
+
+
+
+
+
+
+
+
Lowest / Highest
+
+
+
+ ˚F
+ /
+
+ ˚F
+
+
+
+
+
+
+
+
+
+
+
+
Pressure
+
+
+
+ hpa
+
+
+
+
+
+
+
+
+
+
+
Humidity
+
+
+
+ %
+
+
+
+
+
+
+
+
Temperature Forecast
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ˚F
+
+
+
+
Sunrise & Sunset
+
+
+
+
Sunrise
+
+
+
+
+
+
+
+
Sunset
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/advanced-weather-app/solution/script.js b/projects/advanced-weather-app/solution/script.js
new file mode 100644
index 0000000..6b22df8
--- /dev/null
+++ b/projects/advanced-weather-app/solution/script.js
@@ -0,0 +1,228 @@
+/*
+ 🔥 APP: Weather App
+
+ These are the promises you'll need to create
+ =============================================
+ 1. currentWeather - Gets the current weather
+ 2. forecast - Gets 5 day forecast
+
+These are all the functions you'll need to build
+================================================
+ 1. getWeatherData() - Runs both promises then updates the DOM by running...
+ 2. updateDom() - Updates the DOM with the data from the promises and runs the...
+ 3. renderChart() - Renders the chart with the data from the promises
+
+ 4. getDirection() - Returns a cardinal direction based on the degree passed in
+ - this will be a helper function only
+ */
+
+// Get DOM Elements
+// Hint: All required elements have an ID attribute in the HTML file (a total of 17 elements)
+const currentTemperature = document.getElementById('currentTemp')
+const weatherIcon = document.getElementById('weatherIcon')
+const weatherDescription = document.getElementById('weatherDescription')
+const windSpeed = document.getElementById('wind')
+const windDirection = document.getElementById('windDir')
+const lowestToday = document.getElementById('lowestToday')
+const highestToday = document.getElementById('highestToday')
+const pressure = document.getElementById('pressure')
+const humidity = document.getElementById('humidity')
+const sunrise = document.getElementById('sunrise')
+const sunset = document.getElementById('sunset')
+const sunriseRelative = document.getElementById('sunriseRelative')
+const sunsetRelative = document.getElementById('sunsetRelative')
+const userLocation = document.getElementById('location')
+const time = document.getElementById('time')
+const date = document.getElementById('date')
+const searchInput = document.getElementById('searchInput')
+
+// Create an array of month names
+const monthNames = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+]
+
+const getWeatherData = async () => {
+ // Use the try-catch block to handle errors
+ try {
+ // Create a const that stores the user input from the searchbar or defaults back to 'Los Angeles' if left blank
+ const city = searchInput.value || 'Los Angeles'
+
+ // Create 2 promises that call the APIs and pass in the city name
+ // If the user haven't typed anything, use Los Angeles as default
+ const currentWeather = new Promise(async (resolve, reject) => {
+ try {
+ const weatherApiData = await fetch(
+ `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=8109965e7254a469d08a746e8b210e1e&units=imperial`,
+ )
+
+ resolve(await weatherApiData.json())
+ } catch (error) {
+ reject()
+ }
+ })
+
+ const forecast = new Promise(async (resolve, reject) => {
+ try {
+ const forecastApiData = await fetch(
+ `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=8109965e7254a469d08a746e8b210e1e&units=imperial&cnt=10`,
+ )
+
+ resolve(await forecastApiData.json())
+ } catch (error) {
+ reject()
+ }
+ })
+
+ // Using the Promise.all method, wait for both promises to resolve, and save the returned data in a variable
+ const data = await Promise.all([currentWeather, forecast])
+
+ // Now pass that data into the updateDom() function
+ updateDom(data)
+ } catch (error) {
+ console.log(error)
+ }
+}
+
+// Create a function that returns a cardinal direction based on the degree passed in
+// Hint: Draw a Circle and Visualize each Direction First. It will help... A ton!
+const getDirection = deg => {
+ switch (true) {
+ case deg < 22.5:
+ return 'N'
+ case deg < 67.5:
+ return 'NE'
+ case deg < 112.5:
+ return 'E'
+ case deg < 157.5:
+ return 'SE'
+ case deg < 202.5:
+ return 'S'
+ case deg < 247.5:
+ return 'SW'
+ case deg < 292.5:
+ return 'W'
+ case deg < 337.5:
+ return 'NW'
+ }
+}
+
+/**
+ * Update each DOM element with the API data
+ */
+const updateDom = data => {
+ console.log('🔥 updating', data)
+ // Current temperature
+ currentTemperature.innerText = data[0].main.temp.toFixed(1)
+
+ // Weather Icon
+ // Use template literals to insert the in the below link, then set it as image source:
+ // https://openweathermap.org/img/wn/API_RESPONSE_DATA@2x.png
+ weatherIcon.src = `https://openweathermap.org/img/wn/${data[0].weather[0].icon}@2x.png`
+
+ // Description of the Current Weather
+ weatherDescription.innerText = data[0].weather[0].main
+
+ // Wind Speed
+ windSpeed.innerText = data[0].wind.speed.toFixed(1)
+
+ // Wind Direction (Use the getDirection function)
+ windDirection.innerText = getDirection(data[0].wind.deg)
+
+ // Lowest Temperature of the Day
+ lowestToday.innerText = Math.round(data[0].main.temp_min)
+
+ // Highest Temperature of the Day
+ highestToday.innerText = Math.round(data[0].main.temp_max)
+
+ // Pressure
+ pressure.innerText = data[0].main.pressure
+
+ // Humidity
+ humidity.innerText = data[0].main.humidity
+
+ // Save both Sunrise and Sunset time in a variable as Milliseconds
+ // Hint: the data from the API is in seconds
+ const sunriseTs = new Date(data[0].sys.sunrise * 1000)
+ const sunsetTs = new Date(data[0].sys.sunset * 1000)
+
+ // Use the Sunrise Time in Milliseconds to get Sunrise Time
+ // use the .toLocaleString() method to get the time in a readable format
+ sunrise.innerText = sunriseTs.toLocaleTimeString('en-US', {
+ hour: 'numeric',
+ minute: 'numeric',
+ })
+
+ // Do the same for Sunset
+ sunset.innerText = sunsetTs.toLocaleTimeString('en-US', {
+ hour: 'numeric',
+ minute: 'numeric',
+ })
+
+ // Using timeago.js, create relative timestamps for both sunrise and sunset
+ sunriseRelative.innerText = timeago.format(sunriseTs)
+ sunsetRelative.innerText = timeago.format(sunsetTs)
+
+ // Get the location of the user from the API (When you type, it's probably not formatted)
+ userLocation.innerText = data[0].name
+
+ // Get and format Current Time
+ time.innerText = new Date(Date.now()).toLocaleString('en-US', {
+ hour: 'numeric',
+ minute: 'numeric',
+ })
+
+ // Get and format Current Date
+ date.innerText = new Date(Date.now()).toLocaleString('en-US', {
+ weekday: 'long',
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ })
+
+ // Call the renderChart function and pass in the list array of the 2nd object in the data array
+ renderChart(data[1].list)
+}
+
+// Create a function that renders the chart
+const renderChart = data => {
+ // Store the DOM element that will hold the chart
+ const myChart = echarts.init(document.getElementById('chart'))
+
+ const option = {
+ legend: {
+ data: ['temperature'],
+ },
+ tooltip: {},
+ xAxis: {
+ data: data.map(item => item.dt_txt),
+ },
+ yAxis: {},
+ series: [
+ {
+ type: 'line',
+ smooth: true,
+ areaStyle: {
+ opacity: 0.5,
+ },
+ data: data.map(item => item.main.temp),
+ },
+ ],
+ }
+
+ // Using the given function from the documentation, generate the chart using the options above
+ myChart.setOption(option)
+}
+
+// Call the getWeatherData function
+getWeatherData()
diff --git a/projects/advanced-weather-app/solution/style.css b/projects/advanced-weather-app/solution/style.css
new file mode 100644
index 0000000..4ef376b
--- /dev/null
+++ b/projects/advanced-weather-app/solution/style.css
@@ -0,0 +1,256 @@
+* {
+ font-family: Avenir Next;
+ padding: 0;
+ margin: 0;
+}
+
+.wrapper {
+ display: flex;
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+}
+
+.left {
+ flex: 1;
+ color: #091f39;
+ font-weight: 600;
+ padding: 1.6rem 2.4rem;
+ display: flex;
+ justify-content: center;
+ height: 100vh;
+ overflow: scroll;
+}
+
+/* Hide scrollbar for Chrome, Safari and Opera */
+.left::-webkit-scrollbar {
+ display: none;
+}
+
+/* Hide scrollbar for IE, Edge and Firefox */
+.left {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+}
+
+.container {
+ flex: 1;
+ max-width: 44rem;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+}
+
+.date {
+ font-size: 1.6rem;
+}
+
+.searchBar {
+ flex: 1;
+ background-color: #eef3f3;
+ border-radius: 0.5rem;
+ display: flex;
+ align-items: center;
+ padding: 0.8rem 1.2rem;
+ margin-left: 4.6rem;
+}
+
+.searchInput {
+ background: none;
+ outline: none;
+ border: none;
+ flex: 1;
+ margin-left: 0.6rem;
+}
+
+.searchButton {
+ border: none;
+ background-color: #a8b7e2;
+ padding: 0.4rem 0.8rem;
+ border-radius: 0.5rem;
+ margin: -0.4rem 0;
+ cursor: pointer;
+}
+
+.searchButton:hover {
+ background-color: #8e9dd3;
+}
+
+.sectionTitle {
+ font-size: 1.2rem;
+ font-weight: 500;
+ margin-top: 3rem;
+ margin-bottom: 1.6rem;
+}
+
+.row {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+}
+
+.overviewProp {
+ height: 6rem;
+ min-width: 16rem;
+ width: 100%;
+ margin: 0.4rem 1.4rem;
+ background-color: #eef3f3;
+ display: flex;
+ align-items: center;
+ border-radius: 0.6rem;
+ padding: 0 1rem;
+}
+
+.propIconContainer {
+ padding-right: 1rem;
+}
+
+.propIcon {
+ color: #4771da;
+}
+
+.propValueContainer {
+ width: 100%;
+}
+
+.propTitle {
+ color: #98989b;
+ font-weight: 400;
+ font-size: 1.1rem;
+}
+
+.propMain {
+ display: flex;
+ align-items: flex-end;
+ width: 100%;
+}
+
+.primaryData {
+ flex: 3;
+}
+
+.propValue {
+ font-weight: 500;
+ font-size: 1.4rem;
+}
+
+.secondaryData {
+ flex: 1;
+ font-weight: 400;
+ color: #98989b;
+}
+
+.right {
+ width: 28rem;
+ background-color: #112b50;
+ background-size: 200% 400%;
+ color: #fafcfe;
+}
+
+.rightContainer {
+ padding: 1.6rem 1.4rem;
+}
+
+.top {
+ display: flex;
+ justify-content: space-between;
+}
+
+.location {
+ font-size: 1.8rem;
+ font-weight: 500;
+}
+
+.time {
+ font-size: 1.4rem;
+ font-weight: 400;
+}
+
+.currentWeather {
+ display: flex;
+ align-items: center;
+ margin-top: 2.6rem;
+}
+
+.weatherIconContainer {
+ height: 5.4rem;
+ width: 5.4rem;
+ margin-right: 1rem;
+}
+
+.weatherIconContainer > img {
+ height: 100%;
+ width: 100%;
+}
+
+.currentTemperatureValueContainer {
+ flex: 1;
+}
+
+.currentTemperature {
+ margin: 0.4rem 1.2rem;
+}
+
+.currentTemperature {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.currentTemperatureValue {
+ font-size: 3rem;
+ font-weight: 400;
+}
+
+.currentTemperatureMetric {
+ font-size: 2rem;
+ font-weight: 400;
+}
+
+.temperatureDescription {
+ font-size: 1.4rem;
+ font-weight: 400;
+}
+
+.horizontalLine {
+ width: 100%;
+ height: 1px;
+ background-color: #445977;
+ margin: 1.6rem 0;
+}
+
+.sunStageContainer {
+ display: flex;
+ align-items: center;
+ margin-top: 1.6rem;
+ background-color: #294269;
+ padding: 1rem 1.2rem;
+ border-radius: 0.6rem;
+ border: 1px #6180b9 solid;
+}
+
+.sunIcon {
+ margin-right: 1.2rem;
+ font-size: 1.2rem;
+}
+
+.col {
+ flex: 1;
+}
+
+.sunStageTitle {
+ color: #98989b;
+ font-weight: 400;
+ font-size: 0.8rem;
+}
+
+.sunTime {
+ font-weight: 500;
+}
+
+.sunTimeRelative {
+ font-size: 0.8rem;
+ font-weight: 400;
+}
diff --git a/projects/fetchmovies/solution/index.html b/projects/create-netflix/exercise/index.html
similarity index 84%
rename from projects/fetchmovies/solution/index.html
rename to projects/create-netflix/exercise/index.html
index f84962e..6f15727 100644
--- a/projects/fetchmovies/solution/index.html
+++ b/projects/create-netflix/exercise/index.html
@@ -3,7 +3,7 @@
- PWJ Netflix Clone
+ Netflix Clone
-
-
-
-
-
-
-
-
- Sign out
-
-
-
@@ -45,7 +33,7 @@
Title
NETFLIX ORIGINALS
-
+
@@ -56,11 +44,15 @@
Wishlist
Trending Now
-
+
+
+
Top Rated
-
+
+
+
diff --git a/projects/create-netflix/exercise/script.js b/projects/create-netflix/exercise/script.js
new file mode 100644
index 0000000..64819e9
--- /dev/null
+++ b/projects/create-netflix/exercise/script.js
@@ -0,0 +1,118 @@
+/*
+🌟 APP: Make Netflix
+
+Here we have the Netflix app but it's up to you to make it work by pulling all the movies using an API!
+
+Create a fetchMovies() function that will make a dynamic API call to what you need 👇
+========================================
+
+- fetchMovies()
+
+** fetchMovies takes in an URL, a div id or class from the HTML, and a path (poster or backdrop)
+
+
+
+These are the 3 main functions and their URL'S you must create 👇
+========================================
+
+- getOriginals()
+ * URL : 'https://api.themoviedb.org/3/discover/tv?api_key=19f84e11932abbc79e6d83f82d6d1045&with_networks=213'
+
+- getTrendingNow()
+ * URL : 'https://api.themoviedb.org/3/trending/movie/week?api_key=19f84e11932abbc79e6d83f82d6d1045'
+
+- getTopRated()
+ * URL : 'https://api.themoviedb.org/3/movie/top_rated?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US&page=1'
+
+
+** These functions will provide the URL you need to fetch() movies of that genere **
+
+These are all the DIV ID's you're gonna need access to 👇
+========================================================
+#1 CLASS 👉 'original__movies' = Div that holds Netflix Originals
+#2 ID 👉 'trending' = Div that holds trending Movies
+#3 ID 👉 'top_rated' = Div that holds top rated Movies
+*/
+
+// Call the main functions the page is loaded
+window.onload = () => {
+ getOriginals()
+ getTrendingNow()
+ getTopRated()
+}
+
+// ** Helper function that makes dynamic API calls **
+function fetchMovies(url, dom_element, path_type) {
+ // Use Fetch with the url passed down
+
+ // Within Fetch get the response and call showMovies() with the data , dom_element, and path type
+}
+
+// ** Function that displays the movies to the DOM **
+showMovies = (movies, dom_element, path_type) => {
+
+ // Create a variable that grabs id or class
+
+
+ // Loop through object
+
+
+ // Within loop create an img element
+
+
+ // Set attribute
+
+
+ // Set source
+
+
+ // Add event listener to handleMovieSelection() onClick
+
+
+ // Append the imageElement to the dom_element selected
+
+ }
+}
+
+// ** Function that fetches Netflix Originals **
+function getOriginals() {
+
+}
+// ** Function that fetches Trending Movies **
+function getTrendingNow() {
+
+}
+// ** Function that fetches Top Rated Movies **
+function getTopRated() {
+
+}
+
+// ** BONUS **
+
+// ** Fetches URL provided and returns response.json()
+async function getMovieTrailer(id) {
+ //URL: `https://api.themoviedb.org/3/movie/${id}/videos?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US`
+
+}
+
+// ** Function that adds movie data to the DOM
+const setTrailer = trailers => {
+ // Set up iframe variable to hold id of the movieTrailer Element
+ const iframe
+ // Set up variable to select .movieNotFound element
+ const movieNotFound
+
+ // If there is a trailer add the src for it
+ if (trailers.length > 0) {
+ // add d-none class to movieNotFound and remove it from iframe
+
+ // add youtube link with trailers key to iframe.src
+ } else {
+ // Else remove d-none class to movieNotfound and ADD it to iframe
+
+ }
+}
+
+
+
+
diff --git a/projects/fetchmovies/solution/style.css b/projects/create-netflix/exercise/style.css
similarity index 100%
rename from projects/fetchmovies/solution/style.css
rename to projects/create-netflix/exercise/style.css
diff --git a/projects/create-netflix/solution/index.html b/projects/create-netflix/solution/index.html
new file mode 100644
index 0000000..6f15727
--- /dev/null
+++ b/projects/create-netflix/solution/index.html
@@ -0,0 +1,105 @@
+
+
+
+
+
+ Netflix Clone
+
+
+
+
+
+
+
+
+
+
Title
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam tristique
+ magna magna, vit...
+
+
+
+
+
NETFLIX ORIGINALS
+
+
+
+
+
+
+
+
Wishlist
+
+
+
+
Trending Now
+
+
+
+
+
+
Top Rated
+
+
+
+
+
+
+
+
+
+
+
+
Modal title
+
+
+
+ Trailer Not Found
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/create-netflix/solution/script.js b/projects/create-netflix/solution/script.js
new file mode 100644
index 0000000..0da527a
--- /dev/null
+++ b/projects/create-netflix/solution/script.js
@@ -0,0 +1,153 @@
+/*
+🌟 APP: Make Netflix
+
+Create a fetchMovies() function that will make a dynamic API call to what you need 👇
+========================================
+
+- fetchMovies()
+
+** fetchMovies takes in an URL, a div id or class from the HTML, and a path (poster or backdrop)
+
+
+
+These are the 3 main functions you must create 👇
+========================================
+
+- getOriginals()
+
+- getTrendingNow()
+
+- getTopRated()
+
+
+** These functions will provide the URL you need to fetch movies of that genere **
+
+These are all the DIV ID's you're gonna need access to 👇
+========================================================
+#1 CLASS 👉 'original__movies' = Div that holds Netflix Originals
+#2 ID 👉 'trending' = Div that holds trending Movies
+#3 ID 👉 'top_rated' = Div that holds top rated Movies
+*/
+
+// Call the main functions the page is loaded
+window.onload = () => {
+ getOriginals()
+ getTrendingNow()
+ getTopRated()
+}
+
+// ** Helper function that makes dynamic API calls **
+// path_type 👉 (backdrop, poster)
+// dom_element 👉 (trending, top rated)
+// fetchMovies('https://api.themoviedb.org/3/movie/top_rated?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US&page=1', 'top_rated', 'backdrop_path')
+function fetchMovies(url, dom_element, path_type) {
+ fetch(url)
+ .then(response => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ throw new Error('something went wrong')
+ }
+ })
+ .then(data => {
+ showMovies(data, dom_element, path_type)
+ })
+ .catch(error_data => {
+ console.log(error_data)
+ })
+}
+
+// ** Function that displays the movies to the DOM **
+showMovies = (movies, dom_element, path_type) => {
+
+ // Create a variable that grabs id or class
+ var moviesEl = document.querySelector(dom_element)
+
+ // Loop through object
+ for (var movie of movies.results) {
+
+ // Within loop create an img element
+ var imageElement = document.createElement('img')
+
+ // Set attribute
+ imageElement.setAttribute('data-id', movie.id)
+
+ // Set source
+ imageElement.src = `https://image.tmdb.org/t/p/original${movie[path_type]}`
+
+ // Add event listener to handleMovieSelection() onClick
+ imageElement.addEventListener('click', e => {
+ handleMovieSelection(e)
+ })
+ // Append the imageElement to the dom_element selected
+ moviesEl.appendChild(imageElement)
+ }
+}
+
+// ** Function that fetches Netflix Originals **
+function getOriginals() {
+ var url =
+ 'https://api.themoviedb.org/3/discover/tv?api_key=19f84e11932abbc79e6d83f82d6d1045&with_networks=213'
+ fetchMovies(url, '.original__movies', 'poster_path')
+}
+// ** Function that fetches Trending Movies **
+function getTrendingNow() {
+ var url =
+ 'https://api.themoviedb.org/3/trending/movie/week?api_key=19f84e11932abbc79e6d83f82d6d1045'
+ fetchMovies(url, '#trending', 'backdrop_path')
+}
+// ** Function that fetches Top Rated Movies **
+function getTopRated() {
+ var url =
+ 'https://api.themoviedb.org/3/movie/top_rated?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US&page=1'
+ fetchMovies(url, '#top_rated', 'backdrop_path')
+}
+
+// ** BONUS **
+
+async function getMovieTrailer(id) {
+ var url = `https://api.themoviedb.org/3/movie/${id}/videos?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US`
+ return await fetch(url).then(response => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ throw new Error('something went wrong')
+ }
+ })
+}
+
+const setTrailer = trailers => {
+ const iframe = document.getElementById('movieTrailer')
+ const movieNotFound = document.querySelector('.movieNotFound')
+ if (trailers.length > 0) {
+ movieNotFound.classList.add('d-none')
+ iframe.classList.remove('d-none')
+ iframe.src = `https://www.youtube.com/embed/${trailers[0].key}`
+ } else {
+ iframe.classList.add('d-none')
+ movieNotFound.classList.remove('d-none')
+ }
+}
+
+const handleMovieSelection = e => {
+ const id = e.target.getAttribute('data-id')
+ const iframe = document.getElementById('movieTrailer')
+ // here we need the id of the movie
+ getMovieTrailer(id).then(data => {
+ const results = data.results
+ const youtubeTrailers = results.filter(result => {
+ if (result.site == 'YouTube' && result.type == 'Trailer') {
+ return true
+ } else {
+ return false
+ }
+ })
+ setTrailer(youtubeTrailers)
+ })
+
+ // open modal
+ $('#trailerModal').modal('show')
+ // we need to call the api with the ID
+}
+
+
diff --git a/projects/create-netflix/solution/style.css b/projects/create-netflix/solution/style.css
new file mode 100644
index 0000000..9ef9813
--- /dev/null
+++ b/projects/create-netflix/solution/style.css
@@ -0,0 +1,188 @@
+body {
+ background-color: #111;
+ color: white;
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ margin: 0;
+}
+
+.dropdown-container {
+ position: absolute;
+ top: 100%;
+ right: 0;
+ padding-top: 3px;
+ display: none;
+}
+
+.dropdown {
+ background-color: black;
+ padding: 8px;
+ border-radius: 4px;
+ width: 100px;
+}
+
+.dropdown span {
+ cursor: pointer;
+}
+
+.original__movies {
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+
+.profile:hover .dropdown-container {
+ display: block;
+}
+
+.original__movies,
+.movies__container {
+ padding-left: 50px;
+ display: flex;
+ overflow-x: scroll;
+ overflow: hidden;
+}
+
+.original__movies::-webkit-scrollbar {
+ display: none;
+}
+
+.original__movies img {
+ margin-right: 10px;
+ height: 250px;
+ width: auto;
+}
+
+.original__movies img,
+.movies__container img {
+ transition: all 0.2s ease-out;
+ cursor: pointer;
+}
+
+.original__movies img:hover,
+.movies__container img:hover {
+ transform: scale(1.1);
+}
+
+.movies__container img {
+ margin-right: 10px;
+ width: 200px;
+}
+
+header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-left: 15px;
+ padding-right: 15px;
+ position: absolute;
+ z-index: 100;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding-top: 15px;
+}
+
+.logo > img {
+ width: 120px;
+}
+
+.profile {
+ position: relative;
+}
+
+.profile > img {
+ width: 40px;
+}
+
+img {
+ width: 100px;
+}
+
+.featured {
+ height: 400px;
+ position: relative;
+ background-image: url('https://image.tmdb.org/t/p/original//3lBDg3i6nn5R2NKFCJ6oKyUo2j5.jpg');
+ background-size: cover;
+ background-position: center;
+ padding: 0 0 0 30px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.featured::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 7.4rem;
+ background-image: linear-gradient(
+ 180deg,
+ transparent,
+ rgba(37, 37, 37, 0.61),
+ #111
+ );
+}
+
+.netflixOriginals h2,
+.movies__header h2 {
+ padding-left: 30px;
+}
+
+.featured h2 {
+ font-size: 50px;
+ margin: 0;
+ z-index: 10;
+}
+
+.featured .featured__buttons {
+ z-index: 10;
+}
+
+.featured .featured__buttons button {
+ font-size: 16px;
+ color: white;
+ background-color: rgba(109, 109, 110, 0.7);
+ border: none;
+ padding: 8px 24px;
+ border-radius: 4px;
+}
+
+.featured::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.3);
+}
+
+.featured .featured__buttons .button__play {
+ background-color: white;
+ color: black;
+}
+
+.button__play i {
+ margin-right: 6px;
+}
+
+.featured .featured__description {
+ max-width: 350px;
+ font-weight: 400;
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.45);
+ z-index: 10;
+}
+
+.modal-body iframe {
+ width: 100%;
+}
+
+.modal-body span {
+ color: #333;
+ width: 100%;
+ display: flex;
+ height: 60px;
+ justify-content: center;
+ align-items: center;
+}
diff --git a/projects/fetchmovies/solution/script.js b/projects/fetchmovies/solution/script.js
deleted file mode 100644
index 6f623db..0000000
--- a/projects/fetchmovies/solution/script.js
+++ /dev/null
@@ -1,247 +0,0 @@
-console.log("YOOO")
-var firstName = 'Nazariy'
-let apiUrl = 'http://localhost:3000'
-if (location.href.indexOf('netlify') != -1) {
- apiUrl = 'https://netflix-cp.herokuapp.com'
-}
-
-// Called whe the page is loaded
-window.onload = () => {
- getOriginals()
- getTrendingNow()
- getTopRated()
- getWishList()
- getGenres()
- letVarExample()
-}
-
-function getWishList() {
- fetch(`${apiUrl}/wishlist`, {
- headers: {
- Authorization: `${localStorage.getItem('token')}`,
- },
- })
- .then(response => {
- if (response.ok) {
- return response.json()
- } else {
- throw new Error('something went wrong')
- }
- })
- .then(data => {
- showMovies(data, '#wishlist', 'backdrop_path')
- })
- .catch(error_data => {
- logOut()
- console.log(error_data)
- })
-}
-
-function letVarExample(firstName = 'Nazariy') {
- // Melissas Address
- const address = {
- street: '9879 Test rd.',
- city: 'Brooklyn',
- state: 'NY',
- }
-
- // address.state = "MI";
-
- // let state = address.state;
- // state = "MI"
- // console.log(address);
-
- // let address2 = address;
- // address2.state = "MI";
-
- // let address2 = {
- // ...address,
- // apartment: "MI"
- // }
-
- // let { street, city, state } = address;
-
- // console.log(street + city + state);
-}
-
-async function getMovieTrailer(id) {
- var url = `https://api.themoviedb.org/3/movie/${id}/videos?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US`
- return await fetch(url).then(response => {
- if (response.ok) {
- return response.json()
- } else {
- throw new Error('something went wrong')
- }
- })
-}
-
-const setTrailer = trailers => {
- const iframe = document.getElementById('movieTrailer')
- const movieNotFound = document.querySelector('.movieNotFound')
- if (trailers.length > 0) {
- movieNotFound.classList.add('d-none')
- iframe.classList.remove('d-none')
- iframe.src = `https://www.youtube.com/embed/${trailers[0].key}`
- } else {
- iframe.classList.add('d-none')
- movieNotFound.classList.remove('d-none')
- }
-}
-
-const handleMovieSelection = e => {
- const id = e.target.getAttribute('data-id')
- const iframe = document.getElementById('movieTrailer')
- // here we need the id of the movie
- getMovieTrailer(id).then(data => {
- const results = data.results
- const youtubeTrailers = results.filter(result => {
- if (result.site == 'YouTube' && result.type == 'Trailer') {
- return true
- } else {
- return false
- }
- })
- setTrailer(youtubeTrailers)
- })
-
- // open modal
- $('#trailerModal').modal('show')
- // we need to call the api with the ID
-}
-
-showMovies = (movies, element_selector, path_type) => {
- var moviesEl = document.querySelector(element_selector)
- for (var movie of movies.results) {
- var imageElement = document.createElement('img')
- imageElement.setAttribute('data-id', movie.id)
-
- imageElement.src = `https://image.tmdb.org/t/p/original${movie[path_type]}`
-
- imageElement.addEventListener('click', e => {
- handleMovieSelection(e)
- })
- moviesEl.appendChild(imageElement)
- }
-}
-
-function fetchMoviesBasedOnGenre(genreId) {
- var url = 'https://api.themoviedb.org/3/discover/movie?'
- url +=
- 'api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1'
- url += `&with_genres=${genreId}`
- return fetch(url).then(response => {
- if (response.ok) {
- return response.json()
- } else {
- throw new Error('something went wrong')
- }
- }) // returns a promise already
-}
-
-function fetchMovies(url, element_selector, path_type) {
- fetch(url)
- .then(response => {
- if (response.ok) {
- return response.json()
- } else {
- throw new Error('something went wrong')
- }
- })
- .then(data => {
- showMovies(data, element_selector, path_type)
- })
- .catch(error_data => {
- console.log(error_data)
- })
-}
-
-function showMoviesGenres(genres) {
- genres.genres.forEach(function (genre) {
- // get list of movies
- var movies = fetchMoviesBasedOnGenre(genre.id)
- movies
- .then(function (movies) {
- showMoviesBasedOnGenre(genre.name, movies)
- })
- .catch(function (error) {
- console.log('BAD BAD', error)
- })
- // show movies based on genre
- })
-}
-
-function showMoviesBasedOnGenre(genreName, movies) {
- let allMovies = document.querySelector('.movies')
- let genreEl = document.createElement('div')
- genreEl.classList.add('movies__header')
- genreEl.innerHTML = `
-
${genreName}
- `
- let moviesEl = document.createElement('div')
- moviesEl.classList.add('movies__container')
- moviesEl.setAttribute('id', genreName)
-
- for (var movie of movies.results) {
- var imageElement = document.createElement('img')
- let { backdrop_path, id } = movie
- console.log('TESTING DESCONSTRUCT:', id, backdrop_path)
- imageElement.setAttribute('data-id', id)
-
- imageElement.src = `https://image.tmdb.org/t/p/original${backdrop_path}`
-
- imageElement.addEventListener('click', e => {
- handleMovieSelection(e)
- })
- moviesEl.appendChild(imageElement)
- }
-
- allMovies.appendChild(genreEl)
- allMovies.appendChild(moviesEl)
-}
-
-function getGenres() {
- var url =
- 'https://api.themoviedb.org/3/genre/movie/list?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US'
- fetch(url)
- .then(response => {
- if (response.ok) {
- return response.json()
- } else {
- throw new Error('something went wrong')
- }
- })
- .then(data => {
- showMoviesGenres(data)
- })
- .catch(error_data => {
- console.log(error_data)
- })
-}
-
-function getOriginals() {
- var url =
- 'https://api.themoviedb.org/3/discover/tv?api_key=19f84e11932abbc79e6d83f82d6d1045&with_networks=213'
- fetchMovies(url, '.original__movies', 'poster_path')
-}
-
-function getTrendingNow() {
- var url =
- 'https://api.themoviedb.org/3/trending/movie/week?api_key=19f84e11932abbc79e6d83f82d6d1045'
- fetchMovies(url, '#trending', 'backdrop_path')
-}
-
-function getTopRated() {
- var url =
- 'https://api.themoviedb.org/3/movie/top_rated?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US&page=1'
- fetchMovies(url, '#top_rated', 'backdrop_path')
-}
-
-// Loop through list of genres
-// Show genres in HTML
-// Fetch movies based on genre fetchMovies(url, genre, classInHTML)
-// Display the list of movies
-
-// https://api.themoviedb.org/3/discover/movie?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=28
-
-// Movies genres
-// https://api.themoviedb.org/3/genre/movie/list?api_key=19f84e11932abbc79e6d83f82d6d1045&language=en-US
diff --git a/projects/fightingGame/exercise/index.html b/projects/fightingGame/exercise/index.html
index 51df0c6..22aaa3e 100644
--- a/projects/fightingGame/exercise/index.html
+++ b/projects/fightingGame/exercise/index.html
@@ -9,7 +9,103 @@
- Code the Game class in script.js
+
+
+
+
+
+
+
Player 1
+
+
+
+
+
+
+
Q:
+
+
+
+
+
A:
+
+
+
+
+
100
+
+
+
+
+
+
+
Player 2
+
+
+
+
+
P:
+
+
+
+
L:
+
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/fightingGame/exercise/script.js b/projects/fightingGame/exercise/script.js
index c6bd8cb..b51f254 100644
--- a/projects/fightingGame/exercise/script.js
+++ b/projects/fightingGame/exercise/script.js
@@ -1,72 +1,186 @@
+/*
+🌟 APP: Fighting Game
+
+Create an updateGame() function that will update the DOM with the state of the game 👇
+========================================
+
+- updateGame()
+
+These are the 2 classes you must create and their methods 👇
+========================================
+
+class Player {
+ - strike()
+ - heal()
+}
+
class Game {
- constructor(player1Name = 'pl1', player2Name = 'pl2') {
- // Flag that indicates if the game is over or not
- this.theEnd = false;
-
- this.player1 = {
- name: player1Name,
- health: 100
- };
-
- this.player2 = {
- name: player2Name,
- health: 100
- };
- }
+ - play()
+ - checkIsOver()
+ - declareWinner()
+ - reset()
+}
- //Starts the game and logs out the status of players
- start() {
+These functions are hard coded in the HTML. So, you can't change their names.
- }
+These are all the DIV ID's you're gonna need access to 👇
+========================================================
+#1 ID 👉 'play' = Button to run simulation
+#2 ID 👉 'result' = Div that holds the winner of the match
+#3 ID 👉 'p1Name' = Div that holds player 1's Name
+#4 ID 👉 'p2Name' = Div that holds player 2's Name
+#5 ID 👉 'p1Health' = Div that holds player 1's health
+#6 ID 👉 'p2Health' = Div that holds player 2's health
+*/
- //Console log the winner of the battle
- declareWinner() {
+// ** Grabs elements from the DOM and stores them into variables **
+let playButton = document.getElementById('play')
+let resultDiv = document.getElementById('result')
+let p1NameDiv = document.getElementById('p1Name')
+let p2NameDiv = document.getElementById('p2Name')
+let p1HealthDiv = document.getElementById('p1Health')
+let p2HealthDiv = document.getElementById('p2Health')
- }
+// ** Check if either players health is 0 and if it is, then update isOver to true **
+const updateGame = (p1,p2,gameState) => {
+ // Update the DOM with the names and the latest health of players
- //If player 1 or player 2 health is below 0
- //Mark theEnd true, to stop the game
- checkTheEnd() {
+ // Condition IF either player health is <= 0 then set isOver to true and declareWinner
+}
+
+// ** Create the Player class which can create a player with all it's attributes and methods **
+// qazi = new Player('Qazi', 100, 7)
+// qazi.name 👉 'Qazi'
+// qazi.health 👉 100
+// qazi.attackDmg 👉 7
+class Player {
+ constructor(name, health, attackDamage) {
+ this.name = name;
+ this.health = health;
+ this.attackDmg = attackDamage;
}
+ // ** Attack an enemy with a random number from 0 to YOUR attackDmg bonus **
+ strike (player, enemy, attackDmg) {
+
+ // Get random number between 1 - 10 and that is damageAmount
- //Console log the name and health of both players
- //Ex: Player 1: 100 | Player 2: 50
- playerStatus() {
+ // Subtract the enemy health with the damageAmount
- }
+ // Update the game and DOM with updateGame()
- //Reset health of player 1 and player 2 to 100
- //Reset theEnd to false
- reset() {
+ // Return a message of 'player name attacks enemy name for damageAmount'
}
+ // ** Heal the player for random number from 1 to 5 **
+ heal (player) {
+
+ // Get random number between 1 - 5 and store that in hpAmount
- //Generate a random number between 1 and 10 using Math.random()
- //Use that number to decrease health from player 2
- pl1AttackPl2() {
+ // Add hpAmount to players health
- }
+ // Update the game and DOM with updateGame()
- //Generate a random number between 1 and 10 using Math.random()
- //Use that number to decrease health from player 1
- pl2AttackPl1() {
+ // Return a message of 'player name heals for hpAmount HP'
}
+}
- //Generate a random number between 1 and 5 using Math.random()
- //Use that number to increase health of player 1
- pl1Heal() {
+// ** Create the Game class with all it's attributes and methods to run a match **
+// game = new Game()
+// game.isOver 👉 false
+class Game {
+ constructor() {
+ this.isOver = false;
+ }
+
+ // ** If the game is over and a player has 0 health declare the winner! **
+ declareWinner(isOver,p1, p2) {
+
+ // Create a message variable that will hold a message based on the condition
+
+ // If isOver is true AND p1 health is <= 0 then update message variable to 'p1 WINS!'
+
+ // Else if isOver is true AND p2 health is <= 0 then update message variable to 'p2 WINS!'
+ // Play victory sound
+
+ // Return message variable
}
- //Generate a random number between 1 and 5 using Math.random()
- //Use that number to increase health of player 2
- pl2Heal() {
+ // ** Reset the players health back to it's original state and isOver to FALSE **
+ reset(p1,p2) {
+ // set p1 health and p2 health back to 100 and isOver back to false and clear resultDiv.innerText and don't forget to updateGame()
}
+
+ // ** Simulates the whole match untill one player runs out of health **
+ play(p1, p2) {
+ // Reset to make sure player health is back to full before starting
+
+ // Make sure the players take turns untill isOver is TRUE
+ while (!this.isOver) {
+ //Make sure both players get strike() and heal() once each loop
+ }
+ // Once isOver is TRUE run the declareWinner() method
+
+ }
+
}
-// Initialize the class here
-// Call the start function of the game
+// ** Create 2 players using the player class **
+
+
+// ** Save original Player Data into a variable in order to reset **
+let p1;
+let p2;
+
+// ** Create the game object from the Game class **
+
+// ** Intialize the game by calling updateGame() **
+
+
+// ** Save intial isOver from the game object inside this variable **
+let gameState;
+
+
+// ** Add a click listener to the simulate button that runs the play() method on click and pass in the players **
+
+
+// Add functionality where players can press a button to attack OR heal
+
+// ** Player 1 Controls **
+document.addEventListener('keydown', function(e) {
+ // if you press Q AND the enemy health is greater than 0 AND isOver is still false then strike()
+
+ // After striking then play attack sound
+
+});
+
+document.addEventListener('keydown', function(e) {
+
+ // if you press a AND the player health is greater than 0 AND isOver is still false then strike()
+
+ // After healing then play heal sound
+
+});
+
+// ** Player 2 Controls **
+document.addEventListener('keydown', function(e) {
+
+ // if you press p AND enemy health is greater than 0 AND isOver is still false then stike()
+
+ // After striking then play attack sound
+
+});
+
+document.addEventListener('keydown', function(e) {
+ // if you press l AND the player health is greater than 0 AND isOver is still false then heal()
+
+ // After healing then play heal sound
+
+});
+
+
+
diff --git a/projects/fightingGame/exercise/sounds/fastheal.mp3 b/projects/fightingGame/exercise/sounds/fastheal.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..d8a26512f1b2eb39fdffcd2aaafcd52a1986cf9d
GIT binary patch
literal 18059
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