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