[
  {
    "path": "breakout-game/README.md",
    "content": "## Breakout! Game\r\n\r\nGame where you control a paddle with the arrow keys to bounce a ball up to break bricks. This app uses the HTML5 canvas element and API\r\n\r\n## Project Specifications\r\n\r\n- Draw elements on canvas\r\n- Use canvas paths to draw shapes\r\n- Add animation with requestAnimationFrame(cb)\r\n- Move paddle on arrow key press\r\n- Add collision detection\r\n- Keep score\r\n- Add rules button with slider\r\n"
  },
  {
    "path": "breakout-game/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Breakout!</title>\n  </head>\n  <body>\n    <h1>Breakout!</h1>\n    <button id=\"rules-btn\" class=\"btn rules-btn\">Show Rules</button>\n    <div id=\"rules\" class=\"rules\">\n      <h2>How To Play:</h2>\n      <p>\n        Use your right and left keys to move the paddle to bounce the ball up\n        and break the blocks.\n      </p>\n      <p>If you miss the ball, your score and the blocks will reset.</p>\n      <button id=\"close-btn\" class=\"btn\">Close</button>\n    </div>\n\n    <canvas id=\"canvas\" width=\"800\" height=\"600\"></canvas>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "breakout-game/plan.txt",
    "content": "1. Create canvas context \n2. Create and draw ball \n3. Create and draw paddle \n4. Create bricks \n5. Draw score \n6. Add update() - Animate - requestAnimationFrame(cb) \n7. Move paddle \n8. Keyboard event handlers to move paddle \n9. Move ball \n10. Add wall bounderies \n11. Increase score when bricks break\n12. Lose - redraw bricks, reset score"
  },
  {
    "path": "breakout-game/script.js",
    "content": "const rulesBtn = document.getElementById('rules-btn');\nconst closeBtn = document.getElementById('close-btn');\nconst rules = document.getElementById('rules');\nconst canvas = document.getElementById('canvas');\nconst ctx = canvas.getContext('2d');\n\nlet score = 0;\n\nconst brickRowCount = 9;\nconst brickColumnCount = 5;\nconst delay = 500; //delay to reset the game\n\n// Create ball props\nconst ball = {\n  x: canvas.width / 2,\n  y: canvas.height / 2,\n  size: 10,\n  speed: 4,\n  dx: 4,\n  dy: -4,\n  visible: true\n};\n\n// Create paddle props\nconst paddle = {\n  x: canvas.width / 2 - 40,\n  y: canvas.height - 20,\n  w: 80,\n  h: 10,\n  speed: 8,\n  dx: 0,\n  visible: true\n};\n\n// Create brick props\nconst brickInfo = {\n  w: 70,\n  h: 20,\n  padding: 10,\n  offsetX: 45,\n  offsetY: 60,\n  visible: true\n};\n\n// Create bricks\nconst bricks = [];\nfor (let i = 0; i < brickRowCount; i++) {\n  bricks[i] = [];\n  for (let j = 0; j < brickColumnCount; j++) {\n    const x = i * (brickInfo.w + brickInfo.padding) + brickInfo.offsetX;\n    const y = j * (brickInfo.h + brickInfo.padding) + brickInfo.offsetY;\n    bricks[i][j] = { x, y, ...brickInfo };\n  }\n}\n\n// Draw ball on canvas\nfunction drawBall() {\n  ctx.beginPath();\n  ctx.arc(ball.x, ball.y, ball.size, 0, Math.PI * 2);\n  ctx.fillStyle = ball.visible ? '#0095dd' : 'transparent';\n  ctx.fill();\n  ctx.closePath();\n}\n\n// Draw paddle on canvas\nfunction drawPaddle() {\n  ctx.beginPath();\n  ctx.rect(paddle.x, paddle.y, paddle.w, paddle.h);\n  ctx.fillStyle = paddle.visible ? '#0095dd' : 'transparent';\n  ctx.fill();\n  ctx.closePath();\n}\n\n// Draw score on canvas\nfunction drawScore() {\n  ctx.font = '20px Arial';\n  ctx.fillText(`Score: ${score}`, canvas.width - 100, 30);\n}\n\n// Draw bricks on canvas\nfunction drawBricks() {\n  bricks.forEach(column => {\n    column.forEach(brick => {\n      ctx.beginPath();\n      ctx.rect(brick.x, brick.y, brick.w, brick.h);\n      ctx.fillStyle = brick.visible ? '#0095dd' : 'transparent';\n      ctx.fill();\n      ctx.closePath();\n    });\n  });\n}\n\n// Move paddle on canvas\nfunction movePaddle() {\n  paddle.x += paddle.dx;\n\n  // Wall detection\n  if (paddle.x + paddle.w > canvas.width) {\n    paddle.x = canvas.width - paddle.w;\n  }\n\n  if (paddle.x < 0) {\n    paddle.x = 0;\n    }\n}\n\n// Move ball on canvas\nfunction moveBall() {\n  ball.x += ball.dx;\n  ball.y += ball.dy;\n\n  // Wall collision (right/left)\n  if (ball.x + ball.size > canvas.width || ball.x - ball.size < 0) {\n    ball.dx *= -1; // ball.dx = ball.dx * -1\n  }\n\n  // Wall collision (top/bottom)\n  if (ball.y + ball.size > canvas.height || ball.y - ball.size < 0) {\n    ball.dy *= -1;\n  }\n\n  // console.log(ball.x, ball.y);\n\n  // Paddle collision\n  if (\n    ball.x - ball.size > paddle.x &&\n    ball.x + ball.size < paddle.x + paddle.w &&\n    ball.y + ball.size > paddle.y\n  ) {\n    ball.dy = -ball.speed;\n  }\n\n  // Brick collision\n  bricks.forEach(column => {\n    column.forEach(brick => {\n      if (brick.visible) {\n        if (\n          ball.x - ball.size > brick.x && // left brick side check\n          ball.x + ball.size < brick.x + brick.w && // right brick side check\n          ball.y + ball.size > brick.y && // top brick side check\n          ball.y - ball.size < brick.y + brick.h // bottom brick side check\n        ) {\n          ball.dy *= -1;\n          brick.visible = false;\n\n          increaseScore();\n        }\n      }\n    });\n  });\n\n  // Hit bottom wall - Lose\n  if (ball.y + ball.size > canvas.height) {\n    showAllBricks();\n    score = 0;\n  }\n}\n\n// Increase score\nfunction increaseScore() {\n  score++;\n\n  if (score % (brickRowCount * brickColumnCount) === 0) {\n\n      ball.visible = false;\n      paddle.visible = false;\n\n      //After 0.5 sec restart the game\n      setTimeout(function () {\n          showAllBricks();\n          score = 0;\n          paddle.x = canvas.width / 2 - 40;\n          paddle.y = canvas.height - 20;\n          ball.x = canvas.width / 2;\n          ball.y = canvas.height / 2;\n          ball.visible = true;\n          paddle.visible = true;\n      },delay)\n  }\n}\n\n// Make all bricks appear\nfunction showAllBricks() {\n  bricks.forEach(column => {\n    column.forEach(brick => (brick.visible = true));\n  });\n}\n\n// Draw everything\nfunction draw() {\n  // clear canvas\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n  drawBall();\n  drawPaddle();\n  drawScore();\n  drawBricks();\n}\n\n// Update canvas drawing and animation\nfunction update() {\n  movePaddle();\n  moveBall();\n\n  // Draw everything\n  draw();\n\n  requestAnimationFrame(update);\n}\n\nupdate();\n\n// Keydown event\nfunction keyDown(e) {\n  if (e.key === 'Right' || e.key === 'ArrowRight') {\n    paddle.dx = paddle.speed;\n  } else if (e.key === 'Left' || e.key === 'ArrowLeft') {\n    paddle.dx = -paddle.speed;\n  }\n}\n\n// Keyup event\nfunction keyUp(e) {\n  if (\n    e.key === 'Right' ||\n    e.key === 'ArrowRight' ||\n    e.key === 'Left' ||\n    e.key === 'ArrowLeft'\n  ) {\n    paddle.dx = 0;\n  }\n}\n\n// Keyboard event handlers\ndocument.addEventListener('keydown', keyDown);\ndocument.addEventListener('keyup', keyUp);\n\n// Rules and close event handlers\nrulesBtn.addEventListener('click', () => rules.classList.add('show'));\ncloseBtn.addEventListener('click', () => rules.classList.remove('show'));\n"
  },
  {
    "path": "breakout-game/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #0095dd;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  font-family: Arial, Helvetica, sans-serif;\n  min-height: 100vh;\n  margin: 0;\n}\n\nh1 {\n  font-size: 45px;\n  color: #fff;\n}\n\ncanvas {\n  background: #f0f0f0;\n  display: block;\n  border-radius: 5px;\n}\n\n.btn {\n  cursor: pointer;\n  border: 0;\n  padding: 10px 20px;\n  background: #000;\n  color: #fff;\n  border-radius: 5px;\n}\n\n.btn:focus {\n  outline: 0;\n}\n\n.btn:hover {\n  background: #222;\n}\n\n.btn:active {\n  transform: scale(0.98);\n}\n\n.rules-btn {\n  position: absolute;\n  top: 30px;\n  left: 30px;\n}\n\n.rules {\n  position: absolute;\n  top: 0;\n  left: 0;\n  background: #333;\n  color: #fff;\n  min-height: 100vh;\n  width: 400px;\n  padding: 20px;\n  line-height: 1.5;\n  transform: translateX(-400px);\n  transition: transform 1s ease-in-out;\n}\n\n.rules.show {\n  transform: translateX(0);\n}\n"
  },
  {
    "path": "custom-video-player/README.md",
    "content": "## Custom Video Player\r\n\r\nCustom video player using the HTML5 video element and it's JavaScript API with a custom design\r\n\r\n## Project Specifications\r\n\r\n- Display custom video player styled with CSS\r\n- Play/pause\r\n- Stop\r\n- Video progress bar\r\n- Set progress bar time\r\n- Display time in mins and seconds\r\n"
  },
  {
    "path": "custom-video-player/css/progress.css",
    "content": "/* SOURCE: https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/ */\r\n\r\ninput[type='range'] {\r\n\t-webkit-appearance: none; /* Hides the slider so that custom slider can be made */\r\n\twidth: 100%; /* Specific width is required for Firefox. */\r\n\tbackground: transparent; /* Otherwise white in Chrome */\r\n}\r\n\r\ninput[type='range']::-webkit-slider-thumb {\r\n\t-webkit-appearance: none;\r\n}\r\n\r\ninput[type='range']:focus {\r\n\toutline: none; /* Removes the blue border. You should probably do some kind of focus styling for accessibility reasons though. */\r\n}\r\n\r\ninput[type='range']::-ms-track {\r\n\twidth: 100%;\r\n\tcursor: pointer;\r\n\r\n\t/* Hides the slider so custom styles can be added */\r\n\tbackground: transparent;\r\n\tborder-color: transparent;\r\n\tcolor: transparent;\r\n}\r\n\r\n/* Special styling for WebKit/Blink */\r\ninput[type='range']::-webkit-slider-thumb {\r\n\t-webkit-appearance: none;\r\n\tborder: 1px solid #000000;\r\n\theight: 36px;\r\n\twidth: 16px;\r\n\tborder-radius: 3px;\r\n\tbackground: #ffffff;\r\n\tcursor: pointer;\r\n\tmargin-top: -14px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */\r\n\tbox-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; /* Add cool effects to your sliders! */\r\n}\r\n\r\n/* All the same stuff for Firefox */\r\ninput[type='range']::-moz-range-thumb {\r\n\tbox-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;\r\n\tborder: 1px solid #000000;\r\n\theight: 36px;\r\n\twidth: 16px;\r\n\tborder-radius: 3px;\r\n\tbackground: #ffffff;\r\n\tcursor: pointer;\r\n}\r\n\r\n/* All the same stuff for IE */\r\ninput[type='range']::-ms-thumb {\r\n\tbox-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;\r\n\tborder: 1px solid #000000;\r\n\theight: 36px;\r\n\twidth: 16px;\r\n\tborder-radius: 3px;\r\n\tbackground: #ffffff;\r\n\tcursor: pointer;\r\n}\r\n\r\ninput[type='range']::-webkit-slider-runnable-track {\r\n\twidth: 100%;\r\n\theight: 8.4px;\r\n\tcursor: pointer;\r\n\tbox-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;\r\n\tbackground: #3071a9;\r\n\tborder-radius: 1.3px;\r\n\tborder: 0.2px solid #010101;\r\n}\r\n\r\ninput[type='range']:focus::-webkit-slider-runnable-track {\r\n\tbackground: #367ebd;\r\n}\r\n\r\ninput[type='range']::-moz-range-track {\r\n\twidth: 100%;\r\n\theight: 8.4px;\r\n\tcursor: pointer;\r\n\tbox-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;\r\n\tbackground: #3071a9;\r\n\tborder-radius: 1.3px;\r\n\tborder: 0.2px solid #010101;\r\n}\r\n\r\ninput[type='range']::-ms-track {\r\n\twidth: 100%;\r\n\theight: 8.4px;\r\n\tcursor: pointer;\r\n\tbackground: transparent;\r\n\tborder-color: transparent;\r\n\tborder-width: 16px 0;\r\n\tcolor: transparent;\r\n}\r\ninput[type='range']::-ms-fill-lower {\r\n\tbackground: #2a6495;\r\n\tborder: 0.2px solid #010101;\r\n\tborder-radius: 2.6px;\r\n\tbox-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;\r\n}\r\ninput[type='range']:focus::-ms-fill-lower {\r\n\tbackground: #3071a9;\r\n}\r\ninput[type='range']::-ms-fill-upper {\r\n\tbackground: #3071a9;\r\n\tborder: 0.2px solid #010101;\r\n\tborder-radius: 2.6px;\r\n\tbox-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;\r\n}\r\ninput[type='range']:focus::-ms-fill-upper {\r\n\tbackground: #367ebd;\r\n}\r\n"
  },
  {
    "path": "custom-video-player/css/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Questrial&display=swap');\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  font-family: 'Questrial', sans-serif;\n  background-color: #666;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  max-height: 100vh;\n  margin: 0;\n}\n\nh1 {\n  color: #fff;\n}\n\n.screen {\n  cursor: pointer;\n  width: 60%;\n  background-color: #000 !important;\n  border-top-left-radius: 10px;\n  border-top-right-radius: 10px;\n}\n\n.controls {\n  background: #333;\n  color: #fff;\n  width: 60%;\n  border-bottom-left-radius: 10px;\n  border-bottom-right-radius: 10px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  padding: 10px;\n}\n\n.controls .btn {\n  border: 0;\n  background: transparent;\n  cursor: pointer;\n}\n\n.controls .fa-play {\n  color: #28a745;\n}\n\n.controls .fa-stop {\n  color: #dc3545;\n}\n\n.controls .fa-pause {\n  color: #fff;\n}\n\n.controls .timestamp {\n  color: #fff;\n  font-weight: bold;\n  margin-left: 10px;\n}\n\n.btn:focus {\n  outline: 0;\n}\n\n@media (max-width: 800px) {\n  .screen,\n  .controls {\n    width: 90%;\n  }\n}\n"
  },
  {
    "path": "custom-video-player/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <title>Custom Video Player</title>\n    <link\n      href=\"https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css\"\n      rel=\"stylesheet\"\n      integrity=\"sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN\"\n      crossorigin=\"anonymous\"\n    />\n    <link rel=\"stylesheet\" href=\"css/progress.css\" />\n    <link rel=\"stylesheet\" href=\"css/style.css\" />\n  </head>\n  <body>\n    <h1>Custom Video Player</h1>\n    <video\n      src=\"videos/gone.mp4\"\n      id=\"video\"\n      class=\"screen\"\n      poster=\"img/poster.png\"\n    ></video>\n    <div class=\"controls\">\n      <button class=\"btn\" id=\"play\">\n        <i class=\"fa fa-play fa-2x\"></i>\n      </button>\n      <button class=\"btn\" id=\"stop\">\n        <i class=\"fa fa-stop fa-2x\"></i>\n      </button>\n      <input\n        type=\"range\"\n        id=\"progress\"\n        class=\"progress\"\n        min=\"0\"\n        max=\"100\"\n        step=\"0.1\"\n        value=\"0\"\n      />\n      <span class=\"timestamp\" id=\"timestamp\">00:00</span>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n\n<!-- https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs -->\n"
  },
  {
    "path": "custom-video-player/script.js",
    "content": "const video = document.getElementById('video');\nconst play = document.getElementById('play');\nconst stop = document.getElementById('stop');\nconst progress = document.getElementById('progress');\nconst timestamp = document.getElementById('timestamp');\n\n// Play & pause video\nfunction toggleVideoStatus() {\n  if (video.paused) {\n    video.play();\n  } else {\n    video.pause();\n  }\n}\n\n// update play/pause icon\nfunction updatePlayIcon() {\n  if (video.paused) {\n    play.innerHTML = '<i class=\"fa fa-play fa-2x\"></i>';\n  } else {\n    play.innerHTML = '<i class=\"fa fa-pause fa-2x\"></i>';\n  }\n}\n\n// Update progress & timestamp\nfunction updateProgress() {\n  progress.value = (video.currentTime / video.duration) * 100;\n\n  // Get the minutes\n  let mins = Math.floor(video.currentTime / 60);\n  if(mins < video.duration){\n    mins = '0' + String(mins);\n  }\n\n  // Get Seconds\n  let secs = Math.floor(video.currentTime % 60);\n  if(secs < video.duration){\n    secs = '0' + String(secs);\n  }\n\n  timestamp.innerHTML = `${mins}:${secs}`;\n}\n\n// Set video time to progress\nfunction setVideoProgress() {\n  video.currentTime = (+progress.value * video.duration) / 100;\n}\n\n// Stop video\nfunction stopVideo() {\n  video.currentTime = 0;\n  video.pause();\n}\n\n// Event listeners\nvideo.addEventListener('click', toggleVideoStatus);\nvideo.addEventListener('pause', updatePlayIcon);\nvideo.addEventListener('play', updatePlayIcon);\nvideo.addEventListener('timeupdate', updateProgress);\n\nplay.addEventListener('click', toggleVideoStatus);\n\nstop.addEventListener('click', stopVideo);\n\nprogress.addEventListener('change', setVideoProgress);\n"
  },
  {
    "path": "dom-array-methods/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>DOM Array Methods</title>\n  </head>\n  <body>\n    <h1>DOM Array Methods</h1>\n\n    <div class=\"container\">\n      <aside>\n        <button id=\"add-user\">Add User 👱‍♂️</button>\n        <button id=\"double\">Double Money 💰</button>\n        <button id=\"show-millionaires\">Show Only Millionaires 💵</button>\n        <button id=\"sort\">Sort by Richest ↓</button>\n        <button id=\"calculate-wealth\">Calculate entire Wealth 🧮</button>\n      </aside>\n\n      <main id=\"main\">\n        <h2><strong>Person</strong> Wealth</h2>\n      </main>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "dom-array-methods/readme.md",
    "content": "## DOM Array Methods\n\nProject to teach high order array methods and DOM manipulation\n\n## Project Specifications\n\n- Fetch random users from the [randomuser.me](https://randomuser.me) API\n- Use forEach() to loop and output user/wealth\n- Use map() to double wealth\n- Use filter() to filter only millionaires\n- Use sort() to sort by wealth\n- Use reduce() to add all wealth\n"
  },
  {
    "path": "dom-array-methods/script.js",
    "content": "const main = document.getElementById('main');\nconst addUserBtn = document.getElementById('add-user');\nconst doubleBtn = document.getElementById('double');\nconst showMillionairesBtn = document.getElementById('show-millionaires');\nconst sortBtn = document.getElementById('sort');\nconst calculateWealthBtn = document.getElementById('calculate-wealth');\n\nlet data = [];\n\ngetRandomUser();\ngetRandomUser();\ngetRandomUser();\n\n// Fetch random user and add money\nasync function getRandomUser() {\n  const res = await fetch('https://randomuser.me/api');\n  const data = await res.json();\n\n  const user = data.results[0];\n\n  const newUser = {\n    name: `${user.name.first} ${user.name.last}`,\n    money: Math.floor(Math.random() * 1000000)\n  };\n\n  addData(newUser);\n}\n\n// Double eveyones money\nfunction doubleMoney() {\n  data = data.map(user => {\n    return { ...user, money: user.money * 2 };\n  });\n\n  updateDOM();\n}\n\n// Sort users by richest\nfunction sortByRichest() {\n  console.log(123);\n  data.sort((a, b) => b.money - a.money);\n\n  updateDOM();\n}\n\n// Filter only millionaires\nfunction showMillionaires() {\n  data = data.filter(user => user.money > 1000000);\n\n  updateDOM();\n}\n\n// Calculate the total wealth\nfunction calculateWealth() {\n  const wealth = data.reduce((acc, user) => (acc += user.money), 0);\n\n  const wealthEl = document.createElement('div');\n  wealthEl.innerHTML = `<h3>Total Wealth: <strong>${formatMoney(\n    wealth\n  )}</strong></h3>`;\n  main.appendChild(wealthEl);\n}\n\n// Add new obj to data arr\nfunction addData(obj) {\n  data.push(obj);\n\n  updateDOM();\n}\n\n// Update DOM\nfunction updateDOM(providedData = data) {\n  // Clear main div\n  main.innerHTML = '<h2><strong>Person</strong> Wealth</h2>';\n\n  providedData.forEach(item => {\n    const element = document.createElement('div');\n    element.classList.add('person');\n    element.innerHTML = `<strong>${item.name}</strong> ${formatMoney(\n      item.money\n    )}`;\n    main.appendChild(element);\n  });\n}\n\n// Format number as money - https://stackoverflow.com/questions/149055/how-to-format-numbers-as-currency-string\nfunction formatMoney(number) {\n  return '$' + number.toFixed(2).replace(/\\d(?=(\\d{3})+\\.)/g, '$&,');\n}\n\n// Event listeners\naddUserBtn.addEventListener('click', getRandomUser);\ndoubleBtn.addEventListener('click', doubleMoney);\nsortBtn.addEventListener('click', sortByRichest);\nshowMillionairesBtn.addEventListener('click', showMillionaires);\ncalculateWealthBtn.addEventListener('click', calculateWealth);\n"
  },
  {
    "path": "dom-array-methods/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nbody {\n  background: #f4f4f4;\n  font-family: Arial, Helvetica, sans-serif;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  min-height: 100vh;\n  margin: 0;\n}\n\n.container {\n  display: flex;\n  padding: 20px;\n  margin: 0 auto;\n  max-width: 100%;\n  width: 800px;\n}\n\naside {\n  padding: 10px 20px;\n  width: 250px;\n  border-right: 1px solid #111;\n}\n\nbutton {\n  cursor: pointer;\n  background-color: #fff;\n  border: solid 1px #111;\n  border-radius: 5px;\n  display: block;\n  width: 100%;\n  padding: 10px;\n  margin-bottom: 20px;\n  font-weight: bold;\n  font-size: 14px;\n}\n\nmain {\n  flex: 1;\n  padding: 10px 20px;\n}\n\nh2 {\n  border-bottom: 1px solid #111;\n  padding-bottom: 10px;\n  display: flex;\n  justify-content: space-between;\n  font-weight: 300;\n  margin: 0 0 20px;\n}\n\nh3 {\n  background-color: #fff;\n  border-bottom: 1px solid #111;\n  padding: 10px;\n  display: flex;\n  justify-content: space-between;\n  font-weight: 300;\n  margin: 20px 0 0;\n}\n\n.person {\n  display: flex;\n  justify-content: space-between;\n  font-size: 20px;\n  margin-bottom: 10px;\n}\n"
  },
  {
    "path": "exchange-rate/README.md",
    "content": "## Exchange Rate\r\n\r\nSelect countries to get the exchange rate for a specific amount\r\n\r\n## Project Specifications\r\n\r\n- Display UI with 2 select lists for countries and 2 inputs for amounts\r\n- Fetch exchange rates from API (https://api.exchangerate-api.com)\r\n- Display the values for both countries\r\n- Update values on amount change\r\n- Swap country rates\r\n"
  },
  {
    "path": "exchange-rate/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <title>Exchange Rate Calculator</title>\n    <link rel=\"stylesheet\" href=\"style.css\" />\n  </head>\n  <body>\n    <img src=\"img/money.png\" alt=\"\" class=\"money-img\" />\n    <h1>Exchange Rate Calculator</h1>\n    <p>Choose the currency and the amounts to get the exchange rate</p>\n\n    <div class=\"container\">\n      <div class=\"currency\">\n        <select id=\"currency-one\">\n          <option value=\"AED\">AED</option>\n          <option value=\"ARS\">ARS</option>\n          <option value=\"AUD\">AUD</option>\n          <option value=\"BGN\">BGN</option>\n          <option value=\"BRL\">BRL</option>\n          <option value=\"BSD\">BSD</option>\n          <option value=\"CAD\">CAD</option>\n          <option value=\"CHF\">CHF</option>\n          <option value=\"CLP\">CLP</option>\n          <option value=\"CNY\">CNY</option>\n          <option value=\"COP\">COP</option>\n          <option value=\"CZK\">CZK</option>\n          <option value=\"DKK\">DKK</option>\n          <option value=\"DOP\">DOP</option>\n          <option value=\"EGP\">EGP</option>\n          <option value=\"EUR\">EUR</option>\n          <option value=\"FJD\">FJD</option>\n          <option value=\"GBP\">GBP</option>\n          <option value=\"GTQ\">GTQ</option>\n          <option value=\"HKD\">HKD</option>\n          <option value=\"HRK\">HRK</option>\n          <option value=\"HUF\">HUF</option>\n          <option value=\"IDR\">IDR</option>\n          <option value=\"ILS\">ILS</option>\n          <option value=\"INR\">INR</option>\n          <option value=\"ISK\">ISK</option>\n          <option value=\"JPY\">JPY</option>\n          <option value=\"KRW\">KRW</option>\n          <option value=\"KZT\">KZT</option>\n          <option value=\"MXN\">MXN</option>\n          <option value=\"MYR\">MYR</option>\n          <option value=\"NOK\">NOK</option>\n          <option value=\"NZD\">NZD</option>\n          <option value=\"PAB\">PAB</option>\n          <option value=\"PEN\">PEN</option>\n          <option value=\"PHP\">PHP</option>\n          <option value=\"PKR\">PKR</option>\n          <option value=\"PLN\">PLN</option>\n          <option value=\"PYG\">PYG</option>\n          <option value=\"RON\">RON</option>\n          <option value=\"RUB\">RUB</option>\n          <option value=\"SAR\">SAR</option>\n          <option value=\"SEK\">SEK</option>\n          <option value=\"SGD\">SGD</option>\n          <option value=\"THB\">THB</option>\n          <option value=\"TRY\">TRY</option>\n          <option value=\"TWD\">TWD</option>\n          <option value=\"UAH\">UAH</option>\n          <option value=\"USD\" selected>USD</option>\n          <option value=\"UYU\">UYU</option>\n          <option value=\"VND\">VND</option>\n          <option value=\"ZAR\">ZAR</option>\n        </select>\n        <input type=\"number\" id=\"amount-one\" placeholder=\"0\" value=\"1\" />\n      </div>\n\n      <div class=\"swap-rate-container\">\n        <button class=\"btn\" id=\"swap\">\n          Swap\n        </button>\n        <div class=\"rate\" id=\"rate\"></div>\n      </div>\n\n      <div class=\"currency\">\n        <select id=\"currency-two\">\n          <option value=\"AED\">AED</option>\n          <option value=\"ARS\">ARS</option>\n          <option value=\"AUD\">AUD</option>\n          <option value=\"BGN\">BGN</option>\n          <option value=\"BRL\">BRL</option>\n          <option value=\"BSD\">BSD</option>\n          <option value=\"CAD\">CAD</option>\n          <option value=\"CHF\">CHF</option>\n          <option value=\"CLP\">CLP</option>\n          <option value=\"CNY\">CNY</option>\n          <option value=\"COP\">COP</option>\n          <option value=\"CZK\">CZK</option>\n          <option value=\"DKK\">DKK</option>\n          <option value=\"DOP\">DOP</option>\n          <option value=\"EGP\">EGP</option>\n          <option value=\"EUR\" selected>EUR</option>\n          <option value=\"FJD\">FJD</option>\n          <option value=\"GBP\">GBP</option>\n          <option value=\"GTQ\">GTQ</option>\n          <option value=\"HKD\">HKD</option>\n          <option value=\"HRK\">HRK</option>\n          <option value=\"HUF\">HUF</option>\n          <option value=\"IDR\">IDR</option>\n          <option value=\"ILS\">ILS</option>\n          <option value=\"INR\">INR</option>\n          <option value=\"ISK\">ISK</option>\n          <option value=\"JPY\">JPY</option>\n          <option value=\"KRW\">KRW</option>\n          <option value=\"KZT\">KZT</option>\n          <option value=\"MXN\">MXN</option>\n          <option value=\"MYR\">MYR</option>\n          <option value=\"NOK\">NOK</option>\n          <option value=\"NZD\">NZD</option>\n          <option value=\"PAB\">PAB</option>\n          <option value=\"PEN\">PEN</option>\n          <option value=\"PHP\">PHP</option>\n          <option value=\"PKR\">PKR</option>\n          <option value=\"PLN\">PLN</option>\n          <option value=\"PYG\">PYG</option>\n          <option value=\"RON\">RON</option>\n          <option value=\"RUB\">RUB</option>\n          <option value=\"SAR\">SAR</option>\n          <option value=\"SEK\">SEK</option>\n          <option value=\"SGD\">SGD</option>\n          <option value=\"THB\">THB</option>\n          <option value=\"TRY\">TRY</option>\n          <option value=\"TWD\">TWD</option>\n          <option value=\"UAH\">UAH</option>\n          <option value=\"USD\">USD</option>\n          <option value=\"UYU\">UYU</option>\n          <option value=\"VND\">VND</option>\n          <option value=\"ZAR\">ZAR</option>\n        </select>\n        <input type=\"number\" id=\"amount-two\" placeholder=\"0\" />\n      </div>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "exchange-rate/script.js",
    "content": "const currencyEl_one = document.getElementById('currency-one');\r\nconst amountEl_one = document.getElementById('amount-one');\r\nconst currencyEl_two = document.getElementById('currency-two');\r\nconst amountEl_two = document.getElementById('amount-two');\r\nconst rateEl = document.getElementById('rate');\r\nconst swap = document.getElementById('swap');\r\n\r\nfunction calculate() {\r\n  const currency_one = currencyEl_one.value;\r\n  const currency_two = currencyEl_two.value;\r\n  fetch(\"https://open.exchangerate-api.com/v6/latest\")\r\n    .then(res => res.json())\r\n    .then(data => {\r\n      //  console.log(data);\r\n      const rate = data.rates[currency_two] / data.rates[currency_one];\r\n      rateEl.innerText = `1 ${currency_one} = ${rate} ${currency_two}`;\r\n      amountEl_two.value = (amountEl_one.value * (rate)).toFixed(2);\r\n    });\r\n}\r\n\r\n\r\n// Event Listener\r\ncurrencyEl_one.addEventListener('change', calculate);\r\namountEl_one.addEventListener('input', calculate);\r\ncurrencyEl_two.addEventListener('change', calculate);\r\namountEl_two.addEventListener('input', calculate);\r\n\r\nswap.addEventListener('click', () => {\r\n  const temp = currencyEl_one.value;\r\n  currencyEl_one.value = currencyEl_two.value;\r\n  currencyEl_two.value = temp;\r\n  calculate();\r\n});\r\n\r\n\r\ncalculate();"
  },
  {
    "path": "exchange-rate/style.css",
    "content": ":root {\n  --primary-color: #5fbaa7;\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #f4f4f4;\n  font-family: Arial, Helvetica, sans-serif;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 100vh;\n  margin: 0;\n  padding: 20px;\n}\n\nh1 {\n  color: var(--primary-color);\n}\n\np {\n  text-align: center;\n}\n\n.btn {\n  color: #fff;\n  background: var(--primary-color);\n  cursor: pointer;\n  border-radius: 5px;\n  font-size: 12px;\n  padding: 5px 12px;\n}\n\n.money-img {\n  width: 150px;\n}\n\n.currency {\n  padding: 40px 0;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.currency select {\n  padding: 10px 20px 10px 10px;\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  appearance: none;\n  border: 1px solid #dedede;\n  font-size: 16px;\n  /*  You may not need these following lines. The arrow did not show for me on MacOS/Chrome so I added it. Just remove it if you would like  */\n  background: transparent;\n  background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%20000002%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E');\n  background-position: right 10px top 50%, 0, 0;\n  background-size: 12px auto, 100%;\n  background-repeat: no-repeat;\n}\n\n.currency input {\n  border: 0;\n  background: transparent;\n  font-size: 30px;\n  text-align: right;\n}\n\n.swap-rate-container {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.rate {\n  color: var(--primary-color);\n  font-size: 14px;\n  padding: 0 10px;\n}\n\nselect:focus,\ninput:focus,\nbutton:focus {\n  outline: 0;\n}\n\n@media (max-width: 600px) {\n  .currency input {\n    width: 200px;\n  }\n}\n"
  },
  {
    "path": "expense-tracker/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Expense Tracker</title>\n  </head>\n  <body>\n    <h2>Expense Tracker</h2>\n\n    <div class=\"container\">\n      <h4>Your Balance</h4>\n      <h1 id=\"balance\">$0.00</h1>\n\n      <div class=\"inc-exp-container\">\n        <div>\n          <h4>Income</h4>\n          <p id=\"money-plus\" class=\"money plus\">+$0.00</p>\n        </div>\n        <div>\n          <h4>Expense</h4>\n          <p id=\"money-minus\" class=\"money minus\">-$0.00</p>\n        </div>\n      </div>\n\n      <h3>History</h3>\n      <ul id=\"list\" class=\"list\">\n        <!-- <li class=\"minus\">\n          Cash <span>-$400</span><button class=\"delete-btn\">x</button>\n        </li> -->\n      </ul>\n\n      <h3>Add new transaction</h3>\n      <form id=\"form\">\n        <div class=\"form-control\">\n          <label for=\"text\">Text</label>\n          <input type=\"text\" id=\"text\" placeholder=\"Enter text...\" />\n        </div>\n        <div class=\"form-control\">\n          <label for=\"amount\"\n            >Amount <br />\n            (negative - expense, positive - income)</label\n          >\n          <input type=\"number\" id=\"amount\" placeholder=\"Enter amount...\" />\n        </div>\n        <button class=\"btn\">Add transaction</button>\n      </form>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "expense-tracker/readme.md",
    "content": "## Expense Tracker\n\nKeep track of income and expenses. Add and remove items and save to local storage\n\n## Project Specifications\n\n- Create UI for project\n- Display transaction items in DOM\n- Show balance, expense and income totals\n- Add new transation and reflect in total\n- Delete items from DOM\n- Persist to local storage\n"
  },
  {
    "path": "expense-tracker/script.js",
    "content": "const balance = document.getElementById('balance');\nconst money_plus = document.getElementById('money-plus');\nconst money_minus = document.getElementById('money-minus');\nconst list = document.getElementById('list');\nconst form = document.getElementById('form');\nconst text = document.getElementById('text');\nconst amount = document.getElementById('amount');\n\n// const dummyTransactions = [\n//   { id: 1, text: 'Flower', amount: -20 },\n//   { id: 2, text: 'Salary', amount: 300 },\n//   { id: 3, text: 'Book', amount: -10 },\n//   { id: 4, text: 'Camera', amount: 150 }\n// ];\n\nconst localStorageTransactions = JSON.parse(\n  localStorage.getItem('transactions')\n);\n\nlet transactions =\n  localStorage.getItem('transactions') !== null ? localStorageTransactions : [];\n\n// Add transaction\nfunction addTransaction(e) {\n  e.preventDefault();\n\n  if (text.value.trim() === '' || amount.value.trim() === '') {\n    alert('Please add a text and amount');\n  } else {\n    const transaction = {\n      id: generateID(),\n      text: text.value,\n      amount: +amount.value\n    };\n\n    transactions.push(transaction);\n\n    addTransactionDOM(transaction);\n\n    updateValues();\n\n    updateLocalStorage();\n\n    text.value = '';\n    amount.value = '';\n  }\n}\n\n// Generate random ID\nfunction generateID() {\n  return Math.floor(Math.random() * 100000000);\n}\n\n// Add transactions to DOM list\nfunction addTransactionDOM(transaction) {\n  // Get sign\n  const sign = transaction.amount < 0 ? '-' : '+';\n\n  const item = document.createElement('li');\n\n  // Add class based on value\n  item.classList.add(transaction.amount < 0 ? 'minus' : 'plus');\n\n  item.innerHTML = `\n    ${transaction.text} <span>${sign}${Math.abs(\n    transaction.amount\n  )}</span> <button class=\"delete-btn\" onclick=\"removeTransaction(${\n    transaction.id\n  })\">x</button>\n  `;\n\n  list.appendChild(item);\n}\n\n// Update the balance, income and expense\nfunction updateValues() {\n  const amounts = transactions.map(transaction => transaction.amount);\n\n  const total = amounts.reduce((acc, item) => (acc += item), 0).toFixed(2);\n\n  const income = amounts\n    .filter(item => item > 0)\n    .reduce((acc, item) => (acc += item), 0)\n    .toFixed(2);\n\n  const expense = (\n    amounts.filter(item => item < 0).reduce((acc, item) => (acc += item), 0) *\n    -1\n  ).toFixed(2);\n\n  balance.innerText = `$${total}`;\n  money_plus.innerText = `$${income}`;\n  money_minus.innerText = `$${expense}`;\n}\n\n// Remove transaction by ID\nfunction removeTransaction(id) {\n  transactions = transactions.filter(transaction => transaction.id !== id);\n\n  updateLocalStorage();\n\n  init();\n}\n\n// Update local storage transactions\nfunction updateLocalStorage() {\n  localStorage.setItem('transactions', JSON.stringify(transactions));\n}\n\n// Init app\nfunction init() {\n  list.innerHTML = '';\n\n  transactions.forEach(addTransactionDOM);\n  updateValues();\n}\n\ninit();\n\nform.addEventListener('submit', addTransaction);\n"
  },
  {
    "path": "expense-tracker/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');\n\n:root {\n  --box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #f7f7f7;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  min-height: 100vh;\n  margin: 0;\n  font-family: 'Lato', sans-serif;\n  font-size: 18px;\n}\n\n.container {\n  margin: 30px auto;\n  width: 400px;\n}\n\nh1 {\n  letter-spacing: 1px;\n  margin: 0;\n}\n\nh3 {\n  border-bottom: 1px solid #bbb;\n  padding-bottom: 10px;\n  margin: 40px 0 10px;\n}\n\nh4 {\n  margin: 0;\n  text-transform: uppercase;\n}\n\n.inc-exp-container {\n  background-color: #fff;\n  box-shadow: var(--box-shadow);\n  padding: 20px;\n  display: flex;\n  justify-content: space-between;\n  margin: 20px 0;\n}\n\n.inc-exp-container > div {\n  flex: 1;\n  text-align: center;\n}\n\n.inc-exp-container > div:first-of-type {\n  border-right: 1px solid #dedede;\n}\n\n.money {\n  font-size: 20px;\n  letter-spacing: 1px;\n  margin: 5px 0;\n}\n\n.money.plus {\n  color: #2ecc71;\n}\n\n.money.minus {\n  color: #c0392b;\n}\n\nlabel {\n  display: inline-block;\n  margin: 10px 0;\n}\n\ninput[type='text'],\ninput[type='number'] {\n  border: 1px solid #dedede;\n  border-radius: 2px;\n  display: block;\n  font-size: 16px;\n  padding: 10px;\n  width: 100%;\n}\n\n.btn {\n  cursor: pointer;\n  background-color: #9c88ff;\n  box-shadow: var(--box-shadow);\n  color: #fff;\n  border: 0;\n  display: block;\n  font-size: 16px;\n  margin: 10px 0 30px;\n  padding: 10px;\n  width: 100%;\n}\n\n.btn:focus,\n.delete-btn:focus {\n  outline: 0;\n}\n\n.list {\n  list-style-type: none;\n  padding: 0;\n  margin-bottom: 40px;\n}\n\n.list li {\n  background-color: #fff;\n  box-shadow: var(--box-shadow);\n  color: #333;\n  display: flex;\n  justify-content: space-between;\n  position: relative;\n  padding: 10px;\n  margin: 10px 0;\n}\n\n.list li.plus {\n  border-right: 5px solid #2ecc71;\n}\n\n.list li.minus {\n  border-right: 5px solid #c0392b;\n}\n\n.delete-btn {\n  cursor: pointer;\n  background-color: #e74c3c;\n  border: 0;\n  color: #fff;\n  font-size: 20px;\n  line-height: 20px;\n  padding: 2px 5px;\n  position: absolute;\n  top: 50%;\n  left: 0;\n  transform: translate(-100%, -50%);\n  opacity: 0;\n  transition: opacity 0.3s ease;\n}\n\n.list li:hover .delete-btn {\n  opacity: 1;\n}\n"
  },
  {
    "path": "form-validator/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Form Validator</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <form id=\"form\" class=\"form\">\n        <h2>Register With Us</h2>\n        <div class=\"form-control\">\n          <label for=\"username\">Username</label>\n          <input type=\"text\" id=\"username\" placeholder=\"Enter username\" />\n          <small>Error message</small>\n        </div>\n        <div class=\"form-control\">\n          <label for=\"email\">Email</label>\n          <input type=\"text\" id=\"email\" placeholder=\"Enter email\" />\n          <small>Error message</small>\n        </div>\n        <div class=\"form-control\">\n          <label for=\"password\">Password</label>\n          <input type=\"password\" id=\"password\" placeholder=\"Enter password\" />\n          <small>Error message</small>\n        </div>\n        <div class=\"form-control\">\n          <label for=\"password2\">Confirm Password</label>\n          <input\n            type=\"password\"\n            id=\"password2\"\n            placeholder=\"Enter password again\"\n          />\n          <small>Error message</small>\n        </div>\n        <button type=\"submit\">Submit</button>\n      </form>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "form-validator/readme.md",
    "content": "## Form Validator (Intro Project)\n\nSimple client side form validation. Check required, length, email and password match\n\n## Project Specifications\n\n- Create form UI\n- Show error messages under specific inputs\n- checkRequired() to accept array of inputs\n- checkLength() to check min and max length\n- checkEmail() to validate email with regex\n- checkPasswordsMatch() to match confirm password\n"
  },
  {
    "path": "form-validator/script.js",
    "content": "const form = document.getElementById('form');\nconst username = document.getElementById('username');\nconst email = document.getElementById('email');\nconst password = document.getElementById('password');\nconst password2 = document.getElementById('password2');\n\n// Show input error message\nfunction showError(input, message) {\n  const formControl = input.parentElement;\n  formControl.className = 'form-control error';\n  const small = formControl.querySelector('small');\n  small.innerText = message;\n}\n\n// Show success outline\nfunction showSuccess(input) {\n  const formControl = input.parentElement;\n  formControl.className = 'form-control success';\n}\n\n// Check email is valid\nfunction checkEmail(input) {\n  const re = /^(([^<>()\\[\\]\\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/;\n  if (re.test(input.value.trim())) {\n    showSuccess(input);\n  } else {\n    showError(input, 'Email is not valid');\n  }\n}\n\n// Check required fields\nfunction checkRequired(inputArr) {\n  let isRequired = false;\n  inputArr.forEach(function(input) {\n    if (input.value.trim() === '') {\n      showError(input, `${getFieldName(input)} is required`);\n      isRequired = true;\n    } else {\n      showSuccess(input);\n    }\n  });\n\n  return isRequired;\n}\n\n// Check input length\nfunction checkLength(input, min, max) {\n  if (input.value.length < min) {\n    showError(\n      input,\n      `${getFieldName(input)} must be at least ${min} characters`\n    );\n  } else if (input.value.length > max) {\n    showError(\n      input,\n      `${getFieldName(input)} must be less than ${max} characters`\n    );\n  } else {\n    showSuccess(input);\n  }\n}\n\n// Check passwords match\nfunction checkPasswordsMatch(input1, input2) {\n  if (input1.value !== input2.value) {\n    showError(input2, 'Passwords do not match');\n  }\n}\n\n// Get fieldname\nfunction getFieldName(input) {\n  return input.id.charAt(0).toUpperCase() + input.id.slice(1);\n}\n\n// Event listeners\nform.addEventListener('submit', function(e) {\n  e.preventDefault();\n\n  if(checkRequired([username, email, password, password2])){\n    checkLength(username, 3, 15);\n    checkLength(password, 6, 25);\n    checkEmail(email);\n    checkPasswordsMatch(password, password2);\n  }\n\n});\n"
  },
  {
    "path": "form-validator/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');\n\n:root {\n  --success-color: #2ecc71;\n  --error-color: #e74c3c;\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #f9fafb;\n  font-family: 'Open Sans', sans-serif;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-height: 100vh;\n  margin: 0;\n}\n\n.container {\n  background-color: #fff;\n  border-radius: 5px;\n  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);\n  width: 400px;\n}\n\nh2 {\n  text-align: center;\n  margin: 0 0 20px;\n}\n\n.form {\n  padding: 30px 40px;\n}\n\n.form-control {\n  margin-bottom: 10px;\n  padding-bottom: 20px;\n  position: relative;\n}\n\n.form-control label {\n  color: #777;\n  display: block;\n  margin-bottom: 5px;\n}\n\n.form-control input {\n  border: 2px solid #f0f0f0;\n  border-radius: 4px;\n  display: block;\n  width: 100%;\n  padding: 10px;\n  font-size: 14px;\n}\n\n.form-control input:focus {\n  outline: 0;\n  border-color: #777;\n}\n\n.form-control.success input {\n  border-color: var(--success-color);\n}\n\n.form-control.error input {\n  border-color: var(--error-color);\n}\n\n.form-control small {\n  color: var(--error-color);\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  visibility: hidden;\n}\n\n.form-control.error small {\n  visibility: visible;\n}\n\n.form button {\n  cursor: pointer;\n  background-color: #3498db;\n  border: 2px solid #3498db;\n  border-radius: 4px;\n  color: #fff;\n  display: block;\n  font-size: 16px;\n  padding: 10px;\n  margin-top: 20px;\n  width: 100%;\n}\n"
  },
  {
    "path": "hangman/README.md",
    "content": "## Hangman Game\r\n\r\nSelect a letter to figure out a hidden word in a set amount of chances\r\n\r\n## Project Specifications\r\n\r\n- Display hangman pole and figure using SVG\r\n- Generate a random word\r\n- Display word in UI with correct letters\r\n- Display wrong letters\r\n- Show notification when select a letter twice\r\n- Show popup on win or lose\r\n- Play again button to reset game\r\n"
  },
  {
    "path": "hangman/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Hangman</title>\n  </head>\n  <body>\n    <h1>Hangman</h1>\n    <p>Find the hidden word - Enter a letter</p>\n    <div class=\"game-container\">\n      <svg height=\"250\" width=\"200\" class=\"figure-container\">\n        <!-- Rod -->\n        <line x1=\"60\" y1=\"20\" x2=\"140\" y2=\"20\" />\n        <line x1=\"140\" y1=\"20\" x2=\"140\" y2=\"50\" />\n        <line x1=\"60\" y1=\"20\" x2=\"60\" y2=\"230\" />\n        <line x1=\"20\" y1=\"230\" x2=\"100\" y2=\"230\" />\n\n        <!-- Head -->\n        <circle cx=\"140\" cy=\"70\" r=\"20\" class=\"figure-part\" />\n        <!-- Body -->\n        <line x1=\"140\" y1=\"90\" x2=\"140\" y2=\"150\" class=\"figure-part\" />\n        <!-- Arms -->\n        <line x1=\"140\" y1=\"120\" x2=\"120\" y2=\"100\" class=\"figure-part\" />\n        <line x1=\"140\" y1=\"120\" x2=\"160\" y2=\"100\" class=\"figure-part\" />\n        <!-- Legs -->\n        <line x1=\"140\" y1=\"150\" x2=\"120\" y2=\"180\" class=\"figure-part\" />\n        <line x1=\"140\" y1=\"150\" x2=\"160\" y2=\"180\" class=\"figure-part\" />\n      </svg>\n\n      <div class=\"wrong-letters-container\">\n        <div id=\"wrong-letters\"></div>\n      </div>\n\n      <div class=\"word\" id=\"word\"></div>\n    </div>\n\n    <!-- Container for final message -->\n    <div class=\"popup-container\" id=\"popup-container\">\n      <div class=\"popup\">\n        <h2 id=\"final-message\"></h2>\n        <h3 id=\"final-message-reveal-word\"></h3>\n        <button id=\"play-button\">Play Again</button>\n      </div>\n    </div>\n\n    <!-- Notification -->\n    <div class=\"notification-container\" id=\"notification-container\">\n      <p>You have already entered this letter</p>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "hangman/script.js",
    "content": "const wordEl = document.getElementById('word');\nconst wrongLettersEl = document.getElementById('wrong-letters');\nconst playAgainBtn = document.getElementById('play-button');\nconst popup = document.getElementById('popup-container');\nconst notification = document.getElementById('notification-container');\nconst finalMessage = document.getElementById('final-message');\nconst finalMessageRevealWord = document.getElementById('final-message-reveal-word');\n\nconst figureParts = document.querySelectorAll('.figure-part');\n\nconst words = ['application', 'programming', 'interface', 'wizard'];\n\nlet selectedWord = words[Math.floor(Math.random() * words.length)];\n\nlet playable = true;\n\nconst correctLetters = [];\nconst wrongLetters = [];\n\n// Show hidden word\nfunction displayWord() {\n\twordEl.innerHTML = `\n    ${selectedWord\n\t\t\t.split('')\n\t\t\t.map(\n\t\t\t\tletter => `\n          <span class=\"letter\">\n            ${correctLetters.includes(letter) ? letter : ''}\n          </span>\n        `\n\t\t\t)\n\t\t\t.join('')}\n  `;\n\n\tconst innerWord = wordEl.innerText.replace(/[ \\n]/g, '');\n\n\tif (innerWord === selectedWord) {\n\t\tfinalMessage.innerText = 'Congratulations! You won! 😃';\n\t\tfinalMessageRevealWord.innerText = '';\n\t\tpopup.style.display = 'flex';\n\n\t\tplayable = false;\n\t}\n}\n\n// Update the wrong letters\nfunction updateWrongLettersEl() {\n\t// Display wrong letters\n\twrongLettersEl.innerHTML = `\n    ${wrongLetters.length > 0 ? '<p>Wrong</p>' : ''}\n    ${wrongLetters.map(letter => `<span>${letter}</span>`)}\n  `;\n\n\t// Display parts\n\tfigureParts.forEach((part, index) => {\n\t\tconst errors = wrongLetters.length;\n\n\t\tif (index < errors) {\n\t\t\tpart.style.display = 'block';\n\t\t} else {\n\t\t\tpart.style.display = 'none';\n\t\t}\n\t});\n\n\t// Check if lost\n\tif (wrongLetters.length === figureParts.length) {\n\t\tfinalMessage.innerText = 'Unfortunately you lost. 😕';\n\t\tfinalMessageRevealWord.innerText = `...the word was: ${selectedWord}`;\n\t\tpopup.style.display = 'flex';\n\n\t\tplayable = false;\n\t}\n}\n\n// Show notification\nfunction showNotification() {\n\tnotification.classList.add('show');\n\n\tsetTimeout(() => {\n\t\tnotification.classList.remove('show');\n\t}, 2000);\n}\n\n// Keydown letter press\nwindow.addEventListener('keydown', e => {\n\tif (playable) {\n\t\tif (e.keyCode >= 65 && e.keyCode <= 90) {\n\t\t\tconst letter = e.key.toLowerCase();\n\n\t\t\tif (selectedWord.includes(letter)) {\n\t\t\t\tif (!correctLetters.includes(letter)) {\n\t\t\t\t\tcorrectLetters.push(letter);\n\n\t\t\t\t\tdisplayWord();\n\t\t\t\t} else {\n\t\t\t\t\tshowNotification();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (!wrongLetters.includes(letter)) {\n\t\t\t\t\twrongLetters.push(letter);\n\n\t\t\t\t\tupdateWrongLettersEl();\n\t\t\t\t} else {\n\t\t\t\t\tshowNotification();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Restart game and play again\nplayAgainBtn.addEventListener('click', () => {\n\tplayable = true;\n\n\t//  Empty arrays\n\tcorrectLetters.splice(0);\n\twrongLetters.splice(0);\n\n\tselectedWord = words[Math.floor(Math.random() * words.length)];\n\n\tdisplayWord();\n\n\tupdateWrongLettersEl();\n\n\tpopup.style.display = 'none';\n});\n\ndisplayWord();\n"
  },
  {
    "path": "hangman/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #34495e;\n  color: #fff;\n  font-family: Arial, Helvetica, sans-serif;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  height: 80vh;\n  margin: 0;\n  overflow: hidden;\n}\n\nh1 {\n  margin: 20px 0 0;\n}\n\n.game-container {\n  padding: 20px 30px;\n  position: relative;\n  margin: auto;\n  height: 350px;\n  width: 450px;\n}\n\n.figure-container {\n  fill: transparent;\n  stroke: #fff;\n  stroke-width: 4px;\n  stroke-linecap: round;\n}\n\n.figure-part {\n  display: none;\n}\n\n.wrong-letters-container {\n  position: absolute;\n  top: 20px;\n  right: 20px;\n  display: flex;\n  flex-direction: column;\n  text-align: right;\n}\n\n.wrong-letters-container p {\n  margin: 0 0 5px;\n}\n\n.wrong-letters-container span {\n  font-size: 24px;\n}\n\n.word {\n  display: flex;\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  transform: translateX(-50%);\n}\n\n.letter {\n  border-bottom: 3px solid #2980b9;\n  display: inline-flex;\n  font-size: 30px;\n  align-items: center;\n  justify-content: center;\n  margin: 0 3px;\n  height: 50px;\n  width: 20px;\n}\n\n.popup-container {\n  background-color: rgba(0, 0, 0, 0.3);\n  position: fixed;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  /* display: flex; */\n  display: none;\n  align-items: center;\n  justify-content: center;\n}\n\n.popup {\n  background: #2980b9;\n  border-radius: 5px;\n  box-shadow: 0 15px 10px 3px rgba(0, 0, 0, 0.1);\n  padding: 20px;\n  text-align: center;\n}\n\n.popup button {\n  cursor: pointer;\n  background-color: #fff;\n  color: #2980b9;\n  border: 0;\n  margin-top: 20px;\n  padding: 12px 20px;\n  font-size: 16px;\n}\n\n.popup button:active {\n  transform: scale(0.98);\n}\n\n.popup button:focus {\n  outline: 0;\n}\n\n.notification-container {\n  background-color: rgba(0, 0, 0, 0.3);\n  border-radius: 10px 10px 0 0;\n  padding: 15px 20px;\n  position: absolute;\n  bottom: -50px;\n  transition: transform 0.3s ease-in-out;\n}\n\n.notification-container p {\n  margin: 0;\n}\n\n.notification-container.show {\n  transform: translateY(-50px);\n}\n"
  },
  {
    "path": "infinite_scroll_blog/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>My Blog</title>\n  </head>\n  <body>\n    <h1>My Blog</h1>\n\n    <div class=\"filter-container\">\n      <input\n        type=\"text\"\n        id=\"filter\"\n        class=\"filter\"\n        placeholder=\"Filter posts...\"\n      />\n    </div>\n\n    <div id=\"posts-container\"></div>\n\n    <div class=\"loader\">\n      <div class=\"circle\"></div>\n      <div class=\"circle\"></div>\n      <div class=\"circle\"></div>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "infinite_scroll_blog/readme.md",
    "content": "## Infinite Scrolling & Filter\n\nDisplay blog posts from [jsonplaceholder](https://jsonplaceholder.typicode.com) and add infinite scroll to fetch posts and also add filter box\n\n## Project Specifications\n\n- Create UI & custom CSS loader animation\n- Fetch initial posts from API and display\n- Scroll down, show loader and fetch next set of posts\n- Add filtering for fetched posts\n"
  },
  {
    "path": "infinite_scroll_blog/script.js",
    "content": "const postsContainer = document.getElementById('posts-container');\nconst loading = document.querySelector('.loader');\nconst filter = document.getElementById('filter');\n\nlet limit = 5;\nlet page = 1;\n\n// Fetch posts from API\nasync function getPosts() {\n  const res = await fetch(\n    `https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}`\n  );\n\n  const data = await res.json();\n\n  return data;\n}\n\n// Show posts in DOM\nasync function showPosts() {\n  const posts = await getPosts();\n\n  posts.forEach(post => {\n    const postEl = document.createElement('div');\n    postEl.classList.add('post');\n    postEl.innerHTML = `\n      <div class=\"number\">${post.id}</div>\n      <div class=\"post-info\">\n        <h2 class=\"post-title\">${post.title}</h2>\n        <p class=\"post-body\">${post.body}</p>\n      </div>\n    `;\n\n    postsContainer.appendChild(postEl);\n  });\n}\n\n// Show loader & fetch more posts\nfunction showLoading() {\n  loading.classList.add('show');\n\n  setTimeout(() => {\n    loading.classList.remove('show');\n\n    setTimeout(() => {\n      page++;\n      showPosts();\n    }, 300);\n  }, 1000);\n}\n\n// Filter posts by input\nfunction filterPosts(e) {\n  const term = e.target.value.toUpperCase();\n  const posts = document.querySelectorAll('.post');\n\n  posts.forEach(post => {\n    const title = post.querySelector('.post-title').innerText.toUpperCase();\n    const body = post.querySelector('.post-body').innerText.toUpperCase();\n\n    if (title.indexOf(term) > -1 || body.indexOf(term) > -1) {\n      post.style.display = 'flex';\n    } else {\n      post.style.display = 'none';\n    }\n  });\n}\n\n// Show initial posts\nshowPosts();\n\nwindow.addEventListener('scroll', () => {\n  const { scrollTop, scrollHeight, clientHeight } = document.documentElement;\n\n  if (scrollHeight - scrollTop === clientHeight) {\n    showLoading();\n  }\n});\n\nfilter.addEventListener('input', filterPosts);\n"
  },
  {
    "path": "infinite_scroll_blog/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #296ca8;\n  color: #fff;\n  font-family: 'Roboto', sans-serif;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  min-height: 100vh;\n  margin: 0;\n  padding-bottom: 100px;\n}\n\nh1 {\n  margin-bottom: 0;\n  text-align: center;\n}\n\n.filter-container {\n  margin-top: 20px;\n  width: 80vw;\n  max-width: 800px;\n}\n\n.filter {\n  width: 100%;\n  padding: 12px;\n  font-size: 16px;\n}\n\n.post {\n  position: relative;\n  background-color: #4992d3;\n  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n  border-radius: 3px;\n  padding: 20px;\n  margin: 40px 0;\n  display: flex;\n  width: 80vw;\n  max-width: 800px;\n}\n\n.post .post-title {\n  margin: 0;\n}\n\n.post .post-body {\n  margin: 15px 0 0;\n  line-height: 1.3;\n}\n\n.post .post-info {\n  margin-left: 20px;\n}\n\n.post .number {\n  position: absolute;\n  top: -15px;\n  left: -15px;\n  font-size: 15px;\n  width: 40px;\n  height: 40px;\n  border-radius: 50%;\n  background: #fff;\n  color: #296ca8;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 7px 10px;\n}\n\n.loader {\n  opacity: 0;\n  display: flex;\n  position: fixed;\n  bottom: 50px;\n  transition: opacity 0.3s ease-in;\n}\n\n.loader.show {\n  opacity: 1;\n}\n\n.circle {\n  background-color: #fff;\n  width: 10px;\n  height: 10px;\n  border-radius: 50%;\n  margin: 5px;\n  animation: bounce 0.5s ease-in infinite;\n}\n\n.circle:nth-of-type(2) {\n  animation-delay: 0.1s;\n}\n\n.circle:nth-of-type(3) {\n  animation-delay: 0.2s;\n}\n\n@keyframes bounce {\n  0%,\n  100% {\n    transform: translateY(0);\n  }\n\n  50% {\n    transform: translateY(-10px);\n  }\n}\n"
  },
  {
    "path": "lyrics-search/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>LyricsSearch</title>\n  </head>\n  <body>\n    <header>\n      <h1>LyricsSearch</h1>\n\n      <form id=\"form\">\n        <input\n          type=\"text\"\n          id=\"search\"\n          placeholder=\"Enter artist or song name...\"\n        />\n        <button>Search</button>\n      </form>\n    </header>\n\n    <div id=\"result\" class=\"container\">\n      <p>Results will be displayed here</p>\n    </div>\n\n    <div id=\"more\" class=\"container centered\"></div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "lyrics-search/readme.md",
    "content": "## LyricsSearch App\n\nFind songs, artists and lyrics using the [lyrics.ovh](https://lyrics.ovh) API\n\n## Project Specifications\n\n- Display UI with song/artist input\n- Fetch songs/artists and put in DOM\n- Add pagination\n- Add get lyrics functionality and display in DOM\n"
  },
  {
    "path": "lyrics-search/script.js",
    "content": "const form = document.getElementById('form');\nconst search = document.getElementById('search');\nconst result = document.getElementById('result');\nconst more = document.getElementById('more');\n\nconst apiURL = 'https://api.lyrics.ovh';\n\n// Search by song or artist\nasync function searchSongs(term) {\n  const res = await fetch(`${apiURL}/suggest/${term}`);\n  const data = await res.json();\n\n  showData(data);\n}\n\n// Show song and artist in DOM\nfunction showData(data) {\n  result.innerHTML = `\n    <ul class=\"songs\">\n      ${data.data\n        .map(\n          song => `<li>\n      <span><strong>${song.artist.name}</strong> - ${song.title}</span>\n      <button class=\"btn\" data-artist=\"${song.artist.name}\" data-songtitle=\"${song.title}\">Get Lyrics</button>\n    </li>`\n        )\n        .join('')}\n    </ul>\n  `;\n\n  if (data.prev || data.next) {\n    more.innerHTML = `\n      ${\n        data.prev\n          ? `<button class=\"btn\" onclick=\"getMoreSongs('${data.prev}')\">Prev</button>`\n          : ''\n      }\n      ${\n        data.next\n          ? `<button class=\"btn\" onclick=\"getMoreSongs('${data.next}')\">Next</button>`\n          : ''\n      }\n    `;\n  } else {\n    more.innerHTML = '';\n  }\n}\n\n// Get prev and next songs\nasync function getMoreSongs(url) {\n  const res = await fetch(`https://cors-anywhere.herokuapp.com/${url}`);\n  const data = await res.json();\n\n  showData(data);\n}\n\n// Get lyrics for song\nasync function getLyrics(artist, songTitle) {\n  const res = await fetch(`${apiURL}/v1/${artist}/${songTitle}`);\n  const data = await res.json();\n\n   if (data.error) {\n        result.innerHTML = data.error;\n   } else {\n        const lyrics = data.lyrics.replace(/(\\r\\n|\\r|\\n)/g, '<br>');\n\n        result.innerHTML = `\n            <h2><strong>${artist}</strong> - ${songTitle}</h2>\n            <span>${lyrics}</span>\n        `;\n  }\n\n  more.innerHTML = '';\n}\n\n// Event listeners\nform.addEventListener('submit', e => {\n  e.preventDefault();\n\n  const searchTerm = search.value.trim();\n\n  if (!searchTerm) {\n    alert('Please type in a search term');\n  } else {\n    searchSongs(searchTerm);\n  }\n});\n\n// Get lyrics button click\nresult.addEventListener('click', e => {\n  const clickedEl = e.target;\n\n  if (clickedEl.tagName === 'BUTTON') {\n    const artist = clickedEl.getAttribute('data-artist');\n    const songTitle = clickedEl.getAttribute('data-songtitle');\n\n    getLyrics(artist, songTitle);\n  }\n});\n"
  },
  {
    "path": "lyrics-search/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #fff;\n  font-family: Arial, Helvetica, sans-serif;\n  margin: 0;\n}\n\nbutton {\n  cursor: pointer;\n}\n\nbutton:active {\n  transform: scale(0.95);\n}\n\ninput:focus,\nbutton:focus {\n  outline: none;\n}\n\nheader {\n  background-image: url('https://images.unsplash.com/photo-1510915361894-db8b60106cb1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80');\n  background-repeat: no-repeat;\n  background-size: cover;\n  background-position: center center;\n  color: #fff;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding: 100px 0;\n  position: relative;\n}\n\nheader::after {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  height: 100%;\n  width: 100%;\n  background-color: rgba(0, 0, 0, 0.4);\n}\n\nheader * {\n  z-index: 1;\n}\n\nheader h1 {\n  margin: 0 0 30px;\n}\n\nform {\n  position: relative;\n  width: 500px;\n  max-width: 100%;\n}\n\nform input {\n  border: 0;\n  border-radius: 50px;\n  font-size: 16px;\n  padding: 15px 30px;\n  width: 100%;\n}\n\nform button {\n  position: absolute;\n  top: 2px;\n  right: 2px;\n  background-color: #e056fd;\n  border: 0;\n  border-radius: 50px;\n  color: #fff;\n  font-size: 16px;\n  padding: 13px 30px;\n}\n\n.btn {\n  background-color: #8d56fd;\n  border: 0;\n  border-radius: 10px;\n  color: #fff;\n  padding: 4px 10px;\n}\n\nul.songs {\n  list-style-type: none;\n  padding: 0;\n}\n\nul.songs li {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  margin: 10px 0;\n}\n\n.container {\n  margin: 30px auto;\n  max-width: 100%;\n  width: 500px;\n}\n\n.container h2 {\n  font-weight: 300;\n}\n\n.container p {\n  text-align: center;\n}\n\n.centered {\n  display: flex;\n  justify-content: center;\n}\n\n.centered button {\n  transform: scale(1.3);\n  margin: 15px;\n}\n"
  },
  {
    "path": "meal-finder/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css\"\n    />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Meal Finder</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Meal Finder</h1>\n      <div class=\"flex\">\n        <form class=\"flex\" id=\"submit\">\n          <input\n            type=\"text\"\n            id=\"search\"\n            placeholder=\"Search for meals or keywords\"\n          />\n          <button class=\"search-btn\" type=\"submit\">\n            <i class=\"fas fa-search\"></i>\n          </button>\n        </form>\n        <button class=\"random-btn\" id=\"random\">\n          <i class=\"fas fa-random\"></i>\n        </button>\n      </div>\n\n      <div id=\"result-heading\"></div>\n      <div id=\"meals\" class=\"meals\"></div>\n      <div id=\"single-meal\"></div>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "meal-finder/readme.md",
    "content": "## Meal Finder App\n\nSearch and generate random meals from the [themealdb.com](https://www.themealdb.com) API\n\n## Project Specifications\n\n- Display UI with form to search and button to generate\n- Connect to API and get meals\n- Display meals in DOM with image and hover effect\n- Click on meal and see the details\n- Click on generate button and fetch & display a random meal\n"
  },
  {
    "path": "meal-finder/script.js",
    "content": "const search = document.getElementById('search'),\n  submit = document.getElementById('submit'),\n  random = document.getElementById('random'),\n  mealsEl = document.getElementById('meals'),\n  resultHeading = document.getElementById('result-heading'),\n  single_mealEl = document.getElementById('single-meal');\n\n// Search meal and fetch from API\nfunction searchMeal(e) {\n  e.preventDefault();\n\n  // Clear single meal\n  single_mealEl.innerHTML = '';\n\n  // Get search term\n  const term = search.value;\n\n  // Check for empty\n  if (term.trim()) {\n    fetch(`https://www.themealdb.com/api/json/v1/1/search.php?s=${term}`)\n      .then(res => res.json())\n      .then(data => {\n        console.log(data);\n        resultHeading.innerHTML = `<h2>Search results for '${term}':</h2>`;\n\n        if (data.meals === null) {\n          resultHeading.innerHTML = `<p>There are no search results. Try again!<p>`;\n        } else {\n          mealsEl.innerHTML = data.meals\n            .map(\n              meal => `\n            <div class=\"meal\">\n              <img src=\"${meal.strMealThumb}\" alt=\"${meal.strMeal}\" />\n              <div class=\"meal-info\" data-mealID=\"${meal.idMeal}\">\n                <h3>${meal.strMeal}</h3>\n              </div>\n            </div>\n          `\n            )\n            .join('');\n        }\n      });\n    // Clear search text\n    search.value = '';\n  } else {\n    alert('Please enter a search term');\n  }\n}\n\n// Fetch meal by ID\nfunction getMealById(mealID) {\n  fetch(`https://www.themealdb.com/api/json/v1/1/lookup.php?i=${mealID}`)\n    .then(res => res.json())\n    .then(data => {\n      const meal = data.meals[0];\n\n      addMealToDOM(meal);\n    });\n}\n\n// Fetch random meal from API\nfunction getRandomMeal() {\n  // Clear meals and heading\n  mealsEl.innerHTML = '';\n  resultHeading.innerHTML = '';\n\n  fetch(`https://www.themealdb.com/api/json/v1/1/random.php`)\n    .then(res => res.json())\n    .then(data => {\n      const meal = data.meals[0];\n\n      addMealToDOM(meal);\n    });\n}\n\n// Add meal to DOM\nfunction addMealToDOM(meal) {\n  const ingredients = [];\n\n  for (let i = 1; i <= 20; i++) {\n    if (meal[`strIngredient${i}`]) {\n      ingredients.push(\n        `${meal[`strIngredient${i}`]} - ${meal[`strMeasure${i}`]}`\n      );\n    } else {\n      break;\n    }\n  }\n\n  single_mealEl.innerHTML = `\n    <div class=\"single-meal\">\n      <h1>${meal.strMeal}</h1>\n      <img src=\"${meal.strMealThumb}\" alt=\"${meal.strMeal}\" />\n      <div class=\"single-meal-info\">\n        ${meal.strCategory ? `<p>${meal.strCategory}</p>` : ''}\n        ${meal.strArea ? `<p>${meal.strArea}</p>` : ''}\n      </div>\n      <div class=\"main\">\n        <p>${meal.strInstructions}</p>\n        <h2>Ingredients</h2>\n        <ul>\n          ${ingredients.map(ing => `<li>${ing}</li>`).join('')}\n        </ul>\n      </div>\n    </div>\n  `;\n}\n\n// Event listeners\nsubmit.addEventListener('submit', searchMeal);\nrandom.addEventListener('click', getRandomMeal);\n\nmealsEl.addEventListener('click', e => {\n  const mealInfo = e.composedPath().find(item => {\n    if (item.classList) {\n      return item.classList.contains('meal-info');\n    } else {\n      return false;\n    }\n  });\n\n  if (mealInfo) {\n    const mealID = mealInfo.getAttribute('data-mealid');\n    getMealById(mealID);\n  }\n});\n"
  },
  {
    "path": "meal-finder/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nbody {\n  background: #2d2013;\n  color: #fff;\n  font-family: Verdana, Geneva, Tahoma, sans-serif;\n  margin: 0;\n}\n\n.container {\n  margin: auto;\n  max-width: 800px;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n}\n\n.flex {\n  display: flex;\n}\n\ninput,\nbutton {\n  border: 1px solid #dedede;\n  border-top-left-radius: 4px;\n  border-bottom-left-radius: 4px;\n  font-size: 14px;\n  padding: 8px 10px;\n  margin: 0;\n}\n\ninput[type='text'] {\n  width: 300px;\n}\n\n.search-btn {\n  cursor: pointer;\n  border-left: 0;\n  border-radius: 0;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n}\n\n.random-btn {\n  cursor: pointer;\n  margin-left: 10px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n}\n\n.meals {\n  display: grid;\n  grid-template-columns: repeat(4, 1fr);\n  grid-gap: 20px;\n  margin-top: 20px;\n}\n\n.meal {\n  cursor: pointer;\n  position: relative;\n  height: 180px;\n  width: 180px;\n  text-align: center;\n}\n\n.meal img {\n  width: 100%;\n  height: 100%;\n  border: 4px #fff solid;\n  border-radius: 2px;\n}\n\n.meal-info {\n  position: absolute;\n  top: 0;\n  left: 0;\n  height: 100%;\n  width: 100%;\n  background: rgba(0, 0, 0, 0.7);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  transition: opacity 0.2s ease-in;\n  opacity: 0;\n}\n\n.meal:hover .meal-info {\n  opacity: 1;\n}\n\n.single-meal {\n  margin: 30px auto;\n  width: 70%;\n}\n\n.single-meal img {\n  width: 300px;\n  margin: 15px;\n  border: 4px #fff solid;\n  border-radius: 2px;\n}\n\n.single-meal-info {\n  margin: 20px;\n  padding: 10px;\n  border: 2px #e09850 dashed;\n  border-radius: 5px;\n}\n\n.single-meal p {\n  margin: 0;\n  letter-spacing: 0.5px;\n  line-height: 1.5;\n}\n\n.single-meal ul {\n  padding-left: 0;\n  list-style-type: none;\n}\n\n.single-meal ul li {\n  border: 1px solid #ededed;\n  border-radius: 5px;\n  background-color: #fff;\n  display: inline-block;\n  color: #2d2013;\n  font-size: 12px;\n  font-weight: bold;\n  padding: 5px;\n  margin: 0 5px 5px 0;\n}\n\n@media (max-width: 800px) {\n  .meals {\n    grid-template-columns: repeat(3, 1fr);\n  }\n}\n@media (max-width: 700px) {\n  .meals {\n    grid-template-columns: repeat(2, 1fr);\n  }\n\n  .meal {\n    height: 200px;\n    width: 200px;\n  }\n}\n@media (max-width: 500px) {\n  input[type='text'] {\n    width: 100%;\n  }\n\n  .meals {\n    grid-template-columns: 1fr;\n  }\n\n  .meal {\n    height: 300px;\n    width: 300px;\n  }\n}\n"
  },
  {
    "path": "memory-cards/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css\"\n      integrity=\"sha256-+N4/V/SbAFiW1MPBCXnfnP9QSN3+Keu+NlB+0ev/YKQ=\"\n      crossorigin=\"anonymous\"\n    />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Memory Cards</title>\n  </head>\n  <body>\n    <button id=\"clear\" class=\"clear btn\">\n      <i class=\"fas fa-trash\"></i> Clear Cards\n    </button>\n\n    <h1>\n      Memory Cards\n      <button id=\"show\" class=\"btn btn-small\">\n        <i class=\"fas fa-plus\"></i> Add New Card\n      </button>\n    </h1>\n\n    <div id=\"cards-container\" class=\"cards\">\n      <!-- <div class=\"card active\">\n        <div class=\"inner-card\">\n          <div class=\"inner-card-front\">\n            <p>\n              What is PHP?\n            </p>\n          </div>\n          <div class=\"inner-card-back\">\n            <p>\n              A programming language\n            </p>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"card\">\n        <div class=\"inner-card\">\n          <div class=\"inner-card-front\">\n            <p>\n              What is PHP?\n            </p>\n          </div>\n          <div class=\"inner-card-back\">\n            <p>\n              A programming language\n            </p>\n          </div>\n        </div>\n      </div> -->\n    </div>\n\n    <div class=\"navigation\">\n      <button id=\"prev\" class=\"nav-button\">\n        <i class=\"fas fa-arrow-left\"></i>\n      </button>\n\n      <p id=\"current\"></p>\n\n      <button id=\"next\" class=\"nav-button\">\n        <i class=\"fas fa-arrow-right\"></i>\n      </button>\n    </div>\n\n    <div id=\"add-container\" class=\"add-container\">\n      <h1>\n        Add New Card\n        <button id=\"hide\" class=\"btn btn-small btn-ghost\">\n          <i class=\"fas fa-times\"></i>\n        </button>\n      </h1>\n\n      <div class=\"form-group\">\n        <label for=\"question\">Question</label>\n        <textarea id=\"question\" placeholder=\"Enter question...\"></textarea>\n      </div>\n\n      <div class=\"form-group\">\n        <label for=\"answer\">Answer</label>\n        <textarea id=\"answer\" placeholder=\"Enter Answer...\"></textarea>\n      </div>\n\n      <button id=\"add-card\" class=\"btn\">\n        <i class=\"fas fa-plus\"></i> Add Card\n      </button>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "memory-cards/readme.md",
    "content": "## Memory Cards\n\nFlash card app for learning. Display, add and remove memory cards with questions and answers\n\n## Project Specifications\n\n- Create flip cards using CSS\n- Create \"Add new card\" overlay with form\n- Display question cards and flip for answer\n- View prev and next cards\n- Add new cards to local storage\n- Clear all cards from local storage\n"
  },
  {
    "path": "memory-cards/script.js",
    "content": "const cardsContainer = document.getElementById('cards-container');\nconst prevBtn = document.getElementById('prev');\nconst nextBtn = document.getElementById('next');\nconst currentEl = document.getElementById('current');\nconst showBtn = document.getElementById('show');\nconst hideBtn = document.getElementById('hide');\nconst questionEl = document.getElementById('question');\nconst answerEl = document.getElementById('answer');\nconst addCardBtn = document.getElementById('add-card');\nconst clearBtn = document.getElementById('clear');\nconst addContainer = document.getElementById('add-container');\n\n// Keep track of current card\nlet currentActiveCard = 0;\n\n// Store DOM cards\nconst cardsEl = [];\n\n// Store card data\nconst cardsData = getCardsData();\n\n// const cardsData = [\n//   {\n//     question: 'What must a variable begin with?',\n//     answer: 'A letter, $ or _'\n//   },\n//   {\n//     question: 'What is a variable?',\n//     answer: 'Container for a piece of data'\n//   },\n//   {\n//     question: 'Example of Case Sensitive Variable',\n//     answer: 'thisIsAVariable'\n//   }\n// ];\n\n// Create all cards\nfunction createCards() {\n  cardsData.forEach((data, index) => createCard(data, index));\n}\n\n// Create a single card in DOM\nfunction createCard(data, index) {\n  const card = document.createElement('div');\n  card.classList.add('card');\n\n  if (index === 0) {\n    card.classList.add('active');\n  }\n\n  card.innerHTML = `\n  <div class=\"inner-card\">\n  <div class=\"inner-card-front\">\n    <p>\n      ${data.question}\n    </p>\n  </div>\n  <div class=\"inner-card-back\">\n    <p>\n      ${data.answer}\n    </p>\n  </div>\n</div>\n  `;\n\n  card.addEventListener('click', () => card.classList.toggle('show-answer'));\n\n  // Add to DOM cards\n  cardsEl.push(card);\n\n  cardsContainer.appendChild(card);\n\n  updateCurrentText();\n}\n\n// Show number of cards\nfunction updateCurrentText() {\n  currentEl.innerText = `${currentActiveCard + 1}/${cardsEl.length}`;\n}\n\n// Get cards from local storage\nfunction getCardsData() {\n  const cards = JSON.parse(localStorage.getItem('cards'));\n  return cards === null ? [] : cards;\n}\n\n// Add card to local storage\nfunction setCardsData(cards) {\n  localStorage.setItem('cards', JSON.stringify(cards));\n  window.location.reload();\n}\n\ncreateCards();\n\n// Event listeners\n\n// Next button\nnextBtn.addEventListener('click', () => {\n  cardsEl[currentActiveCard].className = 'card left';\n\n  currentActiveCard = currentActiveCard + 1;\n\n  if (currentActiveCard > cardsEl.length - 1) {\n    currentActiveCard = cardsEl.length - 1;\n  }\n\n  cardsEl[currentActiveCard].className = 'card active';\n\n  updateCurrentText();\n});\n\n// Prev button\nprevBtn.addEventListener('click', () => {\n  cardsEl[currentActiveCard].className = 'card right';\n\n  currentActiveCard = currentActiveCard - 1;\n\n  if (currentActiveCard < 0) {\n    currentActiveCard = 0;\n  }\n\n  cardsEl[currentActiveCard].className = 'card active';\n\n  updateCurrentText();\n});\n\n// Show add container\nshowBtn.addEventListener('click', () => addContainer.classList.add('show'));\n// Hide add container\nhideBtn.addEventListener('click', () => addContainer.classList.remove('show'));\n\n// Add new card\naddCardBtn.addEventListener('click', () => {\n  const question = questionEl.value;\n  const answer = answerEl.value;\n\n  if (question.trim() && answer.trim()) {\n    const newCard = { question, answer };\n\n    createCard(newCard);\n\n    questionEl.value = '';\n    answerEl.value = '';\n\n    addContainer.classList.remove('show');\n\n    cardsData.push(newCard);\n    setCardsData(cardsData);\n  }\n});\n\n// Clear cards button\nclearBtn.addEventListener('click', () => {\n  localStorage.clear();\n  cardsContainer.innerHTML = '';\n  window.location.reload();\n});\n"
  },
  {
    "path": "memory-cards/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato:300,500,700&display=swap');\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #fff;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 100vh;\n  margin: 0;\n  overflow: hidden;\n  font-family: 'Lato', sans-serif;\n}\n\nh1 {\n  position: relative;\n}\n\nh1 button {\n  position: absolute;\n  right: 0;\n  transform: translate(120%, -50%);\n  z-index: 2;\n}\n\n.btn {\n  cursor: pointer;\n  background-color: #fff;\n  border: 1px solid #aaa;\n  border-radius: 3px;\n  font-size: 14px;\n  margin-top: 20px;\n  padding: 10px 15px;\n}\n\n.btn-small {\n  font-size: 12px;\n  padding: 5px 10px;\n}\n\n.btn-ghost {\n  border: 0;\n  background-color: transparent;\n}\n\n.clear {\n  position: absolute;\n  bottom: 30px;\n  left: 30px;\n}\n\n.cards {\n  perspective: 1000px;\n  position: relative;\n  height: 300px;\n  width: 500px;\n  max-width: 100%;\n}\n\n.card {\n  position: absolute;\n  opacity: 0;\n  font-size: 1.5em;\n  top: 0;\n  left: 0;\n  height: 100%;\n  width: 100%;\n  transform: translateX(50%) rotateY(-10deg);\n  transition: transform 0.4s ease, opacity 0.4s ease;\n}\n\n.card.active {\n  cursor: pointer;\n  opacity: 1;\n  z-index: 10;\n  transform: translateX(0) rotateY(0deg);\n}\n\n.card.left {\n  transform: translateX(-50%) rotateY(10deg);\n}\n\n.inner-card {\n  box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);\n  border-radius: 4px;\n  height: 100%;\n  width: 100%;\n  position: relative;\n  transform-style: preserve-3d;\n  transition: transform 0.4s ease;\n}\n\n.card.show-answer .inner-card {\n  transform: rotateX(180deg);\n}\n\n.inner-card-front,\n.inner-card-back {\n  backface-visibility: hidden;\n  position: absolute;\n  top: 0;\n  left: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 100%;\n  width: 100%;\n  background: #fff;\n}\n\n.inner-card-front {\n  transform: rotateX(0deg);\n  z-index: 2;\n}\n\n.inner-card-back {\n  transform: rotateX(180deg);\n}\n\n.inner-card-front::after,\n.inner-card-back::after {\n  content: '\\f021  Flip';\n  font-family: 'Font Awesome 5 Free', Lato, sans-serif;\n  position: absolute;\n  top: 10px;\n  right: 10px;\n  font-weight: bold;\n  font-size: 16px;\n  color: #ddd;\n}\n\n.navigation {\n  display: flex;\n  margin: 20px 0;\n}\n\n.navigation .nav-button {\n  border: none;\n  background-color: transparent;\n  cursor: pointer;\n  font-size: 16px;\n}\n\n.navigation p {\n  margin: 0 25px;\n}\n\n.add-container {\n  opacity: 0;\n  z-index: -1;\n  background-color: #f0f0f0;\n  border-top: 2px solid #eee;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding: 10px 0;\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  width: 100%;\n  transition: 0.3s ease;\n}\n\n.add-container.show {\n  opacity: 1;\n  z-index: 2;\n}\n\n.add-container h3 {\n  margin: 10px 0;\n}\n\n.form-group label {\n  display: block;\n  margin: 20px 0 10px;\n}\n\n.form-group textarea {\n  border: 1px solid #aaa;\n  border-radius: 3px;\n  font-size: 16px;\n  padding: 12px;\n  min-width: 500px;\n  max-width: 100%;\n}\n"
  },
  {
    "path": "modal-menu-slider/README.md",
    "content": "## Modal & Menu Slider\r\n\r\nSimple landing page with sliding menu and modal\r\n\r\n## Project Specifications\r\n\r\n- Create and style landing page\r\n- Style side nav and modal\r\n- Add functionality to make menu open/close on button click\r\n- Add functionality to make modal open/close on button click\r\n"
  },
  {
    "path": "modal-menu-slider/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css\"\n      integrity=\"sha256-+N4/V/SbAFiW1MPBCXnfnP9QSN3+Keu+NlB+0ev/YKQ=\"\n      crossorigin=\"anonymous\"\n    />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>My Landing Page</title>\n  </head>\n  <body>\n    <nav id=\"navbar\">\n      <div class=\"logo\">\n        <img src=\"https://randomuser.me/api/portraits/men/76.jpg\" alt=\"user\" />\n      </div>\n      <ul>\n        <li><a href=\"#\">Home</a></li>\n        <li><a href=\"#\">Portfolio</a></li>\n        <li><a href=\"#\">Blog</a></li>\n        <li><a href=\"#\">Contact Me</a></li>\n      </ul>\n    </nav>\n\n    <header>\n      <button id=\"toggle\" class=\"toggle\">\n        <i class=\"fa fa-bars fa-2x\"></i>\n      </button>\n\n      <h1>My Landing Page</h1>\n\n      <p>\n        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur, amet!\n      </p>\n\n      <button class=\"cta-btn\" id=\"open\">Sign Up</button>\n    </header>\n\n    <div class=\"container\">\n      <h2>What is this landing page about?</h2>\n      <p>\n        Lorem ipsum dolor sit amet consectetur adipisicing elit. Mollitia iure\n        accusamus ab consectetur eos provident ipsa est perferendis architecto.\n        Provident, quaerat asperiores. Quo esse minus repellat sapiente, impedit\n        obcaecati necessitatibus.\n      </p>\n      <p>\n        Lorem, ipsum dolor sit amet consectetur adipisicing elit. Sapiente optio\n        officia ipsa. Cum dignissimos possimus et non provident facilis saepe!\n      </p>\n\n      <h2>Tell Me More</h2>\n\n      <p>\n        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Repellat eaque\n        delectus explicabo qui reprehenderit? Aspernatur ad similique minima\n        accusamus maiores accusantium libero autem iusto reiciendis ullam\n        impedit esse quibusdam, deleniti laudantium rerum beatae, deserunt nemo\n        neque, obcaecati exercitationem sit. Earum.\n      </p>\n\n      <h2>Benefits</h2>\n      <ul>\n        <li>Lifetime Access</li>\n        <li>30 Day Money Back</li>\n        <li>Tailored Customer Support</li>\n      </ul>\n\n      <p>\n        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Esse quam\n        nostrum, fugiat, itaque natus officia laborum dolorum id accusantium\n        culpa architecto tenetur fuga? Consequatur provident rerum eius ratione\n        dolor officiis doloremque minima optio dignissimos doloribus odio\n        debitis vero cumque itaque excepturi a neque, expedita nulla earum\n        accusamus repellat adipisci veritatis quam. Ipsum fugiat iusto pariatur\n        consectetur quas libero dolor dolores dolorem, nostrum ducimus\n        doloremque placeat accusamus iste, culpa quaerat consequatur?\n      </p>\n    </div>\n\n    <!-- Modal -->\n    <div class=\"modal-container\" id=\"modal\">\n      <div class=\"modal\">\n        <button class=\"close-btn\" id=\"close\">\n          <i class=\"fa fa-times\"></i>\n        </button>\n        <div class=\"modal-header\">\n          <h3>Sign Up</h3>\n        </div>\n        <div class=\"modal-content\">\n          <p>Register with us to get offers, support and more</p>\n          <form class=\"modal-form\">\n            <div>\n              <label for=\"name\">Name</label>\n              <input\n                type=\"text\"\n                id=\"name\"\n                placeholder=\"Enter Name\"\n                class=\"form-input\"\n              />\n            </div>\n            <div>\n              <label for=\"email\">Email</label>\n              <input\n                type=\"email\"\n                id=\"email\"\n                placeholder=\"Enter Email\"\n                class=\"form-input\"\n              />\n            </div>\n            <div>\n              <label for=\"password\">Password</label>\n              <input\n                type=\"password\"\n                id=\"password\"\n                placeholder=\"Enter Password\"\n                class=\"form-input\"\n              />\n            </div>\n            <div>\n              <label for=\"password2\">Confirm Password</label>\n              <input\n                type=\"password\"\n                id=\"password2\"\n                placeholder=\"Confirm Password\"\n                class=\"form-input\"\n              />\n            </div>\n            <input type=\"submit\" value=\"Submit\" class=\"submit-btn\" />\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "modal-menu-slider/script.js",
    "content": "const toggle = document.getElementById('toggle');\nconst close = document.getElementById('close');\nconst open = document.getElementById('open');\nconst modal = document.getElementById('modal');\nconst navbar = document.getElementById('navbar');\n\n// This function closes navbar if user clicks anywhere outside of navbar once it's opened\n// Does not leave unused event listeners on\n// It's messy, but it works\nfunction closeNavbar(e) {\n  if (\n    document.body.classList.contains('show-nav') &&\n    e.target !== toggle &&\n    !toggle.contains(e.target) &&\n    e.target !== navbar &&\n    !navbar.contains(e.target)\n  ) {\n    document.body.classList.toggle('show-nav');\n    document.body.removeEventListener('click', closeNavbar);\n  } else if (!document.body.classList.contains('show-nav')) {\n    document.body.removeEventListener('click', closeNavbar);\n  }\n}\n\n// Toggle nav\ntoggle.addEventListener('click', () => {\n  document.body.classList.toggle('show-nav');\n  document.body.addEventListener('click', closeNavbar);\n});\n\n// Show modal\nopen.addEventListener('click', () => modal.classList.add('show-modal'));\n\n// Hide modal\nclose.addEventListener('click', () => modal.classList.remove('show-modal'));\n\n// Hide modal on outside click\nwindow.addEventListener('click', e =>\n  e.target == modal ? modal.classList.remove('show-modal') : false\n);\n"
  },
  {
    "path": "modal-menu-slider/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');\n\n:root {\n  --modal-duration: 1s;\n  --primary-color: #30336b;\n  --secondary-color: #be2edd;\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  font-family: 'Lato', sans-serif;\n  margin: 0;\n  transition: transform 0.3s ease;\n}\n\nbody.show-nav {\n  /* Width of nav */\n  transform: translateX(200px);\n}\n\nnav {\n  background-color: var(--primary-color);\n  border-right: 2px solid rgba(200, 200, 200, 0.1);\n  color: #fff;\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 200px;\n  height: 100%;\n  z-index: 100;\n  transform: translateX(-100%);\n}\n\nnav .logo {\n  padding: 30px 0;\n  text-align: center;\n}\n\nnav .logo img {\n  height: 75px;\n  width: 75px;\n  border-radius: 50%;\n}\n\nnav ul {\n  padding: 0;\n  list-style-type: none;\n  margin: 0;\n}\n\nnav ul li {\n  border-bottom: 2px solid rgba(200, 200, 200, 0.1);\n  padding: 20px;\n}\n\nnav ul li:first-of-type {\n  border-top: 2px solid rgba(200, 200, 200, 0.1);\n}\n\nnav ul li a {\n  color: #fff;\n  text-decoration: none;\n}\n\nnav ul li a:hover {\n  text-decoration: underline;\n}\n\nheader {\n  background-color: var(--primary-color);\n  color: #fff;\n  font-size: 130%;\n  position: relative;\n  padding: 40px 15px;\n  text-align: center;\n}\n\nheader h1 {\n  margin: 0;\n}\n\nheader p {\n  margin: 30px 0;\n}\n\nbutton,\ninput[type='submit'] {\n  background-color: var(--secondary-color);\n  border: 0;\n  border-radius: 5px;\n  color: #fff;\n  cursor: pointer;\n  padding: 8px 12px;\n}\n\nbutton:focus {\n  outline: none;\n}\n\n.toggle {\n  background-color: rgba(0, 0, 0, 0.3);\n  position: absolute;\n  top: 20px;\n  left: 20px;\n}\n\n.cta-btn {\n  padding: 12px 30px;\n  font-size: 20px;\n}\n\n.container {\n  padding: 15px;\n  margin: 0 auto;\n  max-width: 100%;\n  width: 800px;\n}\n\n.modal-container {\n  background-color: rgba(0, 0, 0, 0.6);\n  display: none;\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n}\n\n.modal-container.show-modal {\n  display: block;\n}\n\n.modal {\n  background-color: #fff;\n  border-radius: 5px;\n  box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);\n  position: absolute;\n  overflow: hidden;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  max-width: 100%;\n  width: 400px;\n  animation-name: modalopen;\n  animation-duration: var(--modal-duration);\n}\n\n.modal-header {\n  background: var(--primary-color);\n  color: #fff;\n  padding: 15px;\n}\n\n.modal-header h3 {\n  margin: 0;\n  border-bottom: 1px solid #333;\n}\n\n.modal-content {\n  padding: 20px;\n}\n\n.modal-form div {\n  margin: 15px 0;\n}\n\n.modal-form label {\n  display: block;\n  margin-bottom: 5px;\n}\n\n.modal-form .form-input {\n  padding: 8px;\n  width: 100%;\n}\n\n.close-btn {\n  background: transparent;\n  font-size: 25px;\n  position: absolute;\n  top: 0;\n  right: 0;\n}\n\n@keyframes modalopen {\n  from {\n    opacity: 0;\n  }\n\n  to {\n    opacity: 1;\n  }\n}\n"
  },
  {
    "path": "movie-seat-booking/README.md",
    "content": "## Movie Seat Booking\r\n\r\nDisplay movie choices and seats in a theater to select from in order to purchase tickets\r\n\r\n## Project Specifications\r\n\r\n- Display UI with movie select, screen, seats, legend & seat info\r\n- User can select a movie/price\r\n- User can select/deselect seats\r\n- User can not select occupied seats\r\n- Number of seats and price will update\r\n- Save seats, movie and price to local storage so that UI is still populated on refresh\r\n\r\nDesign inspiration from [Dribbble](https://dribbble.com/shots/3628370-Movie-Seat-Booking)\r\n"
  },
  {
    "path": "movie-seat-booking/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Movie Seat Booking</title>\n  </head>\n  <body>\n    <div class=\"movie-container\">\n      <label>Pick a movie:</label>\n      <select id=\"movie\">\n        <option value=\"10\">Avengers: Endgame ($10)</option>\n        <option value=\"12\">Joker ($12)</option>\n        <option value=\"8\">Toy Story 4 ($8)</option>\n        <option value=\"9\">The Lion King ($9)</option>\n      </select>\n    </div>\n\n    <ul class=\"showcase\">\n      <li>\n        <div class=\"seat\"></div>\n        <small>N/A</small>\n      </li>\n      <li>\n        <div class=\"seat selected\"></div>\n        <small>Selected</small>\n      </li>\n      <li>\n        <div class=\"seat occupied\"></div>\n        <small>Occupied</small>\n      </li>\n    </ul>\n\n    <div class=\"container\">\n      <div class=\"screen\"></div>\n\n      <div class=\"row\">\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n      </div>\n      <div class=\"row\">\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat occupied\"></div>\n        <div class=\"seat occupied\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n      </div>\n      <div class=\"row\">\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat occupied\"></div>\n        <div class=\"seat occupied\"></div>\n      </div>\n      <div class=\"row\">\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n      </div>\n      <div class=\"row\">\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat occupied\"></div>\n        <div class=\"seat occupied\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n      </div>\n      <div class=\"row\">\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat\"></div>\n        <div class=\"seat occupied\"></div>\n        <div class=\"seat occupied\"></div>\n        <div class=\"seat occupied\"></div>\n        <div class=\"seat\"></div>\n      </div>\n    </div>\n\n    <p class=\"text\">\n      You have selected <span id=\"count\">0</span> seats for a price of $<span\n        id=\"total\"\n        >0</span\n      >\n    </p>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "movie-seat-booking/script.js",
    "content": "const container = document.querySelector('.container');\nconst seats = document.querySelectorAll('.row .seat:not(.occupied)');\nconst count = document.getElementById('count');\nconst total = document.getElementById('total');\nconst movieSelect = document.getElementById('movie');\n\npopulateUI();\n\nlet ticketPrice = +movieSelect.value;\n\n// Save selected movie index and price\nfunction setMovieData(movieIndex, moviePrice) {\n  localStorage.setItem('selectedMovieIndex', movieIndex);\n  localStorage.setItem('selectedMoviePrice', moviePrice);\n}\n\n// Update total and count\nfunction updateSelectedCount() {\n  const selectedSeats = document.querySelectorAll('.row .seat.selected');\n\n  const seatsIndex = [...selectedSeats].map(seat => [...seats].indexOf(seat));\n\n  localStorage.setItem('selectedSeats', JSON.stringify(seatsIndex));\n\n  const selectedSeatsCount = selectedSeats.length;\n\n  count.innerText = selectedSeatsCount;\n  total.innerText = selectedSeatsCount * ticketPrice;\n  \n  setMovieData(movieSelect.selectedIndex, movieSelect.value);\n}\n\n// Get data from localstorage and populate UI\nfunction populateUI() {\n  const selectedSeats = JSON.parse(localStorage.getItem('selectedSeats'));\n\n  if (selectedSeats !== null && selectedSeats.length > 0) {\n    seats.forEach((seat, index) => {\n      if (selectedSeats.indexOf(index) > -1) {\n        seat.classList.add('selected');\n      }\n    });\n  }\n\n  const selectedMovieIndex = localStorage.getItem('selectedMovieIndex');\n\n  if (selectedMovieIndex !== null) {\n    movieSelect.selectedIndex = selectedMovieIndex;\n  }\n}\n\n// Movie select event\nmovieSelect.addEventListener('change', e => {\n  ticketPrice = +e.target.value;\n  setMovieData(e.target.selectedIndex, e.target.value);\n  updateSelectedCount();\n});\n\n// Seat click event\ncontainer.addEventListener('click', e => {\n  if (\n    e.target.classList.contains('seat') &&\n    !e.target.classList.contains('occupied')\n  ) {\n    e.target.classList.toggle('selected');\n\n    updateSelectedCount();\n  }\n});\n\n// Initial count and total set\nupdateSelectedCount();\n"
  },
  {
    "path": "movie-seat-booking/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #242333;\n  color: #fff;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 100vh;\n  font-family: 'Lato', sans-serif;\n  margin: 0;\n}\n\n.movie-container {\n  margin: 20px 0;\n}\n\n.movie-container select {\n  background-color: #fff;\n  border: 0;\n  border-radius: 5px;\n  font-size: 14px;\n  margin-left: 10px;\n  padding: 5px 15px 5px 15px;\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  appearance: none;\n}\n\n.container {\n  perspective: 1000px;\n  margin-bottom: 30px;\n}\n\n.seat {\n  background-color: #444451;\n  height: 12px;\n  width: 15px;\n  margin: 3px;\n  border-top-left-radius: 10px;\n  border-top-right-radius: 10px;\n}\n\n.seat.selected {\n  background-color: #6feaf6;\n}\n\n.seat.occupied {\n  background-color: #fff;\n}\n\n.seat:nth-of-type(2) {\n  margin-right: 18px;\n}\n\n.seat:nth-last-of-type(2) {\n  margin-left: 18px;\n}\n\n.seat:not(.occupied):hover {\n  cursor: pointer;\n  transform: scale(1.2);\n}\n\n.showcase .seat:not(.occupied):hover {\n  cursor: default;\n  transform: scale(1);\n}\n\n.showcase {\n  background: rgba(0, 0, 0, 0.1);\n  padding: 5px 10px;\n  border-radius: 5px;\n  color: #777;\n  list-style-type: none;\n  display: flex;\n  justify-content: space-between;\n}\n\n.showcase li {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin: 0 10px;\n}\n\n.showcase li small {\n  margin-left: 2px;\n}\n\n.row {\n  display: flex;\n}\n\n.screen {\n  background-color: #fff;\n  height: 70px;\n  width: 100%;\n  margin: 15px 0;\n  transform: rotateX(-45deg);\n  box-shadow: 0 3px 10px rgba(255, 255, 255, 0.7);\n}\n\np.text {\n  margin: 5px 0;\n}\n\np.text span {\n  color: #6feaf6;\n}\n"
  },
  {
    "path": "music-player/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css\"\n    />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Music Player</title>\n  </head>\n  <body>\n    <h1>Music Player</h1>\n\n    <div class=\"music-container\" id=\"music-container\">\n      <div class=\"music-info\">\n        <h4 id=\"title\"></h4>\n        <div class=\"progress-container\" id=\"progress-container\">\n          <div class=\"progress\" id=\"progress\"></div>\n        </div>\n      </div>\n\n      <audio src=\"music/ukulele.mp3\" id=\"audio\"></audio>\n\n      <div class=\"img-container\">\n        <img src=\"images/ukulele.jpg\" alt=\"music-cover\" id=\"cover\" />\n      </div>\n      <div class=\"navigation\">\n        <button id=\"prev\" class=\"action-btn\">\n          <i class=\"fas fa-backward\"></i>\n        </button>\n        <button id=\"play\" class=\"action-btn action-btn-big\">\n          <i class=\"fas fa-play\"></i>\n        </button>\n        <button id=\"next\" class=\"action-btn\">\n          <i class=\"fas fa-forward\"></i>\n        </button>\n      </div>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "music-player/readme.md",
    "content": "## Music Player\n\nCreate beautiful UI to play music stored in the \"music folder\" using the HTML5 audio API\n\n## Project Specifications\n\n- Create UI for music player including spinning image and song detail popup\n- Add play and pause functionality\n- Switch songs\n- Progress bar\n"
  },
  {
    "path": "music-player/script.js",
    "content": "const musicContainer = document.getElementById('music-container');\nconst playBtn = document.getElementById('play');\nconst prevBtn = document.getElementById('prev');\nconst nextBtn = document.getElementById('next');\n\nconst audio = document.getElementById('audio');\nconst progress = document.getElementById('progress');\nconst progressContainer = document.getElementById('progress-container');\nconst title = document.getElementById('title');\nconst cover = document.getElementById('cover');\nconst currTime = document.querySelector('#currTime');\nconst durTime = document.querySelector('#durTime');\n\n// Song titles\nconst songs = ['hey', 'summer', 'ukulele'];\n\n// Keep track of song\nlet songIndex = 2;\n\n// Initially load song details into DOM\nloadSong(songs[songIndex]);\n\n// Update song details\nfunction loadSong(song) {\n  title.innerText = song;\n  audio.src = `music/${song}.mp3`;\n  cover.src = `images/${song}.jpg`;\n}\n\n// Play song\nfunction playSong() {\n  musicContainer.classList.add('play');\n  playBtn.querySelector('i.fas').classList.remove('fa-play');\n  playBtn.querySelector('i.fas').classList.add('fa-pause');\n\n  audio.play();\n}\n\n// Pause song\nfunction pauseSong() {\n  musicContainer.classList.remove('play');\n  playBtn.querySelector('i.fas').classList.add('fa-play');\n  playBtn.querySelector('i.fas').classList.remove('fa-pause');\n\n  audio.pause();\n}\n\n// Previous song\nfunction prevSong() {\n  songIndex--;\n\n  if (songIndex < 0) {\n    songIndex = songs.length - 1;\n  }\n\n  loadSong(songs[songIndex]);\n\n  playSong();\n}\n\n// Next song\nfunction nextSong() {\n  songIndex++;\n\n  if (songIndex > songs.length - 1) {\n    songIndex = 0;\n  }\n\n  loadSong(songs[songIndex]);\n\n  playSong();\n}\n\n// Update progress bar\nfunction updateProgress(e) {\n  const { duration, currentTime } = e.srcElement;\n  const progressPercent = (currentTime / duration) * 100;\n  progress.style.width = `${progressPercent}%`;\n}\n\n// Set progress bar\nfunction setProgress(e) {\n  const width = this.clientWidth;\n  const clickX = e.offsetX;\n  const duration = audio.duration;\n\n  audio.currentTime = (clickX / width) * duration;\n}\n\n//get duration & currentTime for Time of song\nfunction DurTime (e) {\n\tconst {duration,currentTime} = e.srcElement;\n\tvar sec;\n\tvar sec_d;\n\n\t// define minutes currentTime\n\tlet min = (currentTime==null)? 0:\n\t Math.floor(currentTime/60);\n\t min = min <10 ? '0'+min:min;\n\n\t// define seconds currentTime\n\tfunction get_sec (x) {\n\t\tif(Math.floor(x) >= 60){\n\t\t\t\n\t\t\tfor (var i = 1; i<=60; i++){\n\t\t\t\tif(Math.floor(x)>=(60*i) && Math.floor(x)<(60*(i+1))) {\n\t\t\t\t\tsec = Math.floor(x) - (60*i);\n\t\t\t\t\tsec = sec <10 ? '0'+sec:sec;\n\t\t\t\t}\n\t\t\t}\n\t\t}else{\n\t\t \tsec = Math.floor(x);\n\t\t \tsec = sec <10 ? '0'+sec:sec;\n\t\t }\n\t} \n\n\tget_sec (currentTime,sec);\n\n\t// change currentTime DOM\n\tcurrTime.innerHTML = min +':'+ sec;\n\n\t// define minutes duration\n\tlet min_d = (isNaN(duration) === true)? '0':\n\t\tMath.floor(duration/60);\n\t min_d = min_d <10 ? '0'+min_d:min_d;\n\n\n\t function get_sec_d (x) {\n\t\tif(Math.floor(x) >= 60){\n\t\t\t\n\t\t\tfor (var i = 1; i<=60; i++){\n\t\t\t\tif(Math.floor(x)>=(60*i) && Math.floor(x)<(60*(i+1))) {\n\t\t\t\t\tsec_d = Math.floor(x) - (60*i);\n\t\t\t\t\tsec_d = sec_d <10 ? '0'+sec_d:sec_d;\n\t\t\t\t}\n\t\t\t}\n\t\t}else{\n\t\t \tsec_d = (isNaN(duration) === true)? '0':\n\t\t \tMath.floor(x);\n\t\t \tsec_d = sec_d <10 ? '0'+sec_d:sec_d;\n\t\t }\n\t} \n\n\t// define seconds duration\n\t\n\tget_sec_d (duration);\n\n\t// change duration DOM\n\tdurTime.innerHTML = min_d +':'+ sec_d;\n\t\t\n};\n\n// Event listeners\nplayBtn.addEventListener('click', () => {\n  const isPlaying = musicContainer.classList.contains('play');\n\n  if (isPlaying) {\n    pauseSong();\n  } else {\n    playSong();\n  }\n});\n\n// Change song\nprevBtn.addEventListener('click', prevSong);\nnextBtn.addEventListener('click', nextSong);\n\n// Time/song update\naudio.addEventListener('timeupdate', updateProgress);\n\n// Click on progress bar\nprogressContainer.addEventListener('click', setProgress);\n\n// Song ends\naudio.addEventListener('ended', nextSong);\n\n// Time of song\naudio.addEventListener('timeupdate',DurTime);\n"
  },
  {
    "path": "music-player/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-image: linear-gradient(\n    0deg,\n    rgba(247, 247, 247, 1) 23.8%,\n    rgba(252, 221, 221, 1) 92%\n  );\n  height: 100vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  font-family: 'Lato', sans-serif;\n  margin: 0;\n}\n\n.music-container {\n  background-color: #fff;\n  border-radius: 15px;\n  box-shadow: 0 20px 20px 0 rgba(252, 169, 169, 0.6);\n  display: flex;\n  padding: 20px 30px;\n  position: relative;\n  margin: 100px 0;\n  z-index: 10;\n}\n\n.img-container {\n  position: relative;\n  width: 110px;\n}\n\n.img-container::after {\n  content: '';\n  background-color: #fff;\n  border-radius: 50%;\n  position: absolute;\n  bottom: 100%;\n  left: 50%;\n  width: 20px;\n  height: 20px;\n  transform: translate(-50%, 50%);\n}\n\n.img-container img {\n  border-radius: 50%;\n  object-fit: cover;\n  height: 110px;\n  width: inherit;\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  animation: rotate 3s linear infinite;\n\n  animation-play-state: paused;\n}\n\n.music-container.play .img-container img {\n  animation-play-state: running;\n}\n\n@keyframes rotate {\n  from {\n    transform: rotate(0deg);\n  }\n\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.navigation {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  z-index: 1;\n}\n\n.action-btn {\n  background-color: #fff;\n  border: 0;\n  color: #dfdbdf;\n  font-size: 20px;\n  cursor: pointer;\n  padding: 10px;\n  margin: 0 20px;\n}\n\n.action-btn.action-btn-big {\n  color: #cdc2d0;\n  font-size: 30px;\n}\n\n.action-btn:focus {\n  outline: 0;\n}\n\n.music-info {\n  background-color: rgba(255, 255, 255, 0.5);\n  border-radius: 15px 15px 0 0;\n  position: absolute;\n  top: 0;\n  left: 20px;\n  width: calc(100% - 40px);\n  padding: 10px 10px 10px 150px;\n  opacity: 0;\n  transform: translateY(0%);\n  transition: transform 0.3s ease-in, opacity 0.3s ease-in;\n  z-index: 0;\n}\n\n.music-container.play .music-info {\n  opacity: 1;\n  transform: translateY(-100%);\n}\n\n.music-info h4 {\n  margin: 0;\n}\n\n.progress-container {\n  background: #fff;\n  border-radius: 5px;\n  cursor: pointer;\n  margin: 10px 0;\n  height: 4px;\n  width: 100%;\n}\n\n.progress {\n  background-color: #fe8daa;\n  border-radius: 5px;\n  height: 100%;\n  width: 0%;\n  transition: width 0.1s linear;\n}\n"
  },
  {
    "path": "new-year-countdown/README.md",
    "content": "## New Year Countdown\r\n\r\nLanding page that counts down from the current date to the next new year\r\n\r\n## Project Specifications\r\n\r\n- Create landing page with HTML/CSS\r\n- Calculate the days, hours, mins and seconds to the new year\r\n- Insert values into the DOM\r\n- Show a spinner right before loading the countdown\r\n- Show the coming year in the background\r\n"
  },
  {
    "path": "new-year-countdown/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>New Year Countdown</title>\n  </head>\n  <body>\n    <div id=\"year\" class=\"year\"></div>\n\n    <h1>New Year Countdown</h1>\n\n    <div id=\"countdown\" class=\"countdown\">\n      <div class=\"time\">\n        <h2 id=\"days\">00</h2>\n        <small>days</small>\n      </div>\n      <div class=\"time\">\n        <h2 id=\"hours\">00</h2>\n        <small>hours</small>\n      </div>\n      <div class=\"time\">\n        <h2 id=\"minutes\">00</h2>\n        <small>minutes</small>\n      </div>\n      <div class=\"time\">\n        <h2 id=\"seconds\">00</h2>\n        <small>seconds</small>\n      </div>\n    </div>\n\n    <img\n      src=\"./img/spinner.gif\"\n      alt=\"Loading...\"\n      id=\"loading\"\n      class=\"loading\"\n    />\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "new-year-countdown/script.js",
    "content": "const days = document.getElementById('days');\nconst hours = document.getElementById('hours');\nconst minutes = document.getElementById('minutes');\nconst seconds = document.getElementById('seconds');\nconst countdown = document.getElementById('countdown');\nconst year = document.getElementById('year');\nconst loading = document.getElementById('loading');\n\nconst currentYear = new Date().getFullYear();\n\nconst newYearTime = new Date(`January 01 ${currentYear + 1} 00:00:00`);\n\n// Set background year\nyear.innerText = currentYear + 1;\n\n// Update countdown time\nfunction updateCountdown() {\n  const currentTime = new Date();\n  const diff = newYearTime - currentTime;\n\n  const d = Math.floor(diff / 1000 / 60 / 60 / 24);\n  const h = Math.floor(diff / 1000 / 60 / 60) % 24;\n  const m = Math.floor(diff / 1000 / 60) % 60;\n  const s = Math.floor(diff / 1000) % 60;\n\n  // Add values to DOM\n  days.innerHTML = d;\n  hours.innerHTML = h < 10 ? '0' + h : h;\n  minutes.innerHTML = m < 10 ? '0' + m : m;\n  seconds.innerHTML = s < 10 ? '0' + s : s;\n}\n\n// Show spinner before countdown\nsetTimeout(() => {\n  loading.remove();\n  countdown.style.display = 'flex';\n}, 1000);\n\n// Run every second\nsetInterval(updateCountdown, 1000);\n"
  },
  {
    "path": "new-year-countdown/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-image: url('https://images.unsplash.com/photo-1467810563316-b5476525c0f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1349&q=80');\n  background-repeat: no-repeat;\n  background-size: cover;\n  background-position: center center;\n  height: 100vh;\n  color: #fff;\n  font-family: 'Lato', sans-serif;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  margin: 0;\n  overflow: hidden;\n}\n\n/* Add a dark overlay */\nbody::after {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background-color: rgba(0, 0, 0, 0.5);\n}\n\nbody * {\n  z-index: 1;\n}\n\nh1 {\n  font-size: 60px;\n  margin: -80px 0 40px;\n}\n\n.year {\n  font-size: 200px;\n  z-index: -1;\n  opacity: 0.2;\n  position: absolute;\n  bottom: 20px;\n  left: 50%;\n  transform: translateX(-50%);\n}\n\n.countdown {\n  /* display: flex; */\n  display: none;\n  transform: scale(2);\n}\n\n.time {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  margin: 15px;\n}\n\n.time h2 {\n  margin: 0 0 5px;\n}\n\n@media (max-width: 500px) {\n  h1 {\n    font-size: 45px;\n  }\n\n  .time {\n    margin: 5px;\n  }\n\n  .time h2 {\n    font-size: 12px;\n    margin: 0;\n  }\n\n  .time small {\n    font-size: 10px;\n  }\n}\n"
  },
  {
    "path": "product-filtering/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <script src=\"https://cdn.tailwindcss.com\"></script>\n    <title>Product Filtering</title>\n  </head>\n  <body class=\"bg-gray-800 text-white\">\n    <nav class=\"bg-gray-900 p-4 mb-6\">\n      <div\n        class=\"container max-w-6xl mx-auto flex flex-col sm:flex-row gap-8 items-center\"\n      >\n        <!-- Search area -->\n        <div class=\"relative w-full\">\n          <input\n            type=\"text\"\n            id=\"search\"\n            class=\"bg-gray-700 rounded-full p-2 pl-10 transition focus:w-full\"\n            placeholder=\"Search products...\"\n          />\n          <svg\n            class=\"absolute left-2 top-1/2 -translate-y-1/2\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 24 24\"\n            stroke-width=\"2\"\n            stroke=\"currentColor\"\n            fill=\"none\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          >\n            <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n            <path d=\"M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0\" />\n            <path d=\"M21 21l-6 -6\" />\n          </svg>\n        </div>\n\n        <!-- Cart button -->\n        <button id=\"cartButton\" class=\"relative\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 24 24\"\n            stroke-width=\"2\"\n            stroke=\"currentColor\"\n            fill=\"none\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n          >\n            <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n            <path\n              d=\"M6.331 8h11.339a2 2 0 0 1 1.977 2.304l-1.255 8.152a3 3 0 0 1 -2.966 2.544h-6.852a3 3 0 0 1 -2.965 -2.544l-1.255 -8.152a2 2 0 0 1 1.977 -2.304z\"\n            />\n            <path d=\"M9 11v-5a3 3 0 0 1 6 0v5\" />\n          </svg>\n          <small\n            id=\"cartCount\"\n            class=\"bg-red-500 text-xs text-white w-4 h-4 absolute -top-2 -right-2 rounded-full\"\n            >0</small\n          >\n        </button>\n      </div>\n    </nav>\n\n    <main class=\"flex flex-col md:flex-row container mx-auto max-w-6xl\">\n      <div class=\"space-y-4 p-2 w-full md:max-w-[10rem]\">\n        <h2 class=\"text-2xl\">Filters</h2>\n        <h3 class=\"text-xl mb-2\">Category</h3>\n        <div id=\"filters-container\" class=\"text-xl space-y-2\">\n          <div>\n            <input type=\"checkbox\" class=\"check\" id=\"cameras\" />\n            <label for=\"cameras\">Cameras</label>\n          </div>\n          <div>\n            <input type=\"checkbox\" class=\"check\" id=\"smartphones\" />\n            <label for=\"smartphones\">Smartphones</label>\n          </div>\n          <div>\n            <input type=\"checkbox\" class=\"check\" id=\"games\" />\n            <label for=\"games\">Games</label>\n          </div>\n          <div>\n            <input type=\"checkbox\" class=\"check\" id=\"televisions\" />\n            <label for=\"televisions\">Televisions</label>\n          </div>\n        </div>\n      </div>\n\n      <!-- Products wrapper -->\n      <div\n        id=\"products-wrapper\"\n        class=\"w-full max-w-4xl mx-auto grid grid-cols-2 sm:grid-cols-3 xl:grid-cols-4 gap-6 place-content-center p-4\"\n      ></div>\n    </main>\n\n    <script src=\"/script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "product-filtering/script.js",
    "content": "const products = [\n  {\n    name: 'Sony Playstation 5',\n    url: 'images/playstation_5.png',\n    type: 'games',\n    price: 499.99,\n  },\n  {\n    name: 'Samsung Galaxy',\n    url: 'images/samsung_galaxy.png',\n    type: 'smartphones',\n    price: 399.99,\n  },\n  {\n    name: 'Cannon EOS Camera',\n    url: 'images/cannon_eos_camera.png',\n    type: 'cameras',\n    price: 749.99,\n  },\n  {\n    name: 'Sony A7 Camera',\n    url: 'images/sony_a7_camera.png',\n    type: 'cameras',\n    price: 1999.99,\n  },\n  {\n    name: 'LG TV',\n    url: 'images/lg_tv.png',\n    type: 'televisions',\n    price: 799.99,\n  },\n  {\n    name: 'Nintendo Switch',\n    url: 'images/nintendo_switch.png',\n    type: 'games',\n    price: 299.99,\n  },\n  {\n    name: 'Xbox Series X',\n    url: 'images/xbox_series_x.png',\n    type: 'games',\n    price: 499.99,\n  },\n  {\n    name: 'Samsung TV',\n    url: 'images/samsung_tv.png',\n    type: 'televisions',\n    price: 1099.99,\n  },\n  {\n    name: 'Google Pixel',\n    url: 'images/google_pixel.png',\n    type: 'smartphones',\n    price: 499.99,\n  },\n  {\n    name: 'Sony ZV1F Camera',\n    url: 'images/sony_zv1f_camera.png',\n    type: 'cameras',\n    price: 799.99,\n  },\n  {\n    name: 'Toshiba TV',\n    url: 'images/toshiba_tv.png',\n    type: 'televisions',\n    price: 499.99,\n  },\n  {\n    name: 'iPhone 14',\n    url: 'images/iphone_14.png',\n    type: 'smartphones',\n    price: 999.99,\n  },\n];\n\n// Get DOM elements\nconst productsWrapperEl = document.getElementById('products-wrapper');\nconst checkEls = document.querySelectorAll('.check');\nconst filtersContainer = document.getElementById('filters-container');\nconst searchInput = document.getElementById('search');\nconst cartButton = document.getElementById('cartButton');\nconst cartCount = document.getElementById('cartCount');\n\n// Initialize cart item count\nlet cartItemCount = 0;\n\n// Initialize products\nconst productsEls = [];\n\n// Loop over the products and create the product elements\nproducts.forEach((product) => {\n  const productEl = createProductElement(product);\n  productsEls.push(productEl);\n  productsWrapperEl.appendChild(productEl);\n});\n\n// Add filter event listeners\nfiltersContainer.addEventListener('change', filterProducts);\nsearchInput.addEventListener('input', filterProducts);\n\n// Create product element\nfunction createProductElement(product) {\n  const productEl = document.createElement('div');\n\n  productEl.className = 'item space-y-2';\n\n  productEl.innerHTML = `<div\n  class=\"bg-gray-100 flex justify-center relative overflow-hidden group cursor-pointer border\"\n>\n  <img\n    src=\"${product.url}\"\n    alt=\"${product.name}\"\n    class=\"w-full h-full object-cover\"\n  />\n  <span\n    class=\"status bg-black text-white absolute bottom-0 left-0 right-0 text-center py-2 translate-y-full transition group-hover:translate-y-0\"\n    >Add To Cart</span\n  >\n</div>\n<p class=\"text-xl\">${product.name}</p>\n<strong>$${product.price.toLocaleString()}</strong>`;\n\n  productEl.querySelector('.status').addEventListener('click', addToCart);\n\n  return productEl;\n}\n\n// Toggle add/remove from cart\nfunction addToCart(e) {\n  const statusEl = e.target;\n\n  if (statusEl.classList.contains('added')) {\n    // Remove from cart\n    statusEl.classList.remove('added');\n    statusEl.innerText = 'Add To Cart';\n    statusEl.classList.remove('bg-red-600');\n    statusEl.classList.add('bg-gray-800');\n\n    cartItemCount--;\n  } else {\n    // Add to cart\n    statusEl.classList.add('added');\n    statusEl.innerText = 'Remove From Cart';\n    statusEl.classList.remove('bg-gray-800');\n    statusEl.classList.add('bg-red-600');\n\n    cartItemCount++;\n  }\n\n  // Update cart item count\n  cartCount.innerText = cartItemCount.toString();\n}\n\n// Filter products by search or checkbox\nfunction filterProducts() {\n  // Get search term\n  const searchTerm = searchInput.value.trim().toLowerCase();\n  // Get checked categories\n  const checkedCategories = Array.from(checkEls)\n    .filter((check) => check.checked)\n    .map((check) => check.id);\n\n  // Loop over products and check for matches\n  productsEls.forEach((productEl, index) => {\n    const product = products[index];\n\n    // Check to see if product matches the search or checked items\n    const matchesSearchTerm = product.name.toLowerCase().includes(searchTerm);\n    const isInCheckedCategory =\n      checkedCategories.length === 0 ||\n      checkedCategories.includes(product.type);\n\n    // Show or hide product based on matches\n    if (matchesSearchTerm && isInCheckedCategory) {\n      productEl.classList.remove('hidden');\n    } else {\n      productEl.classList.add('hidden');\n    }\n  });\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# 20+ Web Projects With Vanilla JavaScript\n\nThis is the main repository for all of the projects in the course.\n\n- [Course Link](https://www.traversymedia.com/20-Vanilla-JavaScript-Projects)\n- [Get Course On Udemy](https://www.udemy.com/course/web-projects-with-vanilla-javascript/?referralCode=F9B7C7FED834F91ADE75)\n\n|  #  |            Project             | Live Demo |\n| :-: | :----------------------------: | :-------: |\n| 01  |       [Form Validator](https://github.com/bradtraversy/vanillawebprojects/tree/master/form-validator)       | [Live Demo](https://vanillawebprojects.com/projects/form-validator/)  |\n| 02  |     [Movie Seat Booking](https://github.com/bradtraversy/vanillawebprojects/tree/master/movie-seat-booking)    | [Live Demo](https://vanillawebprojects.com/projects/movie-seat-booking/)  |\n| 03  |    [Custom Video Player](https://github.com/bradtraversy/vanillawebprojects/tree/master/custom-video-player)     | [Live Demo](https://vanillawebprojects.com/projects/custom-video-player/)  |\n| 04  |  [Exchange Rate Calculator](https://github.com/bradtraversy/vanillawebprojects/tree/master/exchange-rate)  | [Live Demo](https://vanillawebprojects.com/projects/exchange-rate/)  |\n| 05  | [DOM Array Methods Project](https://github.com/bradtraversy/vanillawebprojects/tree/master/dom-array-methods)  | [Live Demo](https://vanillawebprojects.com/projects/dom-array-methods/)  |\n| 06  |    [Menu Slider & Modal](https://github.com/bradtraversy/vanillawebprojects/tree/master/modal-menu-slider)    | [Live Demo](https://vanillawebprojects.com/projects/modal-menu-slider/)  |\n| 07  |        [Hangman Game](https://github.com/bradtraversy/vanillawebprojects/tree/master/hangman)       | [Live Demo](https://vanillawebprojects.com/projects/hangman/)  |\n| 08  |       [Mealfinder App](https://github.com/bradtraversy/vanillawebprojects/tree/master/meal-finder)      | [Live Demo](https://vanillawebprojects.com/projects/meal-finder/)  |\n| 09  |      [Expense Tracker](https://github.com/bradtraversy/vanillawebprojects/tree/master/expense-tracker)       | [Live Demo](https://vanillawebprojects.com/projects/expense-tracker/)  |\n| 10  |        [Music Player](https://github.com/bradtraversy/vanillawebprojects/tree/master/music-player)       | [Live Demo](https://vanillawebprojects.com/projects/music-player/)  |\n| 11  |     [Infinite Scrolling](https://github.com/bradtraversy/vanillawebprojects/tree/master/infinite_scroll_blog)     | [Live Demo](https://vanillawebprojects.com/projects/infinite_scroll_blog/)  |\n| 12  |        [Typing Game](https://github.com/bradtraversy/vanillawebprojects/tree/master/typing-game)     | [Live Demo](https://vanillawebprojects.com/projects/typing-game/)  |\n| 13  |     [Speech Text Reader](https://github.com/bradtraversy/vanillawebprojects/tree/master/speech-text-reader)    | [Live Demo](https://vanillawebprojects.com/projects/speech-text-reader/)  |\n| 14  |        [Memory Cards](https://github.com/bradtraversy/vanillawebprojects/tree/master/memory-cards)     | [Live Demo](https://vanillawebprojects.com/projects/memory-cards/)  |\n| 15  |      [LyricsSearch App](https://github.com/bradtraversy/vanillawebprojects/tree/master/lyrics-search)     | [Live Demo](https://vanillawebprojects.com/projects/lyrics-search/)  |\n| 16  |        [Relaxer App](https://github.com/bradtraversy/vanillawebprojects/tree/master/relaxer-app)       | [Live Demo](https://vanillawebprojects.com/projects//relaxer-app/)  |\n| 17  |       [Breakout Game](https://github.com/bradtraversy/vanillawebprojects/tree/master/breakout-game)       | [Live Demo](https://vanillawebprojects.com/projects/breakout-game/)  |\n| 18  |     [New Year Countdown](https://github.com/bradtraversy/vanillawebprojects/tree/master/new-year-countdown)   | [Live Demo](https://vanillawebprojects.com/projects/new-year-countdown/)  |\n| 19  | [Speak Number Guessing Game](https://github.com/bradtraversy/vanillawebprojects/tree/master/speak-number-guess) | [Live Demo](https://vanillawebprojects.com/projects/speak-number-guess/)  |\n| 20  | [Product Filtering UI](https://github.com/bradtraversy/vanillawebprojects/tree/master/product-filtering) | [Live Demo](https://vanillawebprojects.com/projects/product-filtering/)  |\n\n\nNOTE ON PULL REQUESTS: All of these projects are part of the course. While I do appreciate people trying to make some things prettier or adding new features, we are only accepting pull requests and looking at issues for bug fixes so that the code stays inline with the course\n"
  },
  {
    "path": "relaxer-app/README.md",
    "content": "## Relaxer App\r\n\r\nA relaxing breathing app with a visual director to tell you when to breathe in, hold and breathe out\r\n\r\n## Project Specifications\r\n\r\n- Create circle and gradient circle with CSS\r\n- Create and animate pointer (Small circle)\r\n- Create grow and shrink animations\r\n- Add JavaScript to create the breath animation effect\r\n"
  },
  {
    "path": "relaxer-app/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Relaxer</title>\n  </head>\n  <body>\n    <h1>Relaxer</h1>\n\n    <div class=\"container\" id=\"container\">\n      <div class=\"circle\"></div>\n\n      <p id=\"text\"></p>\n\n      <div class=\"pointer-container\">\n        <span class=\"pointer\"></span>\n      </div>\n\n      <div class=\"gradient-circle\"></div>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "relaxer-app/script.js",
    "content": "const container = document.getElementById('container');\nconst text = document.getElementById('text');\n\nconst totalTime = 7500;\nconst breatheTime = (totalTime / 5) * 2;\nconst holdTime = totalTime / 5;\n\nbreathAnimation();\n\nfunction breathAnimation() {\n  text.innerText = 'Breathe In!';\n  container.className = 'container grow';\n\n  setTimeout(() => {\n    text.innerText = 'Hold';\n\n    setTimeout(() => {\n      text.innerText = 'Breathe Out!';\n      container.className = 'container shrink';\n    }, holdTime);\n  }, breatheTime);\n}\n\nsetInterval(breathAnimation, totalTime);\n"
  },
  {
    "path": "relaxer-app/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Montserrat&display=swap');\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background: #224941 url('./img/bg.jpg') no-repeat center center/cover;\n  color: #fff;\n  font-family: 'Montserrat', sans-serif;\n  min-height: 100vh;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  margin: 0;\n}\n\n.container {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin: auto;\n  height: 300px;\n  width: 300px;\n  position: relative;\n  transform: scale(1);\n}\n\n.circle {\n  background-color: #010f1c;\n  height: 100%;\n  width: 100%;\n  border-radius: 50%;\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: -1;\n}\n\n.gradient-circle {\n  background: conic-gradient(\n    #55b7a4 0%,\n    #4ca493 40%,\n    #fff 40%,\n    #fff 60%,\n    #336d62 60%,\n    #2a5b52 100%\n  );\n  height: 320px;\n  width: 320px;\n  z-index: -2;\n  border-radius: 50%;\n  position: absolute;\n  top: -10px;\n  left: -10px;\n}\n\n.pointer {\n  background-color: #fff;\n  border-radius: 50%;\n  height: 20px;\n  width: 20px;\n  display: block;\n}\n\n.pointer-container {\n  position: absolute;\n  top: -40px;\n  left: 140px;\n  width: 20px;\n  height: 190px;\n  animation: rotate 7.5s linear forwards infinite;\n  transform-origin: bottom center;\n}\n\n@keyframes rotate {\n  from {\n    transform: rotate(0deg);\n  }\n\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.container.grow {\n  animation: grow 3s linear forwards;\n}\n\n@keyframes grow {\n  from {\n    transform: scale(1);\n  }\n\n  to {\n    transform: scale(1.2);\n  }\n}\n\n.container.shrink {\n  animation: shrink 3s linear forwards;\n}\n\n@keyframes shrink {\n  from {\n    transform: scale(1.2);\n  }\n\n  to {\n    transform: scale(1);\n  }\n}\n"
  },
  {
    "path": "sortable-list/README.md",
    "content": "## Sortable List\r\n\r\nDisplay a scrambled list that can be sorted with drag and drop\r\n\r\n## Project Specifications\r\n\r\n- Create an ordered list (Top 10 richest people)\r\n- Scramble list items randomly\r\n- Allow user to drag and drop an item to a different position\r\n- Button to check if items are in correct order\r\n- Show green for correct order and red for wrong order\r\n"
  },
  {
    "path": "sortable-list/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <title>Top 10 Richest People</title>\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <script\n      src=\"https://kit.fontawesome.com/3da1a747b2.js\"\n      crossorigin=\"anonymous\"\n    ></script>\n  </head>\n  <body>\n    <h1>10 Richest People</h1>\n    <p>Drag and drop the items into their corresponding spots</p>\n    <ul class=\"draggable-list\" id=\"draggable-list\"></ul>\n    <button class=\"check-btn\" id=\"check\">\n      Check Order\n      <i class=\"fas fa-paper-plane\"></i>\n    </button>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "sortable-list/script.js",
    "content": "const draggable_list = document.getElementById('draggable-list');\nconst check = document.getElementById('check');\n\nconst richestPeople = [\n  'Jeff Bezos',\n  'Bill Gates',\n  'Warren Buffett',\n  'Bernard Arnault',\n  'Carlos Slim Helu',\n  'Amancio Ortega',\n  'Larry Ellison',\n  'Mark Zuckerberg',\n  'Michael Bloomberg',\n  'Larry Page'\n];\n\n// Store listitems\nconst listItems = [];\n\nlet dragStartIndex;\n\ncreateList();\n\n// Insert list items into DOM\nfunction createList() {\n  [...richestPeople]\n    .map(a => ({ value: a, sort: Math.random() }))\n    .sort((a, b) => a.sort - b.sort)\n    .map(a => a.value)\n    .forEach((person, index) => {\n      const listItem = document.createElement('li');\n\n      listItem.setAttribute('data-index', index);\n\n      listItem.innerHTML = `\n        <span class=\"number\">${index + 1}</span>\n        <div class=\"draggable\" draggable=\"true\">\n          <p class=\"person-name\">${person}</p>\n          <i class=\"fas fa-grip-lines\"></i>\n        </div>\n      `;\n\n      listItems.push(listItem);\n\n      draggable_list.appendChild(listItem);\n    });\n\n  addEventListeners();\n}\n\nfunction dragStart() {\n  // console.log('Event: ', 'dragstart');\n  dragStartIndex = +this.closest('li').getAttribute('data-index');\n}\n\nfunction dragEnter() {\n  // console.log('Event: ', 'dragenter');\n  this.classList.add('over');\n}\n\nfunction dragLeave() {\n  // console.log('Event: ', 'dragleave');\n  this.classList.remove('over');\n}\n\nfunction dragOver(e) {\n  // console.log('Event: ', 'dragover');\n  e.preventDefault();\n}\n\nfunction dragDrop() {\n  // console.log('Event: ', 'drop');\n  const dragEndIndex = +this.getAttribute('data-index');\n  swapItems(dragStartIndex, dragEndIndex);\n\n  this.classList.remove('over');\n}\n\n// Swap list items that are drag and drop\nfunction swapItems(fromIndex, toIndex) {\n  const itemOne = listItems[fromIndex].querySelector('.draggable');\n  const itemTwo = listItems[toIndex].querySelector('.draggable');\n\n  listItems[fromIndex].appendChild(itemTwo);\n  listItems[toIndex].appendChild(itemOne);\n}\n\n// Check the order of list items\nfunction checkOrder() {\n  listItems.forEach((listItem, index) => {\n    const personName = listItem.querySelector('.draggable').innerText.trim();\n\n    if (personName !== richestPeople[index]) {\n      listItem.classList.add('wrong');\n    } else {\n      listItem.classList.remove('wrong');\n      listItem.classList.add('right');\n    }\n  });\n}\n\nfunction addEventListeners() {\n  const draggables = document.querySelectorAll('.draggable');\n  const dragListItems = document.querySelectorAll('.draggable-list li');\n\n  draggables.forEach(draggable => {\n    draggable.addEventListener('dragstart', dragStart);\n  });\n\n  dragListItems.forEach(item => {\n    item.addEventListener('dragover', dragOver);\n    item.addEventListener('drop', dragDrop);\n    item.addEventListener('dragenter', dragEnter);\n    item.addEventListener('dragleave', dragLeave);\n  });\n}\n\ncheck.addEventListener('click', checkOrder);\n"
  },
  {
    "path": "sortable-list/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');\n\n:root {\n  --border-color: #e3e5e4;\n  --background-color: #c3c7ca;\n  --text-color: #34444f;\n}\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #fff;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: flex-start;\n  height: 100vh;\n  margin: 0;\n  font-family: 'Lato', sans-serif;\n}\n\n.draggable-list {\n  border: 1px solid var(--border-color);\n  color: var(--text-color);\n  padding: 0;\n  list-style-type: none;\n}\n\n.draggable-list li {\n  background-color: #fff;\n  display: flex;\n  flex: 1;\n}\n\n.draggable-list li:not(:last-of-type) {\n  border-bottom: 1px solid var(--border-color);\n}\n\n.draggable-list .number {\n  background-color: var(--background-color);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 28px;\n  height: 60px;\n  width: 60px;\n}\n\n.draggable-list li.over .draggable {\n  background-color: #eaeaea;\n}\n\n.draggable-list .person-name {\n  margin: 0 20px 0 0;\n}\n\n.draggable-list li.right .person-name {\n  color: #3ae374;\n}\n\n.draggable-list li.wrong .person-name {\n  color: #ff3838;\n}\n\n.draggable {\n  cursor: pointer;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 15px;\n  flex: 1;\n}\n\n.check-btn {\n  background-color: var(--background-color);\n  border: none;\n  color: var(--text-color);\n  font-size: 16px;\n  padding: 10px 20px;\n  cursor: pointer;\n}\n\n.check-btn:active {\n  transform: scale(0.98);\n}\n\n.check-btn:focus {\n  outline: none;\n}\n"
  },
  {
    "path": "speak-number-guess/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Speak Number Guess</title>\n  </head>\n  <body>\n    <img src=\"img/mic.png\" alt=\"Speak\" />\n\n    <h1>Guess a Number Between 1 - 100</h1>\n\n    <h3>Speak the number into your microphone</h3>\n\n    <div id=\"msg\" class=\"msg\"></div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "speak-number-guess/readme.md",
    "content": "## Speak Number Guessing Game\n\nNumber guessing game where you speak your guess into the microphone using the speech recognition API\n\n## Project Specifications\n\n- Display UI directing user to speak guess\n- Implement speech recognition to listen to mic\n- Process user's guess and match\n- Let user know higher, lower, match or not a number\n"
  },
  {
    "path": "speak-number-guess/script.js",
    "content": "const msgEl = document.getElementById('msg');\n\nconst randomNum = getRandomNumber();\n\nconsole.log('Number:', randomNum);\n\nwindow.SpeechRecognition =\n  window.SpeechRecognition || window.webkitSpeechRecognition;\n\nlet recognition = new window.SpeechRecognition();\n\n// Start recognition and game\nrecognition.start();\n\n// Capture user speak\nfunction onSpeak(e) {\n  const msg = e.results[0][0].transcript;\n\n  writeMessage(msg);\n  checkNumber(msg);\n}\n\n// Write what user speaks\nfunction writeMessage(msg) {\n  msgEl.innerHTML = `\n    <div>You said: </div>\n    <span class=\"box\">${msg}</span>\n  `;\n}\n\n// Check msg against number\nfunction checkNumber(msg) {\n  const num = +msg;\n\n  // Check if valid number\n  if (Number.isNaN(num)) {\n    msgEl.innerHTML += '<div>That is not a valid number</div>';\n    return;\n  }\n\n  // Check in range\n  if (num > 100 || num < 1) {\n    msgEl.innerHTML += '<div>Number must be between 1 and 100</div>';\n    return;\n  }\n\n  // Check number\n  if (num === randomNum) {\n    document.body.innerHTML = `\n      <h2>Congrats! You have guessed the number! <br><br>\n      It was ${num}</h2>\n      <button class=\"play-again\" id=\"play-again\">Play Again</button>\n    `;\n  } else if (num > randomNum) {\n    msgEl.innerHTML += '<div>GO LOWER</div>';\n  } else {\n    msgEl.innerHTML += '<div>GO HIGHER</div>';\n  }\n}\n\n// Generate random number\nfunction getRandomNumber() {\n  return Math.floor(Math.random() * 100) + 1;\n}\n\n// Speak result\nrecognition.addEventListener('result', onSpeak);\n\n// End SR service\nrecognition.addEventListener('end', () => recognition.start());\n\ndocument.body.addEventListener('click', e => {\n  if (e.target.id == 'play-again') {\n    window.location.reload();\n  }\n});\n"
  },
  {
    "path": "speak-number-guess/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nbody {\n  background: #2f3542 url('img/bg.jpg') no-repeat left center/cover;\n  color: #fff;\n  min-height: 100vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  margin: 0;\n  font-family: Arial, Helvetica, sans-serif;\n}\n\nh1,\nh3 {\n  margin-bottom: 0;\n}\n\np {\n  line-height: 1.5;\n  margin: 0;\n}\n\n.play-again {\n  padding: 8px 15px;\n  border: 0;\n  background: #f4f4f4;\n  border-radius: 5px;\n  margin-top: 10px;\n}\n\n.msg {\n  font-size: 1.5em;\n  margin-top: 40px;\n}\n\n.box {\n  border: 1px solid #dedede;\n  display: inline-block;\n  font-size: 30px;\n  margin: 20px;\n  padding: 10px;\n}\n"
  },
  {
    "path": "speech-text-reader/README.md",
    "content": "## Speech Text Reader\r\n\r\nA text to speech app for non-verbal people. Pre-made buttons and custom text speech. This project uses the Web Speech API\r\n\r\n## Project Specifications\r\n\r\n- Create responsive UI (CSS Grid) with picture buttons\r\n- Speaks the text when button clicked\r\n- Drop down custom text to speech\r\n- Speaks the text typed in\r\n- Change voices and accents\r\n"
  },
  {
    "path": "speech-text-reader/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Speech Text Reader</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h1>Speech Text Reader</h1>\n      <button id=\"toggle\" class=\"btn btn-toggle\">\n        Toggle Text Box\n      </button>\n      <div id=\"text-box\" class=\"text-box\">\n        <div id=\"close\" class=\"close\">X</div>\n        <h3>Choose Voice</h3>\n        <select id=\"voices\"></select>\n        <textarea id=\"text\" placeholder=\"Enter text to read...\"></textarea>\n        <button class=\"btn\" id=\"read\">Read Text</button>\n      </div>\n      <main></main>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "speech-text-reader/script.js",
    "content": "const main = document.querySelector('main');\nconst voicesSelect = document.getElementById('voices');\nconst textarea = document.getElementById('text');\nconst readBtn = document.getElementById('read');\nconst toggleBtn = document.getElementById('toggle');\nconst closeBtn = document.getElementById('close');\n\nconst data = [\n  {\n    image: './img/drink.jpg',\n    text: \"I'm Thirsty\"\n  },\n  {\n    image: './img/food.jpg',\n    text: \"I'm Hungry\"\n  },\n  {\n    image: './img/tired.jpg',\n    text: \"I'm Tired\"\n  },\n  {\n    image: './img/hurt.jpg',\n    text: \"I'm Hurt\"\n  },\n  {\n    image: './img/happy.jpg',\n    text: \"I'm Happy\"\n  },\n  {\n    image: './img/angry.jpg',\n    text: \"I'm Angry\"\n  },\n  {\n    image: './img/sad.jpg',\n    text: \"I'm Sad\"\n  },\n  {\n    image: './img/scared.jpg',\n    text: \"I'm Scared\"\n  },\n  {\n    image: './img/outside.jpg',\n    text: 'I Want To Go Outside'\n  },\n  {\n    image: './img/home.jpg',\n    text: 'I Want To Go Home'\n  },\n  {\n    image: './img/school.jpg',\n    text: 'I Want To Go To School'\n  },\n  {\n    image: './img/grandma.jpg',\n    text: 'I Want To Go To Grandmas'\n  }\n];\n\ndata.forEach(createBox);\n\n// Create speech boxes\nfunction createBox(item) {\n  const box = document.createElement('div');\n\n  const { image, text } = item;\n\n  box.classList.add('box');\n\n  box.innerHTML = `\n    <img src=\"${image}\" alt=\"${text}\" />\n    <p class=\"info\">${text}</p>\n  `;\n\n  box.addEventListener('click', () => {\n    setTextMessage(text);\n    speakText();\n\n    // Add active effect\n    box.classList.add('active');\n    setTimeout(() => box.classList.remove('active'), 800);\n  });\n\n  main.appendChild(box);\n}\n\n// Init speech synth\nconst message = new SpeechSynthesisUtterance();\n\n// Store voices\nlet voices = [];\n\nfunction getVoices() {\n  voices = speechSynthesis.getVoices();\n\n  voices.forEach(voice => {\n    const option = document.createElement('option');\n\n    option.value = voice.name;\n    option.innerText = `${voice.name} ${voice.lang}`;\n\n    voicesSelect.appendChild(option);\n  });\n}\n\n// Set text\nfunction setTextMessage(text) {\n  message.text = text;\n}\n\n// Speak text\nfunction speakText() {\n  speechSynthesis.speak(message);\n}\n\n// Set voice\nfunction setVoice(e) {\n  message.voice = voices.find(voice => voice.name === e.target.value);\n}\n\n// Voices changed\nspeechSynthesis.addEventListener('voiceschanged', getVoices);\n\n// Toggle text box\ntoggleBtn.addEventListener('click', () =>\n  document.getElementById('text-box').classList.toggle('show')\n);\n\n// Close button\ncloseBtn.addEventListener('click', () =>\n  document.getElementById('text-box').classList.remove('show')\n);\n\n// Change voice\nvoicesSelect.addEventListener('change', setVoice);\n\n// Read text button\nreadBtn.addEventListener('click', () => {\n  setTextMessage(textarea.value);\n  speakText();\n});\n\ngetVoices();\n"
  },
  {
    "path": "speech-text-reader/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Lato');\n\n* {\n  box-sizing: border-box;\n}\n\nbody {\n  background: #ffefea;\n  font-family: 'Lato', sans-serif;\n  min-height: 100vh;\n  margin: 0;\n}\n\nh1 {\n  text-align: center;\n}\n\n.container {\n  margin: auto;\n  padding: 20px;\n}\n\n.btn {\n  cursor: pointer;\n  background-color: darksalmon;\n  border: 0;\n  border-radius: 4px;\n  color: #fff;\n  font-size: 16px;\n  padding: 8px;\n}\n\n.btn:active {\n  transform: scale(0.98);\n}\n\n.btn:focus,\nselect:focus {\n  outline: 0;\n}\n\n.btn-toggle {\n  display: block;\n  margin: auto;\n  margin-bottom: 20px;\n}\n\n.text-box {\n  width: 70%;\n  position: absolute;\n  top: 30%;\n  left: 50%;\n  transform: translate(-50%, -800px);\n  background-color: #333;\n  color: #fff;\n  padding: 20px;\n  border-radius: 5px;\n  transition: all 1s ease-in-out;\n}\n\n.text-box.show {\n  transform: translate(-50%, 0);\n}\n\n.text-box select {\n  background-color: darksalmon;\n  border: 0;\n  color: #fff;\n  font-size: 12px;\n  height: 30px;\n  width: 100%;\n}\n\n.text-box textarea {\n  border: 1px #dadada solid;\n  border-radius: 4px;\n  font-size: 16px;\n  padding: 8px;\n  margin: 15px 0;\n  width: 100%;\n  height: 150px;\n}\n\n.text-box .btn {\n  width: 100%;\n}\n\n.text-box .close {\n  float: right;\n  text-align: right;\n  cursor: pointer;\n}\n\nmain {\n  display: grid;\n  grid-template-columns: repeat(4, 1fr);\n  grid-gap: 10px;\n}\n\n.box {\n  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);\n  border-radius: 5px;\n  cursor: pointer;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  transition: box-shadow 0.2s ease-out;\n}\n\n.box.active {\n  box-shadow: 0 0 10px 5px darksalmon;\n}\n\n.box img {\n  width: 100%;\n  object-fit: cover;\n  height: 200px;\n}\n\n.box .info {\n  background-color: darksalmon;\n  color: #fff;\n  font-size: 18px;\n  letter-spacing: 1px;\n  text-transform: uppercase;\n  margin: 0;\n  padding: 10px;\n  text-align: center;\n  height: 100%;\n}\n\n@media (max-width: 1100px) {\n  main {\n    grid-template-columns: repeat(3, 1fr);\n  }\n}\n\n@media (max-width: 760px) {\n  main {\n    grid-template-columns: repeat(2, 1fr);\n  }\n}\n\n@media (max-width: 500px) {\n  main {\n    grid-template-columns: 1fr;\n  }\n}\n"
  },
  {
    "path": "typing-game/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css\"\n      integrity=\"sha256-+N4/V/SbAFiW1MPBCXnfnP9QSN3+Keu+NlB+0ev/YKQ=\"\n      crossorigin=\"anonymous\"\n    />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n    <title>Speed Typer</title>\n  </head>\n  <body>\n    <button id=\"settings-btn\" class=\"settings-btn\">\n      <i class=\"fas fa-cog\"></i>\n    </button>\n\n    <div id=\"settings\" class=\"settings\">\n      <form id=\"settings-form\">\n        <div>\n          <label for=\"difficulty\">Difficulty</label>\n          <select id=\"difficulty\">\n            <option value=\"easy\">Easy</option>\n            <option value=\"medium\">Medium</option>\n            <option value=\"hard\">Hard</option>\n          </select>\n        </div>\n      </form>\n    </div>\n\n    <div class=\"container\">\n      <h2>👩‍💻 Speed Typer 👨‍💻</h2>\n      <small>Type the following:</small>\n\n      <h1 id=\"word\"></h1>\n\n      <input\n        type=\"text\"\n        id=\"text\"\n        autocomplete=\"off\"\n        placeholder=\"Type the word here...\"\n        autofocus\n      />\n\n      <p class=\"time-container\">Time left: <span id=\"time\">10s</span></p>\n\n      <p class=\"score-container\">Score: <span id=\"score\">0</span></p>\n\n      <div id=\"end-game-container\" class=\"end-game-container\"></div>\n    </div>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "typing-game/readme.md",
    "content": "## Speed Typer Typing Game\n\nGame to beat the clock by typing random words\n\n## Project Specifications\n\n- Create game UI including a difficuly setting\n- Generate random word and place in DOM\n- Score increase after word is typed\n- Implement timer\n- Add certain amount of time after word is typed based on difficulty\n- Store difficulty setting in local storage\n"
  },
  {
    "path": "typing-game/script.js",
    "content": "const word = document.getElementById('word');\nconst text = document.getElementById('text');\nconst scoreEl = document.getElementById('score');\nconst timeEl = document.getElementById('time');\nconst endgameEl = document.getElementById('end-game-container');\nconst settingsBtn = document.getElementById('settings-btn');\nconst settings = document.getElementById('settings');\nconst settingsForm = document.getElementById('settings-form');\nconst difficultySelect = document.getElementById('difficulty');\n\n// List of words for game\nconst words = [\n  'sigh',\n  'tense',\n  'airplane',\n  'ball',\n  'pies',\n  'juice',\n  'warlike',\n  'bad',\n  'north',\n  'dependent',\n  'steer',\n  'silver',\n  'highfalutin',\n  'superficial',\n  'quince',\n  'eight',\n  'feeble',\n  'admit',\n  'drag',\n  'loving'\n];\n\n// Init word\nlet randomWord;\n\n// Init score\nlet score = 0;\n\n// Init time\nlet time = 10;\n\n// Set difficulty to value in ls or medium\nlet difficulty =\n  localStorage.getItem('difficulty') !== null\n    ? localStorage.getItem('difficulty')\n    : 'medium';\n\n// Set difficulty select value\ndifficultySelect.value =\n  localStorage.getItem('difficulty') !== null\n    ? localStorage.getItem('difficulty')\n    : 'medium';\n\n// Focus on text on start\ntext.focus();\n\n// Start counting down\nconst timeInterval = setInterval(updateTime, 1000);\n\n// Generate random word from array\nfunction getRandomWord() {\n  return words[Math.floor(Math.random() * words.length)];\n}\n\n// Add word to DOM\nfunction addWordToDOM() {\n  randomWord = getRandomWord();\n  word.innerHTML = randomWord;\n}\n\n// Update score\nfunction updateScore() {\n  score++;\n  scoreEl.innerHTML = score;\n}\n\n// Update time\nfunction updateTime() {\n  time--;\n  timeEl.innerHTML = time + 's';\n\n  if (time === 0) {\n    clearInterval(timeInterval);\n    // end game\n    gameOver();\n  }\n}\n\n// Game over, show end screen\nfunction gameOver() {\n  endgameEl.innerHTML = `\n    <h1>Time ran out</h1>\n    <p>Your final score is ${score}</p>\n    <button onclick=\"location.reload()\">Reload</button>\n  `;\n\n  endgameEl.style.display = 'flex';\n}\n\naddWordToDOM();\n\n// Event listeners\n\n// Typing\ntext.addEventListener('input', e => {\n  const insertedText = e.target.value;\n\n  if (insertedText === randomWord) {\n    addWordToDOM();\n    updateScore();\n\n    // Clear\n    e.target.value = '';\n\n    if (difficulty === 'hard') {\n      time += 2;\n    } else if (difficulty === 'medium') {\n      time += 3;\n    } else {\n      time += 5;\n    }\n\n    updateTime();\n  }\n});\n\n// Settings btn click\nsettingsBtn.addEventListener('click', () => settings.classList.toggle('hide'));\n\n// Settings select\nsettingsForm.addEventListener('change', e => {\n  difficulty = e.target.value;\n  localStorage.setItem('difficulty', difficulty);\n});\n"
  },
  {
    "path": "typing-game/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nbody {\n  background-color: #2c3e50;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-height: 100vh;\n  margin: 0;\n  font-family: Verdana, Geneva, Tahoma, sans-serif;\n}\n\nbutton {\n  cursor: pointer;\n  font-size: 14px;\n  border-radius: 4px;\n  padding: 5px 15px;\n}\n\nselect {\n  width: 200px;\n  padding: 5px;\n  appearance: none;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  border-radius: 0;\n  background-color: #a7c5e3;\n}\n\nselect:focus,\nbutton:focus {\n  outline: 0;\n}\n\n.settings-btn {\n  position: absolute;\n  bottom: 30px;\n  left: 30px;\n}\n\n.settings {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  background-color: rgba(0, 0, 0, 0.3);\n  height: 70px;\n  color: #fff;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  transform: translateY(0);\n  transition: transform 0.3s ease-in-out;\n}\n\n.settings.hide {\n  transform: translateY(-100%);\n}\n\n.container {\n  background-color: #34495e;\n  padding: 20px;\n  border-radius: 4px;\n  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.3);\n  color: #fff;\n  position: relative;\n  text-align: center;\n  width: 500px;\n}\n\nh2 {\n  background-color: rgba(0, 0, 0, 0.3);\n  padding: 8px;\n  border-radius: 4px;\n  margin: 0 0 40px;\n}\n\nh1 {\n  margin: 0;\n}\n\ninput {\n  border: 0;\n  border-radius: 4px;\n  font-size: 14px;\n  width: 300px;\n  padding: 12px 20px;\n  margin-top: 10px;\n}\n\n.score-container {\n  position: absolute;\n  top: 60px;\n  right: 20px;\n}\n\n.time-container {\n  position: absolute;\n  top: 60px;\n  left: 20px;\n}\n\n.end-game-container {\n  background-color: inherit;\n  display: none;\n  align-items: center;\n  justify-content: center;\n  flex-direction: column;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 1;\n}\n"
  }
]