├── 01-quiz-game
├── index.html
├── script.js
└── style.css
├── 02-color-palette-generator
├── index.html
├── script.js
└── style.css
├── 03-drag-and-drop
├── index.html
├── script.js
└── style.css
├── 04-expense-tracker
├── index.html
├── script.js
└── style.css
├── 05-bookmark-saver
├── index.html
├── script.js
└── style.css
├── 06-form-validator
├── index.html
├── script.js
└── style.css
├── 07-password-generator
├── index.html
├── script.js
└── style.css
├── 08-todo-app
├── index.html
├── script.js
└── style.css
├── 09-contact-form
├── index.html
└── style.css
├── 10-pricing-cards
├── index.html
└── style.css
├── 11-team-members-showcase
├── index.html
└── style.css
├── 12-recipe-finder
├── index.html
├── script.js
└── style.css
├── 13-currency-converter
├── index.html
├── script.js
└── style.css
├── 14-github-finder
├── index.html
├── script.js
└── style.css
├── 15-404-page
├── 404.png
├── index.html
└── style.css
└── 16-newsletter-signup
├── index.html
└── style.css
/01-quiz-game/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quiz Game
7 |
8 |
9 |
10 |
11 |
12 |
Quiz Time!
13 |
Test your knowledge with these fun questions
14 |
Start Quiz
15 |
16 |
17 |
18 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
Quiz Results
40 |
41 |
You scored 0 out of 5
42 |
43 |
Good job!
44 |
45 |
Restart Quiz
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/01-quiz-game/script.js:
--------------------------------------------------------------------------------
1 | // DOM Elements
2 | const startScreen = document.getElementById("start-screen");
3 | const quizScreen = document.getElementById("quiz-screen");
4 | const resultScreen = document.getElementById("result-screen");
5 | const startButton = document.getElementById("start-btn");
6 | const questionText = document.getElementById("question-text");
7 | const answersContainer = document.getElementById("answers-container");
8 | const currentQuestionSpan = document.getElementById("current-question");
9 | const totalQuestionsSpan = document.getElementById("total-questions");
10 | const scoreSpan = document.getElementById("score");
11 | const finalScoreSpan = document.getElementById("final-score");
12 | const maxScoreSpan = document.getElementById("max-score");
13 | const resultMessage = document.getElementById("result-message");
14 | const restartButton = document.getElementById("restart-btn");
15 | const progressBar = document.getElementById("progress");
16 |
17 | const quizQuestions = [
18 | {
19 | question: "What is the capital of France?",
20 | answers: [
21 | { text: "London", correct: false },
22 | { text: "Berlin", correct: false },
23 | { text: "Paris", correct: true },
24 | { text: "Madrid", correct: false },
25 | ],
26 | },
27 | {
28 | question: "Which planet is known as the Red Planet?",
29 | answers: [
30 | { text: "Venus", correct: false },
31 | { text: "Mars", correct: true },
32 | { text: "Jupiter", correct: false },
33 | { text: "Saturn", correct: false },
34 | ],
35 | },
36 | {
37 | question: "What is the largest ocean on Earth?",
38 | answers: [
39 | { text: "Atlantic Ocean", correct: false },
40 | { text: "Indian Ocean", correct: false },
41 | { text: "Arctic Ocean", correct: false },
42 | { text: "Pacific Ocean", correct: true },
43 | ],
44 | },
45 | {
46 | question: "Which of these is NOT a programming language?",
47 | answers: [
48 | { text: "Java", correct: false },
49 | { text: "Python", correct: false },
50 | { text: "Banana", correct: true },
51 | { text: "JavaScript", correct: false },
52 | ],
53 | },
54 | {
55 | question: "What is the chemical symbol for gold?",
56 | answers: [
57 | { text: "Go", correct: false },
58 | { text: "Gd", correct: false },
59 | { text: "Au", correct: true },
60 | { text: "Ag", correct: false },
61 | ],
62 | },
63 | ];
64 |
65 | // QUIZ STATE VARS
66 | let currentQuestionIndex = 0;
67 | let score = 0;
68 | let answersDisabled = false;
69 |
70 | totalQuestionsSpan.textContent = quizQuestions.length;
71 | maxScoreSpan.textContent = quizQuestions.length;
72 |
73 | // event listeners
74 | startButton.addEventListener("click", startQuiz);
75 | restartButton.addEventListener("click", restartQuiz);
76 |
77 | function startQuiz() {
78 | // reset vars
79 | currentQuestionIndex = 0;
80 | score = 0;
81 | scoreSpan.textContent = 0;
82 |
83 | startScreen.classList.remove("active");
84 | quizScreen.classList.add("active");
85 |
86 | showQuestion();
87 | }
88 |
89 | function showQuestion() {
90 | // reset state
91 | answersDisabled = false;
92 |
93 | const currentQuestion = quizQuestions[currentQuestionIndex];
94 |
95 | currentQuestionSpan.textContent = currentQuestionIndex + 1;
96 |
97 | const progressPercent = (currentQuestionIndex / quizQuestions.length) * 100;
98 | progressBar.style.width = progressPercent + "%";
99 |
100 | questionText.textContent = currentQuestion.question;
101 |
102 | answersContainer.innerHTML = "";
103 |
104 | currentQuestion.answers.forEach((answer) => {
105 | const button = document.createElement("button");
106 | button.textContent = answer.text;
107 | button.classList.add("answer-btn");
108 |
109 | // what is dataset? it's a property of the button element that allows you to store custom data
110 | button.dataset.correct = answer.correct;
111 |
112 | button.addEventListener("click", selectAnswer);
113 |
114 | answersContainer.appendChild(button);
115 | });
116 | }
117 |
118 | function selectAnswer(event) {
119 | // optimization check
120 | if (answersDisabled) return;
121 |
122 | answersDisabled = true;
123 |
124 | const selectedButton = event.target;
125 | const isCorrect = selectedButton.dataset.correct === "true";
126 |
127 | // Here Array.from() is used to convert the NodeList returned by answersContainer.children into an array, this is because the NodeList is not an array and we need to use the forEach method
128 | Array.from(answersContainer.children).forEach((button) => {
129 | if (button.dataset.correct === "true") {
130 | button.classList.add("correct");
131 | } else if (button === selectedButton) {
132 | button.classList.add("incorrect");
133 | }
134 | });
135 |
136 | if (isCorrect) {
137 | score++;
138 | scoreSpan.textContent = score;
139 | }
140 |
141 | setTimeout(() => {
142 | currentQuestionIndex++;
143 |
144 | // check if there are more questions or if the quiz is over
145 | if (currentQuestionIndex < quizQuestions.length) {
146 | showQuestion();
147 | } else {
148 | showResults();
149 | }
150 | }, 1000);
151 | }
152 |
153 | function showResults() {
154 | quizScreen.classList.remove("active");
155 | resultScreen.classList.add("active");
156 |
157 | finalScoreSpan.textContent = score;
158 |
159 | const percentage = (score / quizQuestions.length) * 100;
160 |
161 | if (percentage === 100) {
162 | resultMessage.textContent = "Perfect! You're a genius!";
163 | } else if (percentage >= 80) {
164 | resultMessage.textContent = "Great job! You know your stuff!";
165 | } else if (percentage >= 60) {
166 | resultMessage.textContent = "Good effort! Keep learning!";
167 | } else if (percentage >= 40) {
168 | resultMessage.textContent = "Not bad! Try again to improve!";
169 | } else {
170 | resultMessage.textContent = "Keep studying! You'll get better!";
171 | }
172 | }
173 |
174 | function restartQuiz() {
175 | resultScreen.classList.remove("active");
176 |
177 | startQuiz();
178 | }
179 |
--------------------------------------------------------------------------------
/01-quiz-game/style.css:
--------------------------------------------------------------------------------
1 | /* Basic Reset */
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | background: #f5efe6;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | min-height: 100vh;
15 | padding: 1rem;
16 | font-family: sans-serif;
17 | }
18 |
19 | .container {
20 | background-color: white;
21 | border-radius: 1rem;
22 | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
23 | width: 100%;
24 | max-width: 600px;
25 | overflow: hidden;
26 | position: relative;
27 | }
28 |
29 | /* SCREENS */
30 | .screen {
31 | display: none;
32 | padding: 2rem;
33 | text-align: center;
34 | }
35 |
36 | .screen.active {
37 | display: block;
38 | }
39 |
40 | /* START SCREEN */
41 |
42 | #start-screen h1 {
43 | color: #e86a33;
44 | margin-bottom: 20px;
45 | font-size: 2.5rem;
46 | }
47 |
48 | #start-screen p {
49 | color: #666;
50 | margin-bottom: 30px;
51 | font-size: 1.1rem;
52 | }
53 |
54 | .quiz-header {
55 | margin-bottom: 1rem;
56 | }
57 |
58 | #question-text {
59 | color: #333;
60 | font-size: 1.5rem;
61 | margin-bottom: 1rem;
62 | line-height: 1.4;
63 | }
64 |
65 | .quiz-info {
66 | display: flex;
67 | justify-content: space-between;
68 | color: #666;
69 | margin-bottom: 10px;
70 | }
71 |
72 | .answers-container {
73 | display: flex;
74 | flex-direction: column;
75 | gap: 10px;
76 | margin-bottom: 25px;
77 | }
78 |
79 | .answer-btn {
80 | background-color: #f8f0e5;
81 | color: #333;
82 | border: 2px solid #eadbc8;
83 | border-radius: 10px;
84 | padding: 1rem;
85 | cursor: pointer;
86 | text-align: left;
87 | transition: all 0.3s ease;
88 | }
89 |
90 | .answer-btn:hover {
91 | background-color: #eadbc8;
92 | border-color: #dac0ae;
93 | }
94 |
95 | .answer-btn.correct {
96 | background-color: #e6fff0;
97 | border-color: #a3f0c4;
98 | color: #28a745;
99 | }
100 |
101 | .answer-btn.incorrect {
102 | background-color: #fff0f0;
103 | border-color: #ffbdbd;
104 | color: #dc3545;
105 | }
106 |
107 | .progress-bar {
108 | height: 10px;
109 | background-color: #f8f0e5;
110 | border-radius: 5px;
111 | overflow: hidden;
112 | margin-top: 20px;
113 | }
114 |
115 | .progress {
116 | height: 100%;
117 | background-color: #e86a33;
118 | width: 0%;
119 | transition: width 0.3s ease;
120 | }
121 |
122 | /* RESULT SCREEN */
123 |
124 | #result-screen h1 {
125 | color: #e86a33;
126 | margin-bottom: 30px;
127 | }
128 |
129 | .result-info {
130 | background-color: #f8f0e5;
131 | border-radius: 10px;
132 | padding: 20px;
133 | margin-bottom: 30px;
134 | }
135 |
136 | .result-info p {
137 | font-size: 1.2rem;
138 | color: #333;
139 | margin-bottom: 1rem;
140 | }
141 |
142 | #result-message {
143 | font-size: 1.5rem;
144 | font-weight: 600;
145 | color: #e86a33;
146 | }
147 |
148 | button {
149 | background-color: #e86a33;
150 | color: white;
151 | border: none;
152 | border-radius: 10px;
153 | padding: 15px 30px;
154 | font-size: 1.1rem;
155 | cursor: pointer;
156 | transition: background-color 0.3s ease;
157 | }
158 |
159 | button:hover {
160 | background-color: #d45b28;
161 | }
162 |
163 | /* RESPONSIVE DESIGN */
164 | @media (max-width: 500px) {
165 | /* when we hit 500px and below, implement the styles below */
166 |
167 | .screen {
168 | padding: 1rem;
169 | }
170 |
171 | #start-screen h1 {
172 | font-size: 2rem;
173 | }
174 |
175 | #question-text {
176 | font-size: 1.3rem;
177 | }
178 |
179 | .answer-btn {
180 | padding: 12px;
181 | }
182 |
183 | button {
184 | padding: 12px 25px;
185 | font-size: 1rem;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/02-color-palette-generator/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Color Palette Generator
7 |
14 |
15 |
16 |
17 |
18 |
Color Palette Generator
19 |
20 |
21 |
22 | Generate Palette
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | #E1F5FE
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | #B3E5FC
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | #81D4FA
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | #4FC3F7
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | #29B6F6
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/02-color-palette-generator/script.js:
--------------------------------------------------------------------------------
1 | const generateBtn = document.getElementById("generate-btn");
2 | const paletteContainer = document.querySelector(".palette-container");
3 |
4 | generateBtn.addEventListener("click", generatePalette);
5 |
6 | paletteContainer.addEventListener("click", function (e) {
7 | if (e.target.classList.contains("copy-btn")) {
8 | const hexValue = e.target.previousElementSibling.textContent;
9 |
10 | navigator.clipboard
11 | .writeText(hexValue)
12 | .then(() => showCopySuccess(e.target))
13 | .catch((err) => console.log(err));
14 | } else if (e.target.classList.contains("color")) {
15 | const hexValue = e.target.nextElementSibling.querySelector(".hex-value").textContent;
16 | navigator.clipboard
17 | .writeText(hexValue)
18 | .then(() => showCopySuccess(e.target.nextElementSibling.querySelector(".copy-btn")))
19 | .catch((err) => console.log(err));
20 | }
21 | });
22 |
23 | function showCopySuccess(element) {
24 | element.classList.remove("far", "fa-copy");
25 | element.classList.add("fas", "fa-check");
26 |
27 | element.style.color = "#48bb78";
28 |
29 | setTimeout(() => {
30 | element.classList.remove("fas", "fa-check");
31 | element.classList.add("far", "fa-copy");
32 | element.style.color = "";
33 | }, 1500);
34 | }
35 |
36 | function generatePalette() {
37 | const colors = [];
38 |
39 | for (let i = 0; i < 5; i++) {
40 | colors.push(generateRandomColor());
41 | }
42 |
43 | updatePaletteDisplay(colors);
44 | }
45 |
46 | function generateRandomColor() {
47 | const letters = "0123456789ABCDEF";
48 | let color = "#";
49 |
50 | for (let i = 0; i < 6; i++) {
51 | color += letters[Math.floor(Math.random() * 16)];
52 | }
53 | return color;
54 | }
55 |
56 | function updatePaletteDisplay(colors) {
57 | const colorBoxes = document.querySelectorAll(".color-box");
58 |
59 | colorBoxes.forEach((box, index) => {
60 | const color = colors[index];
61 | const colorDiv = box.querySelector(".color");
62 | const hexValue = box.querySelector(".hex-value");
63 |
64 | colorDiv.style.backgroundColor = color;
65 | hexValue.textContent = color;
66 | });
67 | }
68 |
69 | // generatePalette();
70 |
--------------------------------------------------------------------------------
/02-color-palette-generator/style.css:
--------------------------------------------------------------------------------
1 | /* Basic Reset */
2 | @import url(https://fonts.googleapis.com/css?family=Poppins:100,100italic,200,200italic,300,300italic,regular,italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic);
3 |
4 | * {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | font-family: "Poppins", sans-serif;
9 | }
10 |
11 | body {
12 | background: linear-gradient(135deg, #83a8df, #c3cfe2);
13 | min-height: 100vh;
14 | display: flex;
15 | align-items: center;
16 | justify-content: center;
17 | padding: 20px;
18 | }
19 |
20 | .container {
21 | background-color: #fff;
22 | border-radius: 1rem;
23 | padding: 2rem;
24 | width: 100%;
25 | max-width: 800px;
26 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
27 | }
28 |
29 | h1 {
30 | text-align: center;
31 | margin-bottom: 1.5rem;
32 | color: #333;
33 | position: relative;
34 | padding-bottom: 0.5rem;
35 | }
36 |
37 | h1::after {
38 | content: "";
39 | position: absolute;
40 | bottom: 0;
41 | left: 50%;
42 | transform: translateX(-50%);
43 | width: 50%;
44 | height: 3px;
45 | background-color: #667eea;
46 | border-radius: 2px;
47 | }
48 |
49 | #generate-btn {
50 | background: linear-gradient(45deg, #667eea, #764ba2);
51 | color: white;
52 | border: none;
53 | padding: 0.8rem 1.5rem;
54 | border-radius: 50px;
55 | cursor: pointer;
56 | font-weight: 500;
57 | margin-bottom: 2rem;
58 | font-size: 1rem;
59 | }
60 |
61 | #generate-btn:hover {
62 | transform: translateY(-2px);
63 | box-shadow: 0 6px 10px rgba(102, 126, 234, 0.3);
64 | }
65 |
66 | #generate-btn:active {
67 | transform: translateY(0);
68 | }
69 |
70 | .palette-container {
71 | display: grid;
72 | gap: 1rem;
73 | grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
74 | }
75 |
76 | .color-box {
77 | border-radius: 10px;
78 | overflow: hidden;
79 | box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
80 | transition: transform 0.2s;
81 | }
82 |
83 | .color-box:hover {
84 | transform: translateY(-5px);
85 | }
86 |
87 | .color {
88 | height: 120px;
89 | cursor: pointer;
90 | }
91 |
92 | .color-info {
93 | background-color: #fff;
94 | padding: 0.7rem;
95 | display: flex;
96 | align-items: center;
97 | justify-content: space-between;
98 | font-size: 0.9rem;
99 | }
100 |
101 | .hex-value {
102 | font-weight: 500;
103 | letter-spacing: 0.5px;
104 | }
105 |
106 | .copy-btn {
107 | cursor: pointer;
108 | color: #64748b;
109 | transition: color 0.2s;
110 | }
111 |
112 | .copy-btn:hover {
113 | color: #667eea;
114 | }
115 |
116 | @media (max-width: 768px) {
117 | .palette-container {
118 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/03-drag-and-drop/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Kanban Board
7 |
8 |
9 |
10 |
11 |
Simple Kanban Board
12 |
13 |
14 |
15 |
16 |
To Do
17 |
Wash Dishes
18 |
Buy Groceries
19 |
20 |
21 |
22 |
23 |
In Progress
24 |
Learn to code
25 |
26 |
27 |
28 |
29 |
Done
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/03-drag-and-drop/script.js:
--------------------------------------------------------------------------------
1 | const cards = document.querySelectorAll(".card");
2 | const lists = document.querySelectorAll(".list");
3 |
4 | for (const card of cards) {
5 | card.addEventListener("dragstart", dragStart);
6 | card.addEventListener("dragend", dragEnd);
7 | }
8 |
9 | for (const list of lists) {
10 | list.addEventListener("dragover", dragOver);
11 | list.addEventListener("dragenter", dragEnter);
12 | list.addEventListener("dragleave", dragLeave);
13 | list.addEventListener("drop", dragDrop);
14 | }
15 |
16 | function dragStart(e) {
17 | // this allows the drop location to know which element is being moved when you release it
18 | e.dataTransfer.setData("text/plain", this.id);
19 | }
20 |
21 | function dragEnd() {
22 | console.log("Drag ended");
23 | }
24 |
25 | function dragOver(e) {
26 | // this line is important because by default, browsers don't allow you to drop elements onto other elements.
27 | e.preventDefault();
28 | }
29 |
30 | function dragEnter(e) {
31 | e.preventDefault();
32 | this.classList.add("over");
33 | }
34 |
35 | function dragLeave(e) {
36 | this.classList.remove("over");
37 | }
38 |
39 | function dragDrop(e) {
40 | const id = e.dataTransfer.getData("text/plain");
41 | const card = document.getElementById(id);
42 |
43 | this.appendChild(card);
44 | this.classList.remove("over");
45 | }
46 |
--------------------------------------------------------------------------------
/03-drag-and-drop/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | font-family: sans-serif;
9 | background-color: #f7f9fc;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | min-height: 100vh;
14 | }
15 |
16 | .container {
17 | text-align: center;
18 | width: 100%;
19 | padding: 1.2rem;
20 | }
21 |
22 | h1 {
23 | color: #333;
24 | margin-bottom: 20px;
25 | font-size: 2rem;
26 | }
27 |
28 | .board {
29 | display: flex;
30 | justify-content: space-around;
31 | align-items: flex-start;
32 | width: 100%;
33 | max-width: 1200px;
34 | margin: 0 auto;
35 | gap: 20px;
36 | }
37 |
38 | .list {
39 | background-color: #e3e4e8;
40 | padding: 1rem;
41 | border-radius: 8px;
42 | width: 30%;
43 | min-height: 400px;
44 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
45 | }
46 |
47 | .list h2 {
48 | color: #555;
49 | margin-bottom: 1rem;
50 | font-size: 1.5rem;
51 | }
52 |
53 | .card {
54 | background-color: white;
55 | color: #333;
56 | padding: 1rem;
57 | margin-bottom: 10px;
58 | border-radius: 8px;
59 | cursor: grab;
60 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
61 | transition: transform 0.2s, box-shadow 0.2s;
62 | }
63 |
64 | .card:active {
65 | cursor: grabbing;
66 | transform: scale(1.05);
67 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
68 | }
69 |
70 | .list.over {
71 | background-color: #d1d3d8;
72 | }
73 |
74 | @media (max-width: 768px) {
75 | .board {
76 | flex-direction: column;
77 | align-items: center;
78 | }
79 |
80 | .list {
81 | width: 80%;
82 | margin-bottom: 20px;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/04-expense-tracker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Expense Tracker
7 |
8 |
12 |
13 |
14 |
15 |
Expense Tracker
16 |
17 |
18 |
Your Balance
19 |
$0.00
20 |
21 |
22 |
23 |
Income
24 |
$0.00
25 |
26 |
27 |
Expenses
28 |
$0.00
29 |
30 |
31 |
32 |
33 |
34 |
35 |
Transactions
36 |
39 |
40 |
41 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/04-expense-tracker/script.js:
--------------------------------------------------------------------------------
1 | const balanceEl = document.getElementById("balance");
2 | const incomeAmountEl = document.getElementById("income-amount");
3 | const expenseAmountEl = document.getElementById("expense-amount");
4 | const transactionListEl = document.getElementById("transaction-list");
5 | const transactionFormEl = document.getElementById("transaction-form");
6 | const descriptionEl = document.getElementById("description");
7 | const amountEl = document.getElementById("amount");
8 |
9 | let transactions = JSON.parse(localStorage.getItem("transactions")) || [];
10 |
11 | transactionFormEl.addEventListener("submit", addTransaction);
12 |
13 | function addTransaction(e) {
14 | e.preventDefault();
15 |
16 | // get form values
17 | const description = descriptionEl.value.trim();
18 | const amount = parseFloat(amountEl.value);
19 |
20 | transactions.push({
21 | id: Date.now(),
22 | description,
23 | amount,
24 | });
25 |
26 | localStorage.setItem("transactions", JSON.stringify(transactions));
27 |
28 | updateTransactionList();
29 | updateSummary();
30 |
31 | transactionFormEl.reset();
32 | }
33 |
34 | function updateTransactionList() {
35 | transactionListEl.innerHTML = "";
36 |
37 | const sortedTransactions = [...transactions].reverse();
38 |
39 | sortedTransactions.forEach((transaction) => {
40 | const transactionEl = createTransactionElement(transaction);
41 | transactionListEl.appendChild(transactionEl);
42 | });
43 | }
44 |
45 | function createTransactionElement(transaction) {
46 | const li = document.createElement("li");
47 | li.classList.add("transaction");
48 | li.classList.add(transaction.amount > 0 ? "income" : "expense");
49 |
50 | li.innerHTML = `
51 | ${transaction.description}
52 |
53 |
54 | ${formatCurrency(transaction.amount)}
55 | x
56 |
57 | `;
58 |
59 | return li;
60 | }
61 |
62 | function updateSummary() {
63 | // 100, -50, 200, -200 => 50
64 | const balance = transactions.reduce((acc, transaction) => acc + transaction.amount, 0);
65 |
66 | const income = transactions
67 | .filter((transaction) => transaction.amount > 0)
68 | .reduce((acc, transaction) => acc + transaction.amount, 0);
69 |
70 | const expenses = transactions
71 | .filter((transaction) => transaction.amount < 0)
72 | .reduce((acc, transaction) => acc + transaction.amount, 0);
73 |
74 | // update ui => todo: fix the formatting
75 | balanceEl.textContent = formatCurrency(balance);
76 | incomeAmountEl.textContent = formatCurrency(income);
77 | expenseAmountEl.textContent = formatCurrency(expenses);
78 | }
79 |
80 | function formatCurrency(number) {
81 | return new Intl.NumberFormat("en-US", {
82 | style: "currency",
83 | currency: "USD",
84 | }).format(number);
85 | }
86 |
87 | function removeTransaction(id) {
88 | // filter out the one we wanted to delete
89 | transactions = transactions.filter((transaction) => transaction.id !== id);
90 |
91 | localStorage.setItem("transcations", JSON.stringify(transactions));
92 |
93 | updateTransactionList();
94 | updateSummary();
95 | }
96 |
97 | // initial render
98 | updateTransactionList();
99 | updateSummary();
100 |
--------------------------------------------------------------------------------
/04-expense-tracker/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | background: linear-gradient(135deg, #2e8b57, #a8d5ba);
9 | min-height: 100vh;
10 | font-family: "Poppins", sans-serif;
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | padding: 20px;
15 | color: #2d3436;
16 | }
17 |
18 | .container {
19 | width: 100%;
20 | max-width: 1200px;
21 | background-color: #fff;
22 | padding: 2rem;
23 | border-radius: 24px;
24 | box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
25 | }
26 |
27 | h1 {
28 | text-align: center;
29 | color: #1a202c;
30 | margin-bottom: 35px;
31 | font-size: 2.2rem;
32 | font-weight: 600;
33 | letter-spacing: -0.5px;
34 | }
35 |
36 | h2 {
37 | color: #2d3748;
38 | margin-bottom: 1rem;
39 | font-size: 1.25rem;
40 | font-weight: 500;
41 | }
42 |
43 | .balance-container {
44 | text-align: center;
45 | margin-bottom: 35px;
46 | padding: 24px;
47 | background: linear-gradient(135deg, #a8d5ba, #6b8e23);
48 | border-radius: 1rem;
49 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
50 | }
51 |
52 | .balance-container h1 {
53 | font-size: 3rem;
54 | margin: 15px 0;
55 | }
56 |
57 | .summary {
58 | display: grid;
59 | grid-template-columns: 1fr 1fr;
60 | gap: 24px;
61 | margin-top: 24px;
62 | }
63 |
64 | .main-content {
65 | display: grid;
66 | grid-template-columns: 1fr 1fr;
67 | gap: 24px;
68 | }
69 |
70 | .income,
71 | .expenses {
72 | background-color: white;
73 | padding: 24px;
74 | border-radius: 1rem;
75 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
76 | transition: transform 0.2s ease;
77 | }
78 |
79 | .income:hover,
80 | .expenses:hover {
81 | transform: translateY(-2px);
82 | }
83 |
84 | .income h3 {
85 | color: #059669;
86 | font-size: 1.1rem;
87 | font-weight: 500;
88 | }
89 |
90 | .expenses h3 {
91 | color: #dc2626;
92 | font-size: 1.1rem;
93 | font-weight: 500;
94 | }
95 |
96 | .income p,
97 | .expenses p {
98 | margin-top: 8px;
99 | font-size: 1.75rem;
100 | font-weight: 600;
101 | }
102 |
103 | .income p {
104 | color: #059669;
105 | }
106 |
107 | .expenses p {
108 | color: #dc2626;
109 | }
110 |
111 | .transaction-container {
112 | height: 100%;
113 | display: flex;
114 | flex-direction: column;
115 | }
116 |
117 | #transaction-list {
118 | list-style: none;
119 | max-height: 500px;
120 | overflow-y: auto;
121 | padding-right: 8px;
122 | flex-grow: 1;
123 | }
124 |
125 | #transaction-list::-webkit-scrollbar {
126 | width: 8px;
127 | }
128 |
129 | #transaction-list::-webkit-scrollbar-track {
130 | /* track is the bg of the scrollbar */
131 | background-color: #f1f1f1;
132 | border-radius: 4px;
133 | }
134 | #transaction-list::-webkit-scrollbar-thumb {
135 | /* thumb is the draggble part of the scrollbar */
136 | background-color: #cbd5e0;
137 | border-radius: 4px;
138 | }
139 | #transaction-list::-webkit-scrollbar-thumb:hover {
140 | background-color: #a0aec0;
141 | }
142 |
143 | .transaction {
144 | display: flex;
145 | justify-content: space-between;
146 | align-items: center;
147 | padding: 1rem 1.2rem;
148 | margin-bottom: 12px;
149 | border-radius: 12px;
150 | background-color: #fff;
151 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
152 | transition: all 0.2s ease;
153 | border-right: 5px solid;
154 | animation: slideIn 0.3s ease;
155 | }
156 |
157 | @keyframes slideIn {
158 | from {
159 | opacity: 0;
160 | transform: translateX(-20px);
161 | }
162 |
163 | to {
164 | opacity: 1;
165 | transform: translateX(0px);
166 | }
167 | }
168 |
169 | .transaction:hover {
170 | transform: translateX(4px);
171 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
172 | }
173 |
174 | .transaction.income {
175 | border-right-color: #059669;
176 | }
177 |
178 | .transaction.expense {
179 | border-right-color: #dc2626;
180 | }
181 |
182 | .transaction .delete-btn {
183 | background: none;
184 | border: none;
185 | color: #dc2626;
186 | cursor: pointer;
187 | font-size: 1.4rem;
188 | opacity: 0;
189 | transition: all 0.2s ease;
190 | padding: 4px 8px;
191 | border-radius: 4px;
192 | margin-left: 12px;
193 | }
194 |
195 | .transaction:hover .delete-btn {
196 | opacity: 1;
197 | }
198 |
199 | .transaction .delete-btn:hover {
200 | background-color: #fee2e2;
201 | transform: scale(1.1);
202 | }
203 |
204 | .form-container {
205 | background: linear-gradient(135deg, #f6f8fb, #f1f4f8);
206 | padding: 24px;
207 | border-radius: 16px;
208 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
209 | height: 100%;
210 | display: flex;
211 | flex-direction: column;
212 | }
213 |
214 | .form-container form {
215 | height: 100%;
216 | display: flex;
217 | flex-direction: column;
218 | }
219 |
220 | .form-group {
221 | margin-bottom: 20px;
222 | }
223 |
224 | label {
225 | display: block;
226 | margin-bottom: 8px;
227 | color: #4a5568;
228 | font-weight: 500;
229 | }
230 |
231 | input {
232 | width: 100%;
233 | padding: 12px 16px;
234 | border: 2px solid #e2e8f0;
235 | border-radius: 8px;
236 | font-size: 1rem;
237 | transition: all 0.2s ease;
238 | background-color: white;
239 | }
240 |
241 | input:focus {
242 | outline: none;
243 | border-color: #667eea;
244 | box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
245 | }
246 |
247 | input:hover {
248 | border-color: #cbd5e0;
249 | }
250 |
251 | small {
252 | color: #718096;
253 | font-size: 0.875rem;
254 | margin-top: 4px;
255 | display: block;
256 | }
257 |
258 | button[type="submit"] {
259 | width: 100%;
260 | padding: 14px;
261 | background: linear-gradient(135deg, #2e8b57 0%, #3cb371 100%);
262 | color: white;
263 | border: none;
264 | border-radius: 8px;
265 | cursor: pointer;
266 | font-size: 1rem;
267 | font-weight: 500;
268 | transition: all 0.2s ease;
269 | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
270 | margin-top: auto;
271 | }
272 |
273 | button[type="submit"]:hover {
274 | transform: translateY(-2px);
275 | box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3);
276 | }
277 |
278 | button[type="submit"]:active {
279 | transform: translateY(0);
280 | }
281 |
282 | /* responsiveness */
283 |
284 | @media (max-width: 900px) {
285 | .main-content {
286 | grid-template-columns: 1fr;
287 | }
288 |
289 | #transaction-list {
290 | max-height: 300px;
291 | }
292 | }
293 |
294 | @media (max-width: 480px) {
295 | .container {
296 | padding: 24px;
297 | }
298 |
299 | .summary {
300 | grid-template-columns: 1fr;
301 | gap: 16px;
302 | }
303 |
304 | .balance-container h1 {
305 | font-size: 2.5rem;
306 | }
307 |
308 | .income p,
309 | .expenses p {
310 | font-size: 1.5rem;
311 | }
312 |
313 | .transaction {
314 | padding: 14px 16px;
315 | }
316 |
317 | h1 {
318 | font-size: 1.8rem;
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/05-bookmark-saver/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Bookmark Saver
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/05-bookmark-saver/script.js:
--------------------------------------------------------------------------------
1 | const addBookmarkBtn = document.getElementById("add-bookmark");
2 | const bookmarkList = document.getElementById("bookmark-list");
3 | const bookmarkNameInput = document.getElementById("bookmark-name");
4 | const bookmarkUrlInput = document.getElementById("bookmark-url");
5 |
6 | document.addEventListener("DOMContentLoaded", loadBookmarks);
7 |
8 | addBookmarkBtn.addEventListener("click", function () {
9 | const name = bookmarkNameInput.value.trim();
10 | const url = bookmarkUrlInput.value.trim();
11 |
12 | if (!name || !url) {
13 | alert("Please enter both name and URL.");
14 | return;
15 | } else {
16 | if (!url.startsWith("http://") && !url.startsWith("https://")) {
17 | alert("Please enter a valid URL starting with http:// or https://");
18 | return;
19 | }
20 |
21 | addBookmark(name, url);
22 | saveBookmark(name, url);
23 | bookmarkNameInput.value = "";
24 | bookmarkUrlInput.value = "";
25 | }
26 | });
27 |
28 | function addBookmark(name, url) {
29 | const li = document.createElement("li");
30 | const link = document.createElement("a");
31 | link.href = url;
32 | link.textContent = name;
33 | link.target = "_blank";
34 |
35 | const removeButton = document.createElement("button");
36 | removeButton.textContent = "Remove";
37 | removeButton.addEventListener("click", function () {
38 | bookmarkList.removeChild(li);
39 | removeBookmarkFromStorage(name, url);
40 | });
41 |
42 | li.appendChild(link);
43 | li.appendChild(removeButton);
44 |
45 | bookmarkList.appendChild(li);
46 | }
47 |
48 | function getBookmarksFromStorage() {
49 | const bookmarks = localStorage.getItem("bookmarks");
50 | return bookmarks ? JSON.parse(bookmarks) : [];
51 | }
52 |
53 | function saveBookmark(name, url) {
54 | const bookmarks = getBookmarksFromStorage();
55 | bookmarks.push({ name, url });
56 | localStorage.setItem("bookmarks", JSON.stringify(bookmarks));
57 | }
58 |
59 | function loadBookmarks() {
60 | const bookmarks = getBookmarksFromStorage();
61 | bookmarks.forEach((bookmark) => addBookmark(bookmark.name, bookmark.url));
62 | }
63 |
64 | function removeBookmarkFromStorage(name, url) {
65 | let bookmarks = getBookmarksFromStorage();
66 | bookmarks = bookmarks.filter((bookmark) => bookmark.name !== name || bookmark.url !== url);
67 | localStorage.setItem("bookmarks", JSON.stringify(bookmarks));
68 | }
69 |
--------------------------------------------------------------------------------
/05-bookmark-saver/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | font-family: sans-serif;
9 | background-color: #f0f9f4;
10 | color: #333;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | height: 100vh;
15 | }
16 |
17 | .app-container {
18 | background-color: #fff;
19 | padding: 20px;
20 | border-radius: 8px;
21 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
22 | max-width: 400px;
23 | width: 90%;
24 | text-align: center;
25 | }
26 |
27 | h1 {
28 | color: #2ecc71;
29 | margin-bottom: 20px;
30 | }
31 |
32 | .input-container {
33 | display: flex;
34 | flex-direction: column;
35 | gap: 10px;
36 | margin-bottom: 20px;
37 | }
38 |
39 | input {
40 | padding: 10px;
41 | border-radius: 4px;
42 | border: 1px solid #ddd;
43 | }
44 |
45 | button {
46 | padding: 10px;
47 | background-color: #2ecc71;
48 | color: white;
49 | border: none;
50 | border-radius: 4px;
51 | cursor: pointer;
52 | transition: background-color 0.3s ease;
53 | }
54 |
55 | button:hover {
56 | background-color: #27ae60;
57 | }
58 |
59 | #bookmark-list {
60 | list-style: none;
61 | padding: 0;
62 | }
63 |
64 | #bookmark-list li {
65 | background-color: #f9f9f9;
66 | padding: 10px;
67 | border: 1px solid #ddd;
68 | border-radius: 4px;
69 | margin-bottom: 10px;
70 | display: flex;
71 | justify-content: space-between;
72 | align-items: center;
73 | }
74 |
75 | #bookmark-list a {
76 | color: #2ecc71;
77 | text-decoration: none;
78 | }
79 |
80 | #bookmark-list button {
81 | background-color: #e74c3c;
82 | padding: 5px 10px;
83 | }
84 |
85 | #bookmark-list button:hover {
86 | background-color: #c0392b;
87 | }
88 |
--------------------------------------------------------------------------------
/06-form-validator/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Form Validator
7 |
8 |
9 |
10 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/06-form-validator/script.js:
--------------------------------------------------------------------------------
1 | const form = document.getElementById("registration-form");
2 | const username = document.getElementById("username");
3 | const email = document.getElementById("email");
4 | const password = document.getElementById("password");
5 | const confirmPassword = document.getElementById("confirmPassword");
6 |
7 | form.addEventListener("submit", function (e) {
8 | e.preventDefault();
9 |
10 | const isRequiredValid = checkRequired([username, email, password, confirmPassword]);
11 |
12 | let isFormValid = isRequiredValid;
13 |
14 | if (isRequiredValid) {
15 | const isUsernameValid = checkLength(username, 3, 15);
16 | const isEmailValid = checkEmail(email);
17 | const isPasswordValid = checkLength(password, 6, 25);
18 | const isPasswordsMatch = checkPasswordsMatch(password, confirmPassword);
19 |
20 | isFormValid = isUsernameValid && isEmailValid && isPasswordValid && isPasswordsMatch;
21 | }
22 |
23 | if (isFormValid) {
24 | alert("Registration successful!");
25 | form.reset();
26 | document.querySelectorAll(".form-group").forEach((group) => {
27 | group.className = "form-group";
28 | });
29 | }
30 | });
31 |
32 | function checkPasswordsMatch(input1, input2) {
33 | if (input1.value !== input2.value) {
34 | showError(input2, "Passwords do not match");
35 | return false;
36 | }
37 | return true;
38 | }
39 |
40 | function checkEmail(email) {
41 | // Email regex that covers most common email formats
42 | const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
43 | if (emailRegex.test(email.value.trim())) {
44 | showSuccess(email);
45 | return true;
46 | } else {
47 | showError(email, "Email is not valid");
48 | return false;
49 | }
50 | }
51 |
52 | function checkLength(input, min, max) {
53 | if (input.value.length < min) {
54 | showError(input, `${formatFieldName(input)} must be at least ${min} characters.`);
55 | return false;
56 | } else if (input.value.length > max) {
57 | showError(input, `${formatFieldName(input)} must be less than ${max} characters.`);
58 | return false;
59 | } else {
60 | showSuccess(input);
61 | return true;
62 | }
63 | }
64 |
65 | function checkRequired(inputArray) {
66 | let isValid = true;
67 |
68 | inputArray.forEach((input) => {
69 | // Password is required
70 | if (input.value.trim() === "") {
71 | showError(input, `${formatFieldName(input)} is required`);
72 | isValid = false;
73 | } else {
74 | showSuccess(input);
75 | }
76 | });
77 |
78 | return isValid;
79 | }
80 |
81 | // Format field name with proper capitalization
82 | function formatFieldName(input) {
83 | // input id: username -> Username
84 | return input.id.charAt(0).toUpperCase() + input.id.slice(1);
85 | }
86 |
87 | function showError(input, message) {
88 | const formGroup = input.parentElement;
89 | formGroup.className = "form-group error";
90 | const small = formGroup.querySelector("small");
91 | small.innerText = message;
92 | }
93 |
94 | function showSuccess(input) {
95 | const formGroup = input.parentElement;
96 | formGroup.className = "form-group success";
97 | }
98 |
--------------------------------------------------------------------------------
/06-form-validator/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | background-color: #f9f9f9;
9 | font-family: sans-serif;
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | min-height: 100vh;
14 | }
15 |
16 | .container {
17 | background-color: #fff;
18 | border-radius: 8px;
19 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.1);
20 | width: 100%;
21 | max-width: 400px;
22 | padding: 30px;
23 | overflow: hidden;
24 | }
25 |
26 | h1 {
27 | text-align: center;
28 | color: #333;
29 | margin-bottom: 1.2rem;
30 | }
31 |
32 | .form-group {
33 | margin-bottom: 20px;
34 | position: relative;
35 | }
36 |
37 | label {
38 | display: block;
39 | margin-bottom: 5px;
40 | color: #555;
41 | font-weight: bold;
42 | }
43 |
44 | input {
45 | border: 2px solid #f0f0f0;
46 | border-radius: 4px;
47 | display: block;
48 | width: 100%;
49 | padding: 10px;
50 | font-size: 14px;
51 | transition: border-color 0.3s;
52 | }
53 |
54 | input:focus {
55 | outline: none;
56 | border-color: #3498db;
57 | }
58 |
59 | small {
60 | visibility: hidden;
61 | position: absolute;
62 | bottom: -18px;
63 | left: 0;
64 | font-size: 12px;
65 | }
66 |
67 | button {
68 | cursor: pointer;
69 | background: #3498db;
70 | border: none;
71 | color: white;
72 | display: block;
73 | font-size: 1rem;
74 | margin-top: 20px;
75 | padding: 12px;
76 | border-radius: 4px;
77 | width: 100%;
78 | transition: background-color 0.3s;
79 | }
80 |
81 | button:hover {
82 | background-color: #2980b9;
83 | }
84 |
85 | .form-group.error input {
86 | border-color: #e74c3c;
87 | }
88 |
89 | .form-group.success input {
90 | border-color: #2ecc71;
91 | }
92 |
93 | .form-group.error small {
94 | visibility: visible;
95 | color: #e74c3c;
96 | }
97 |
--------------------------------------------------------------------------------
/07-password-generator/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Password Generator
7 |
8 |
15 |
16 |
17 |
18 |
Password Generator
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
57 |
58 |
59 |
60 | Generate Password
61 |
62 |
63 |
64 |
Password Strength: Medium
65 |
66 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/07-password-generator/script.js:
--------------------------------------------------------------------------------
1 | // DOM Elements - all the elements we need from HTML
2 | const passwordInput = document.getElementById("password");
3 | const lengthSlider = document.getElementById("length");
4 | const lengthDisplay = document.getElementById("length-value");
5 | const uppercaseCheckbox = document.getElementById("uppercase");
6 | const lowercaseCheckbox = document.getElementById("lowercase");
7 | const numbersCheckbox = document.getElementById("numbers");
8 | const symbolsCheckbox = document.getElementById("symbols");
9 | const generateButton = document.getElementById("generate-btn");
10 | const copyButton = document.getElementById("copy-btn");
11 | const strengthBar = document.querySelector(".strength-bar");
12 | const strengthText = document.querySelector(".strength-container p");
13 | const strengthLabel = document.getElementById("strength-label");
14 |
15 | // Character sets
16 | const uppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
17 | const lowercaseLetters = "abcdefghijklmnopqrstuvwxyz";
18 | const numberCharacters = "0123456789";
19 | const symbolCharacters = "!@#$%^&*()-_=+[]{}|;:,.<>?/";
20 |
21 | lengthSlider.addEventListener("input", () => {
22 | lengthDisplay.textContent = lengthSlider.value;
23 | });
24 |
25 | generateButton.addEventListener("click", makePassword);
26 |
27 | function makePassword() {
28 | const length = Number(lengthSlider.value);
29 | const includeUppercase = uppercaseCheckbox.checked;
30 | const includeLowercase = lowercaseCheckbox.checked;
31 | const includeNumbers = numbersCheckbox.checked;
32 | const includeSymbols = symbolsCheckbox.checked;
33 |
34 | if (!includeUppercase && !includeLowercase && !includeNumbers && !includeSymbols) {
35 | alert("Please select at least one char type.");
36 | return;
37 | }
38 |
39 | const newPassword = createRandomPassword(
40 | length,
41 | includeUppercase,
42 | includeLowercase,
43 | includeNumbers,
44 | includeSymbols
45 | );
46 |
47 | passwordInput.value = newPassword;
48 | updateStrengthMeter(newPassword);
49 | }
50 |
51 | function updateStrengthMeter(password) {
52 | const passwordLength = password.length;
53 | const hasUppercase = /[A-Z]/.test(password);
54 | const hasLowercase = /[a-z]/.test(password);
55 | const hasNumbers = /[0-9]/.test(password);
56 | const hasSymbols = /[!@#$%^&*()-_=+[\]{}|;:,.<>?]/.test(password);
57 |
58 | let strengthScore = 0;
59 |
60 | // here the .min will get the minimum value
61 | // but this will make sure that "at maximum" you would get 40
62 | strengthScore += Math.min(passwordLength * 2, 40);
63 |
64 | if (hasUppercase) strengthScore += 15;
65 | if (hasLowercase) strengthScore += 15;
66 | if (hasNumbers) strengthScore += 15;
67 | if (hasSymbols) strengthScore += 15;
68 |
69 | // enforce minimum score for every short password
70 | if (passwordLength < 8) {
71 | strengthScore = Math.min(strengthScore, 40);
72 | }
73 |
74 | // ensure the width of the strength bar is a valid percentage
75 | const safeScore = Math.max(5, Math.min(100, strengthScore));
76 | strengthBar.style.width = safeScore + "%";
77 |
78 | let strengthLabelText = "";
79 | let barColor = "";
80 |
81 | if (strengthScore < 40) {
82 | // weak password
83 | barColor = "#fc8181";
84 | strengthLabelText = "Weak";
85 | } else if (strengthScore < 70) {
86 | // Medium password
87 | barColor = "#fbd38d"; // Yellow
88 | strengthLabelText = "Medium";
89 | } else {
90 | // Strong password
91 | barColor = "#68d391"; // Green
92 | strengthLabelText = "Strong";
93 | }
94 |
95 | strengthBar.style.backgroundColor = barColor;
96 | strengthLabel.textContent = strengthLabelText;
97 | }
98 |
99 | function createRandomPassword(
100 | length,
101 | includeUppercase,
102 | includeLowercase,
103 | includeNumbers,
104 | includeSymbols
105 | ) {
106 | let allCharacters = "";
107 | // "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
108 |
109 | if (includeUppercase) allCharacters += uppercaseLetters;
110 | if (includeLowercase) allCharacters += lowercaseLetters;
111 | if (includeNumbers) allCharacters += numberCharacters;
112 | if (includeSymbols) allCharacters += symbolCharacters;
113 |
114 | let password = "";
115 |
116 | for (let i = 0; i < length; i++) {
117 | const randomIndex = Math.floor(Math.random() * allCharacters.length);
118 | password += allCharacters[randomIndex];
119 | }
120 |
121 | return password;
122 | }
123 |
124 | window.addEventListener("DOMContentLoaded", makePassword);
125 |
126 | copyButton.addEventListener("click", () => {
127 | if (!passwordInput.value) return;
128 |
129 | navigator.clipboard
130 | .writeText(passwordInput.value)
131 | .then(() => showCopySuccess())
132 | .catch((error) => console.log("Could not copy:", error));
133 | });
134 |
135 | function showCopySuccess() {
136 | copyButton.classList.remove("far", "fa-copy");
137 | copyButton.classList.add("fas", "fa-check");
138 | copyButton.style.color = "#48bb78";
139 |
140 | setTimeout(() => {
141 | copyButton.classList.remove("fas", "fa-check");
142 | copyButton.classList.add("far", "fa-copy");
143 | copyButton.style.color = "";
144 | }, 1500);
145 | }
146 |
--------------------------------------------------------------------------------
/07-password-generator/style.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Montserrat:100,200,300,regular,500,600,700,800,900,100italic,200italic,300italic,italic,500italic,600italic,700italic,800italic,900italic);
2 |
3 | :root {
4 | --primary-color: #2b5876;
5 | --secondary-color: #4e4376;
6 | --weak-color: #fc8181;
7 | --medium-color: #fbd38d;
8 | --strong-color: #68d391;
9 | }
10 |
11 | * {
12 | margin: 0;
13 | padding: 0;
14 | box-sizing: border-box;
15 | font-family: "Montserrat", sans-serif;
16 | }
17 |
18 | body {
19 | background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
20 | min-height: 100vh;
21 | display: flex;
22 | justify-content: center;
23 | align-items: center;
24 | color: #333;
25 | padding: 20px;
26 | }
27 |
28 | .container {
29 | background-color: #fff;
30 | padding: 2.5rem;
31 | border-radius: 1rem;
32 | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
33 | width: 90%;
34 | max-width: 450px;
35 | }
36 |
37 | h1 {
38 | text-align: center;
39 | margin-bottom: 1.8rem;
40 | color: var(--primary-color);
41 | font-size: 1.8rem;
42 | font-weight: 700;
43 | position: relative;
44 | padding-bottom: 0.5rem;
45 | }
46 |
47 | h1::after {
48 | content: "";
49 | position: absolute;
50 | bottom: 0;
51 | left: 50%;
52 | transform: translateX(-50%);
53 | width: 50%;
54 | height: 3px;
55 | background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
56 | border-radius: 2px;
57 | }
58 |
59 | .password-container {
60 | margin-bottom: 2rem;
61 | background-color: #f8fafc;
62 | border-radius: 10px;
63 | border: 1px solid #e2e8f0;
64 | overflow: hidden;
65 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
66 | display: flex;
67 | align-items: center;
68 | justify-content: space-between;
69 | padding: 0 1rem 0 0;
70 | }
71 |
72 | #password {
73 | width: 100%;
74 | padding: 1.1rem;
75 | padding-right: 50px;
76 | border: none;
77 | background: transparent;
78 | color: #4a5568;
79 | letter-spacing: 1px;
80 | outline: none;
81 | border-radius: 10px;
82 | }
83 |
84 | #copy-btn {
85 | cursor: pointer;
86 | color: #718096;
87 | font-size: 1.1rem;
88 | width: 35px;
89 | height: 35px;
90 | display: flex;
91 | align-items: center;
92 | justify-content: center;
93 | border-radius: 50%;
94 | }
95 |
96 | #copy-btn:hover {
97 | color: var(--primary-color);
98 | }
99 |
100 | .options {
101 | margin-bottom: 2rem;
102 | background-color: #f8fafc;
103 | padding: 1.5rem;
104 | border-radius: 10px;
105 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
106 | }
107 |
108 | .option {
109 | display: flex;
110 | justify-content: space-between;
111 | align-items: center;
112 | margin-bottom: 1rem;
113 | }
114 |
115 | .option:last-child {
116 | margin-bottom: 0;
117 | }
118 |
119 | .option label {
120 | font-size: 0.9rem;
121 | color: #4a5568;
122 | font-weight: 500;
123 | }
124 |
125 | .range-container {
126 | display: flex;
127 | align-items: center;
128 | gap: 10px;
129 | width: 60%;
130 | }
131 |
132 | input[type="range"] {
133 | accent-color: var(--primary-color);
134 | height: 5px;
135 | cursor: pointer;
136 | flex: 1;
137 | }
138 |
139 | input[type="checkbox"] {
140 | width: 20px;
141 | height: 20px;
142 | border: 2px solid var(--primary-color);
143 | appearance: none;
144 | border-radius: 4px;
145 | cursor: pointer;
146 | outline: none;
147 | position: relative;
148 | transition: all 0.3s;
149 | }
150 |
151 | input[type="checkbox"]:checked {
152 | background-color: var(--primary-color);
153 | }
154 |
155 | input[type="checkbox"]:checked::after {
156 | content: "\2714";
157 | position: absolute;
158 | color: white;
159 | left: 50%;
160 | top: 50%;
161 | transform: translate(-50%, -50%);
162 | font-size: 12px;
163 | }
164 |
165 | #length-value {
166 | font-weight: 600;
167 | color: var(--primary-color);
168 | width: 30px;
169 | text-align: center;
170 | background: #e2e8f0;
171 | border-radius: 4px;
172 | padding: 2px 5px;
173 | }
174 |
175 | button {
176 | width: 100%;
177 | padding: 1rem;
178 | background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
179 | color: white;
180 | border: none;
181 | border-radius: 10px;
182 | font-size: 1rem;
183 | font-weight: 600;
184 | cursor: pointer;
185 | transition: all 0.3s;
186 | margin-bottom: 1.5rem;
187 | box-shadow: 0 4px 8px rgba(43, 88, 118, 0.2);
188 | }
189 |
190 | button:hover {
191 | transform: translateY(-2px);
192 | }
193 |
194 | button:active {
195 | transform: translateY(0);
196 | }
197 |
198 | .strength-container {
199 | margin-top: 0.8rem;
200 | }
201 |
202 | .strength-container p {
203 | font-size: 0.9rem;
204 | color: #4a5568;
205 | margin-bottom: 0.6rem;
206 | display: flex;
207 | justify-content: space-between;
208 | font-weight: 500;
209 | }
210 |
211 | #strength-label {
212 | color: var(--primary-color);
213 | font-weight: 600;
214 | }
215 |
216 | .strength-meter {
217 | height: 10px;
218 | background-color: #edf2f7;
219 | border-radius: 5px;
220 | margin-top: 0.5rem;
221 | overflow: hidden;
222 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
223 | }
224 |
225 | .strength-bar {
226 | height: 100%;
227 | width: 0;
228 | min-width: 5%;
229 | background-color: var(--weak-color);
230 | transition: all 0.3s ease;
231 | border-radius: 5px;
232 | }
233 |
234 | @media (max-width: 768px) {
235 | .container {
236 | width: 95%;
237 | padding: 1.8rem;
238 | }
239 |
240 | h1 {
241 | font-size: 1.6rem;
242 | }
243 |
244 | .option label {
245 | font-size: 0.9rem;
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/08-todo-app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Todo App
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | My Tasks
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | All
35 | Active
36 | Completed
37 |
38 |
39 |
40 |
51 |
52 |
53 |
No tasks here yet
54 |
55 |
56 |
57 |
58 | 0 items left
59 | Clear completed
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/08-todo-app/script.js:
--------------------------------------------------------------------------------
1 | // DOM Elements
2 | const taskInput = document.getElementById("task-input");
3 | const addTaskBtn = document.getElementById("add-task");
4 | const todosList = document.getElementById("todos-list");
5 | const itemsLeft = document.getElementById("items-left");
6 | const clearCompletedBtn = document.getElementById("clear-completed");
7 | const emptyState = document.querySelector(".empty-state");
8 | const dateElement = document.getElementById("date");
9 | const filters = document.querySelectorAll(".filter");
10 |
11 | let todos = [];
12 | let currentFilter = "all";
13 |
14 | addTaskBtn.addEventListener("click", () => {
15 | addTodo(taskInput.value);
16 | });
17 |
18 | taskInput.addEventListener("keydown", (e) => {
19 | if (e.key === "Enter") addTodo(taskInput.value);
20 | });
21 |
22 | clearCompletedBtn.addEventListener("click", clearCompleted);
23 |
24 | function addTodo(text) {
25 | if (text.trim() === "") return;
26 |
27 | const todo = {
28 | id: Date.now(),
29 | text,
30 | completed: false,
31 | };
32 |
33 | todos.push(todo);
34 |
35 | saveTodos();
36 | renderTodos();
37 | taskInput.value = "";
38 | }
39 |
40 | function saveTodos() {
41 | localStorage.setItem("todos", JSON.stringify(todos));
42 | updateItemsCount();
43 | checkEmptyState();
44 | }
45 |
46 | function updateItemsCount() {
47 | const uncompletedTodos = todos.filter((todo) => !todo.completed);
48 | itemsLeft.textContent = `${uncompletedTodos?.length} item${
49 | uncompletedTodos?.length !== 1 ? "s" : ""
50 | } left`;
51 | }
52 |
53 | function checkEmptyState() {
54 | const filteredTodos = filterTodos(currentFilter);
55 | if (filteredTodos?.length === 0) emptyState.classList.remove("hidden");
56 | else emptyState.classList.add("hidden");
57 | }
58 |
59 | function filterTodos(filter) {
60 | switch (filter) {
61 | case "active":
62 | return todos.filter((todo) => !todo.completed);
63 | case "completed":
64 | return todos.filter((todo) => todo.completed);
65 | default:
66 | return todos;
67 | }
68 | }
69 |
70 | function renderTodos() {
71 | todosList.innerHTML = "";
72 |
73 | const filteredTodos = filterTodos(currentFilter);
74 |
75 | filteredTodos.forEach((todo) => {
76 | const todoItem = document.createElement("li");
77 | todoItem.classList.add("todo-item");
78 | if (todo.completed) todoItem.classList.add("completed");
79 |
80 | const checkboxContainer = document.createElement("label");
81 | checkboxContainer.classList.add("checkbox-container");
82 |
83 | const checkbox = document.createElement("input");
84 | checkbox.type = "checkbox";
85 | checkbox.classList.add("todo-checkbox");
86 | checkbox.checked = todo.completed;
87 | checkbox.addEventListener("change", () => toggleTodo(todo.id));
88 |
89 | const checkmark = document.createElement("span");
90 | checkmark.classList.add("checkmark");
91 |
92 | checkboxContainer.appendChild(checkbox);
93 | checkboxContainer.appendChild(checkmark);
94 |
95 | const todoText = document.createElement("span");
96 | todoText.classList.add("todo-item-text");
97 | todoText.textContent = todo.text;
98 |
99 | const deleteBtn = document.createElement("button");
100 | deleteBtn.classList.add("delete-btn");
101 | deleteBtn.innerHTML = ' ';
102 | deleteBtn.addEventListener("click", () => deleteTodo(todo.id));
103 |
104 | todoItem.appendChild(checkboxContainer);
105 | todoItem.appendChild(todoText);
106 | todoItem.appendChild(deleteBtn);
107 |
108 | todosList.appendChild(todoItem);
109 | });
110 | }
111 |
112 | function clearCompleted() {
113 | todos = todos.filter((todo) => !todo.completed);
114 | saveTodos();
115 | renderTodos();
116 | }
117 |
118 | function toggleTodo(id) {
119 | todos = todos.map((todo) => {
120 | if (todo.id === id) {
121 | return { ...todo, completed: !todo.completed };
122 | }
123 |
124 | return todo;
125 | });
126 | saveTodos();
127 | renderTodos();
128 | }
129 |
130 | function deleteTodo(id) {
131 | todos = todos.filter((todo) => todo.id !== id);
132 | saveTodos();
133 | renderTodos();
134 | }
135 |
136 | function loadTodos() {
137 | const storedTodos = localStorage.getItem("todos");
138 | if (storedTodos) todos = JSON.parse(storedTodos);
139 | renderTodos();
140 | }
141 |
142 | filters.forEach((filter) => {
143 | filter.addEventListener("click", () => {
144 | setActiveFilter(filter.getAttribute("data-filter"));
145 | });
146 | });
147 |
148 | function setActiveFilter(filter) {
149 | currentFilter = filter;
150 |
151 | filters.forEach((item) => {
152 | if (item.getAttribute("data-filter") === filter) {
153 | item.classList.add("active");
154 | } else {
155 | item.classList.remove("active");
156 | }
157 | });
158 |
159 | renderTodos();
160 | }
161 |
162 | function setDate() {
163 | const options = { weekday: "long", month: "short", day: "numeric" };
164 | const today = new Date();
165 | dateElement.textContent = today.toLocaleDateString("en-US", options);
166 | }
167 |
168 | window.addEventListener("DOMContentLoaded", () => {
169 | loadTodos();
170 | updateItemsCount();
171 | setDate();
172 | });
173 |
--------------------------------------------------------------------------------
/08-todo-app/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | --primary: #7749f8;
9 | --primary-light: #9775fa;
10 | --dark: #212529;
11 | --light: #f8f9fa;
12 | --gray: #868e96;
13 | --danger: #fa5252;
14 | --success: #40c057;
15 | --border: #e9ecef;
16 | }
17 |
18 | body {
19 | font-family: sans-serif;
20 | background-color: #f1f3f5;
21 | min-height: 100vh;
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | padding: 20px;
26 | color: var(--dark);
27 | }
28 |
29 | .app {
30 | width: 100%;
31 | max-width: 500px;
32 | background-color: #fff;
33 | border-radius: 12px;
34 | overflow: hidden;
35 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
36 | }
37 |
38 | header {
39 | background-color: var(--primary);
40 | color: white;
41 | padding: 20px 25px;
42 | }
43 |
44 | header h1 {
45 | font-size: 24px;
46 | margin-bottom: 5px;
47 | font-weight: 600;
48 | }
49 |
50 | header p {
51 | font-size: 14px;
52 | opacity: 0.9;
53 | }
54 |
55 | .todo-input {
56 | padding: 20px 25px;
57 | display: flex;
58 | gap: 10px;
59 | background-color: var(--light);
60 | border-bottom: 1px solid var(--border);
61 | }
62 |
63 | .todo-input input {
64 | flex: 1;
65 | padding: 12px 15px;
66 | border: 1px solid #dee2e6;
67 | font-size: 1rem;
68 | transition: all 0.2s;
69 | border-radius: 6px;
70 | }
71 |
72 | .todo-input input:focus {
73 | outline: none;
74 | border-color: var(--primary);
75 | box-shadow: 0 0 0 3px rgba(119, 73, 248, 0.15);
76 | }
77 |
78 | .todo-input button {
79 | background-color: var(--primary);
80 | color: white;
81 | width: 40px;
82 | height: 40px;
83 | border: none;
84 | border-radius: 6px;
85 | cursor: pointer;
86 | transition: all 0.2s;
87 | }
88 |
89 | .todo-input button:hover {
90 | background-color: var(--primary-light);
91 | }
92 |
93 | .filters {
94 | display: flex;
95 | gap: 15px;
96 | border-bottom: 1px solid var(--border);
97 | padding: 15px 25px;
98 | }
99 |
100 | .filter {
101 | padding: 5px 3px;
102 | cursor: pointer;
103 | color: var(--gray);
104 | border-bottom: 2px solid transparent;
105 | transition: all 0.2s;
106 | }
107 |
108 | .filter:hover {
109 | color: var(--primary);
110 | }
111 |
112 | .filter.active {
113 | color: var(--primary);
114 | border-bottom: 2px solid var(--primary);
115 | font-weight: 500;
116 | }
117 |
118 | .todos-container {
119 | padding: 15px 0;
120 | max-height: 300px;
121 | overflow-y: auto;
122 | }
123 |
124 | #todos-list {
125 | list-style-type: none;
126 | }
127 |
128 | .todo-item {
129 | padding: 12px 15px;
130 | display: flex;
131 | align-items: center;
132 | transition: background-color 0.2s;
133 | }
134 |
135 | .todo-item:hover {
136 | background-color: var(--light);
137 | }
138 |
139 | .checkbox-container {
140 | margin-right: 15px;
141 | }
142 |
143 | .todo-checkbox {
144 | opacity: 0;
145 | position: absolute;
146 | }
147 |
148 | .checkmark {
149 | display: inline-block;
150 | width: 20px;
151 | height: 20px;
152 | border: 2px solid #dee2e6;
153 | border-radius: 4px;
154 | position: relative;
155 | cursor: pointer;
156 | transition: all 0.2s;
157 | }
158 |
159 | .todo-checkbox:checked + .checkmark {
160 | background-color: var(--success);
161 | border-color: var(--success);
162 | }
163 |
164 | .todo-checkbox:checked + .checkmark::after {
165 | content: "";
166 | position: absolute;
167 | left: 6px;
168 | top: 2px;
169 | width: 5px;
170 | height: 10px;
171 | border: solid white;
172 | border-width: 0 2px 2px 0;
173 | transform: rotate(45deg);
174 | }
175 |
176 | .todo-item-text {
177 | flex: 1;
178 | font-size: 1rem;
179 | transition: all 0.2s;
180 | }
181 |
182 | .todo-item.completed .todo-item-text {
183 | text-decoration: line-through;
184 | color: var(--gray);
185 | }
186 |
187 | .delete-btn {
188 | background: none;
189 | border: none;
190 | color: var(--gray);
191 | cursor: pointer;
192 | font-size: 16px;
193 | opacity: 0;
194 | transition: all 0.2s;
195 | }
196 |
197 | .todo-item:hover .delete-btn {
198 | opacity: 1;
199 | }
200 |
201 | .delete-btn:hover {
202 | color: var(--danger);
203 | }
204 |
205 | .empty-state {
206 | display: flex;
207 | flex-direction: column;
208 | align-items: center;
209 | justify-content: center;
210 | padding: 40px 0;
211 | color: var(--gray);
212 | }
213 |
214 | .empty-state i {
215 | font-size: 40px;
216 | margin-bottom: 10px;
217 | opacity: 0.7;
218 | }
219 |
220 | .hidden {
221 | display: none;
222 | }
223 |
224 | footer {
225 | display: flex;
226 | justify-content: space-between;
227 | align-items: center;
228 | padding: 15px 25px;
229 | border-top: 1px solid var(--border);
230 | background-color: var(--light);
231 | font-size: 14px;
232 | }
233 |
234 | #items-left {
235 | color: var(--gray);
236 | }
237 |
238 | #clear-completed {
239 | background: none;
240 | border: none;
241 | color: var(--gray);
242 | cursor: pointer;
243 | transition: all 0.2s;
244 | }
245 |
246 | #clear-completed:hover {
247 | color: var(--danger);
248 | }
249 |
--------------------------------------------------------------------------------
/09-contact-form/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Contact Form
7 |
8 |
15 |
16 |
17 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/09-contact-form/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | box-sizing: border-box;
5 | font-family: sans-serif;
6 | }
7 |
8 | :root {
9 | --primay-color: #4ade80;
10 | --primay-dark: #22c55e;
11 | --bg-dark: #1a1a1a;
12 | --text-light: #f3f4f6;
13 | --text-gray: #9ca3af;
14 | --card-bg: #252525;
15 | --input-bg: #333;
16 | --border-radius: 10px;
17 | }
18 |
19 | body {
20 | background-color: var(--bg-dark);
21 | color: var(--text-light);
22 | min-height: 100vh;
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | padding: 30px 15px;
27 | }
28 |
29 | .container {
30 | width: 100%;
31 | max-width: 800px;
32 | }
33 |
34 | .form-container {
35 | background-color: var(--card-bg);
36 | border-radius: var(--border-radius);
37 | padding: 40px;
38 | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
39 | animation: fadeIn 0.5s ease-out;
40 | }
41 |
42 | @keyframes fadeIn {
43 | from {
44 | opacity: 0;
45 | transform: translateY(20px);
46 | }
47 | to {
48 | opacity: 1;
49 | transform: translateY(0);
50 | }
51 | }
52 |
53 | h1 {
54 | color: var(--primay-color);
55 | font-size: 2.2rem;
56 | margin-bottom: 10px;
57 | }
58 |
59 | p {
60 | margin-bottom: 30px;
61 | color: var(--text-gray);
62 | }
63 |
64 | .input-group {
65 | display: grid;
66 | grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
67 | gap: 20px;
68 | margin-bottom: 25px;
69 | }
70 |
71 | .input-field {
72 | position: relative;
73 | }
74 |
75 | .message-field {
76 | position: relative;
77 | }
78 |
79 | .input-field i,
80 | .message-field i {
81 | position: absolute;
82 | top: 50%;
83 | left: 15px;
84 | transform: translateY(-50%);
85 | color: var(--primay-color);
86 | font-size: 1.1rem;
87 | }
88 |
89 | .message-field i {
90 | top: 25px;
91 | }
92 |
93 | input,
94 | textarea {
95 | width: 100%;
96 | padding: 15px 15px 15px 45px;
97 | background-color: var(--input-bg);
98 | border: 2px solid transparent;
99 | border-radius: var(--border-radius);
100 | color: var(--text-light);
101 | font-size: 1rem;
102 | transition: all 0.3s ease;
103 | }
104 |
105 | textarea {
106 | height: 150px;
107 | resize: none;
108 | margin-bottom: 25px;
109 | }
110 |
111 | input:focus,
112 | textarea:focus {
113 | border-color: var(--primay-color);
114 | outline: none;
115 | box-shadow: 0 0 0 3px rgba(74, 222, 128, 0.2);
116 | }
117 |
118 | input::placeholder,
119 | textarea::placeholder {
120 | color: var(--text-gray);
121 | }
122 |
123 | button {
124 | background-color: var(--primay-color);
125 | color: #000;
126 | font-weight: 600;
127 | font-size: 1rem;
128 | border: none;
129 | border-radius: var(--border-radius);
130 | padding: 16px 30px;
131 | cursor: pointer;
132 | width: 100%;
133 | transition: all 0.3s ease;
134 | }
135 |
136 | button:hover {
137 | background-color: var(--primay-dark);
138 | transform: translateY(-2px);
139 | box-shadow: 0 5px 15px rgba(74, 222, 128, 0.3);
140 | }
141 |
142 | button:active {
143 | transform: translateY(0);
144 | }
145 |
146 | .btn-content {
147 | display: flex;
148 | justify-content: center;
149 | align-items: center;
150 | gap: 10px;
151 | }
152 |
153 | .contact-info {
154 | margin-top: 40px;
155 | padding-top: 30px;
156 | border-top: 1px solid rgb(255, 255, 255, 0.1);
157 | display: flex;
158 | flex-direction: column;
159 | gap: 15px;
160 | }
161 |
162 | .info-item {
163 | display: flex;
164 | align-items: center;
165 | gap: 15px;
166 | }
167 |
168 | .info-item i {
169 | color: var(--primay-color);
170 | font-size: 1.2rem;
171 | width: 20px;
172 | }
173 |
174 | .info-item span {
175 | color: var(--text-gray);
176 | }
177 |
178 | @media (max-width: 600px) {
179 | .form-container {
180 | padding: 25px;
181 | }
182 |
183 | h1 {
184 | font-size: 1.8rem;
185 | }
186 |
187 | .input-group {
188 | grid-template-columns: 1fr;
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/10-pricing-cards/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pricing Cards
7 |
8 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
38 |
39 |
40 |
41 | 5 Projects
42 | 20GB Storage
43 | Basic Support
44 | Email Notifications
45 | Advanced Analytics
46 | Custom Domain
47 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
Most Popular
58 |
70 |
71 |
72 |
73 | 5 Projects
74 | 20GB Storage
75 | Basic Support
76 | Email Notifications
77 | Advanced Analytics
78 | Custom Domain
79 |
80 |
81 |
82 |
85 |
86 |
87 |
88 |
89 |
101 |
102 |
103 |
104 | 5 Projects
105 | 20GB Storage
106 | Basic Support
107 | Email Notifications
108 | Advanced Analytics
109 | Custom Domain
110 |
111 |
112 |
113 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | Switching to this service was one of the best decisions we've made. The pricing is
124 | transparent and the features are exactly what we needed.
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
Sarah Johnson
133 |
Marketing Director
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/10-pricing-cards/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | font-family: sans-serif;
6 | }
7 |
8 | /* CSS variables */
9 | :root {
10 | --primary-color: #4ade80;
11 | --primary-dark: #22c55e;
12 | --primary-light: #86efac;
13 | --bg-dark: #1a1a1a;
14 | --text-light: #f3f4f6;
15 | --text-gray: #9ca3af;
16 | --card-bg: #252525;
17 | --card-hover: #2e2e2e;
18 | --border-radius: 12px;
19 | --shadow-sm: 0 4px 6px rgba(0, 0, 0, 0.1);
20 | --shadow-md: 0 10px 15px rgba(0, 0, 0, 0.1);
21 | --shadow-lg: 0 20px 25px rgba(0, 0, 0, 0.15);
22 | }
23 |
24 | body {
25 | background-color: var(--bg-dark);
26 | color: var(--text-light);
27 | min-height: 100vh;
28 | display: flex;
29 | justify-content: center;
30 | align-items: center;
31 | padding: 40px 15px;
32 | line-height: 1.6;
33 | }
34 |
35 | .container {
36 | width: 100%;
37 | max-width: 1200px;
38 | margin: 0 auto;
39 | }
40 |
41 | header {
42 | text-align: center;
43 | margin-bottom: 50px;
44 | }
45 |
46 | header h1 {
47 | font-size: 2.5rem;
48 | margin-bottom: 10px;
49 | background: linear-gradient(to right, var(--primary-color), var(--primary-light));
50 | display: inline-block;
51 | background-clip: text;
52 | -webkit-background-clip: text; /* For SAFARI*/
53 | color: transparent;
54 | }
55 |
56 | header p {
57 | color: var(--text-gray);
58 | font-size: 1.1rem;
59 | max-width: 600px;
60 | margin: 0 auto;
61 | }
62 |
63 | .pricing-cards {
64 | display: grid;
65 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
66 | gap: 30px;
67 | margin-bottom: 60px;
68 | }
69 |
70 | .card {
71 | background-color: var(--card-bg);
72 | border-radius: var(--border-radius);
73 | overflow: hidden;
74 | position: relative;
75 | box-shadow: var(--shadow-md);
76 | transition: all 0.3s ease;
77 | height: 100%;
78 | }
79 |
80 | .card:hover {
81 | transform: translateY(-10px);
82 | box-shadow: var(--shadow-lg);
83 | background-color: var(--card-hover);
84 | }
85 |
86 | .card-header {
87 | padding: 30px;
88 | text-align: center;
89 | border-bottom: 1px solid rgba(255, 255, 255, 0.1);
90 | }
91 |
92 | .plan-icon {
93 | width: 70px;
94 | height: 70px;
95 | background: linear-gradient(135deg, rgba(74, 222, 128, 0.2), rgba(74, 222, 128, 0.05));
96 | border-radius: 50%;
97 | display: flex;
98 | align-items: center;
99 | justify-content: center;
100 | margin: 0 auto 20px;
101 | }
102 |
103 | .plan-icon i {
104 | font-size: 1.8rem;
105 | color: var(--primary-color);
106 | }
107 |
108 | h3 {
109 | font-size: 1.5rem;
110 | margin-bottom: 15px;
111 | }
112 |
113 | .price {
114 | margin-bottom: 15px;
115 | }
116 |
117 | .currency {
118 | font-size: 1.5rem;
119 | margin-right: 2px;
120 | color: var(--primary-color);
121 | vertical-align: top;
122 | }
123 |
124 | .amount {
125 | font-size: 3.5rem;
126 | font-weight: 700;
127 | line-height: 1;
128 | color: var(--primary-color);
129 | }
130 |
131 | .period {
132 | font-size: 1rem;
133 | color: var(--text-gray);
134 | }
135 |
136 | .description {
137 | font-size: 0.95rem;
138 | color: var(--text-gray);
139 | }
140 |
141 | .card-body {
142 | padding: 30px;
143 | flex-grow: 1;
144 | }
145 |
146 | .features {
147 | list-style: none;
148 | }
149 |
150 | .features li {
151 | margin-bottom: 15px;
152 | display: flex;
153 | align-items: center;
154 | }
155 |
156 | .features li i {
157 | margin-right: 10px;
158 | font-size: 0.9rem;
159 | }
160 |
161 | .features li i.fa-check {
162 | color: var(--primary-color);
163 | }
164 |
165 | .features li i.fa-times {
166 | color: #ef4444;
167 | }
168 |
169 | .features li.disabled {
170 | opacity: 0.7;
171 | color: var(--text-gray);
172 | }
173 |
174 | .card-footer {
175 | padding: 20px 30px 30px;
176 | }
177 |
178 | .btn {
179 | display: block;
180 | width: 100%;
181 | padding: 14px;
182 | font-size: 1rem;
183 | font-weight: 600;
184 | text-align: center;
185 | color: var(--text-light);
186 | background-color: rgba(255, 255, 255, 0.1);
187 | border: 2px solid rgba(255, 255, 255, 0.1);
188 | border-radius: var(--border-radius);
189 | cursor: pointer;
190 | transition: all 0.3s ease;
191 | }
192 |
193 | .btn:hover {
194 | border-color: var(--primary-color);
195 | background-color: rgba(74, 222, 128, 0.1);
196 | }
197 |
198 | .btn-accent {
199 | background-color: var(--primary-color);
200 | border-color: var(--primary-color);
201 | color: #000;
202 | }
203 |
204 | .btn-accent:hover {
205 | background-color: var(--primary-dark);
206 | border-color: var(--primary-dark);
207 | }
208 |
209 | .popular {
210 | transform: scale(1.05);
211 | border: 2px solid var(--primary-color);
212 | z-index: 2;
213 | }
214 |
215 | .popular:hover {
216 | transform: translateY(-10px) scale(1.05);
217 | }
218 |
219 | .popular-badge {
220 | position: absolute;
221 | top: 15px;
222 | right: 15px;
223 | background-color: var(--primary-color);
224 | color: #000;
225 | font-size: 0.8rem;
226 | font-weight: 600;
227 | padding: 5px 12px;
228 | border-radius: 20px;
229 | z-index: 3;
230 | }
231 |
232 | .testimonial {
233 | margin-top: 60px;
234 | display: flex;
235 | justify-content: center;
236 | }
237 |
238 | .quote {
239 | max-width: 700px;
240 | background-color: var(--card-bg);
241 | border-radius: var(--border-radius);
242 | padding: 40px;
243 | position: relative;
244 | box-shadow: var(--shadow-md);
245 | }
246 |
247 | .quote i {
248 | color: var(--primary-color);
249 | font-size: 2rem;
250 | opacity: 0.3;
251 | position: absolute;
252 | top: 20px;
253 | left: 20px;
254 | }
255 |
256 | .quote p {
257 | font-size: 1.1rem;
258 | margin-bottom: 20px;
259 | line-height: 1.7;
260 | position: relative;
261 | padding-left: 15px;
262 | }
263 |
264 | .author {
265 | display: flex;
266 | align-items: center;
267 | }
268 |
269 | .avatar {
270 | width: 50px;
271 | height: 50px;
272 | border-radius: 50%;
273 | overflow: hidden;
274 | margin-right: 15px;
275 | border: 2px solid var(--primary-color);
276 | }
277 |
278 | .avatar img {
279 | width: 100%;
280 | height: 100%;
281 | object-fit: cover;
282 | }
283 |
284 | .info h4 {
285 | font-size: 1.1rem;
286 | margin-bottom: 3px;
287 | }
288 |
289 | .info p {
290 | padding: 0;
291 | margin: 0;
292 | font-size: 0.9rem;
293 | color: var(--primary-color);
294 | }
295 |
296 | @media (max-width: 768px) {
297 | body {
298 | padding: 30px 15px;
299 | }
300 |
301 | header {
302 | margin-bottom: 30px;
303 | }
304 |
305 | header h1 {
306 | font-size: 2rem;
307 | }
308 |
309 | .pricing-cards {
310 | grid-template-columns: 1fr;
311 | min-width: 400px;
312 | margin: 0 auto 40px;
313 | }
314 |
315 | .card,
316 | .popular {
317 | transform: none;
318 | }
319 | .card:hover,
320 | .popular:hover {
321 | transform: translateY(-5px);
322 | }
323 |
324 | .testimonial {
325 | margin-top: 40px;
326 | }
327 |
328 | .quote {
329 | padding: 30px;
330 | }
331 | }
332 |
333 | @keyframes pulse {
334 | 0% {
335 | box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.7);
336 | }
337 |
338 | 70% {
339 | box-shadow: 0 0 0 10px rgba(74, 222, 128, 0);
340 | }
341 |
342 | 100% {
343 | box-shadow: 0 0 0 0 rgba(74, 222, 128, 0);
344 | }
345 | }
346 |
347 | .btn-accent {
348 | animation: pulse 2s infinite;
349 | }
350 |
--------------------------------------------------------------------------------
/11-team-members-showcase/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Team Members Showcase
7 |
8 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Alex Morgan
32 |
Founder & CEO
33 |
34 |
35 |
36 | Visionary leader with 15+ years of experience in tech innovation and business
37 | strategy.
38 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
Sarah Johnson
55 |
Lead Designer
56 |
57 |
58 |
59 | Award-winning designer specializing in UI/UX with a focus on creating intuitive user
60 | experiences.
61 |
62 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
David Chen
78 |
Lead Developer
79 |
80 |
81 |
82 | Full-stack developer with expertise in scalable architecture and emerging
83 | technologies.
84 |
85 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
Emily Wilson
101 |
Marketing Specialist
102 |
103 |
104 |
105 | Digital marketing expert with a knack for creating compelling content and growth
106 | strategies.
107 |
108 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
Michael Torres
124 |
Product Manager
125 |
126 |
127 |
128 | Strategic thinker focused on bringing innovative products to market that solve real
129 | problems.
130 |
131 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
Jennifer Lee
147 |
Customer Success
148 |
149 |
150 |
151 | Dedicated to ensuring our clients achieve their goals through excellent support and
152 | guidance.
153 |
154 |
159 |
160 |
161 |
162 |
163 |
164 |
Want to join our team?
165 |
We're always looking for talented individuals to join our growing team.
166 |
View Open Positions
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/11-team-members-showcase/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | box-sizing: border-box;
5 | font-family: sans-serif;
6 | }
7 |
8 | :root {
9 | --primary-color: #a78bfa;
10 | --primary-dark: #8b5cf6;
11 | --primary-light: #c4b5fd;
12 | --primary-gradient: linear-gradient(135deg, #a78bfa, #8b5cf6);
13 | --bg-dark: #1a1a1a;
14 | --text-light: #f3f4f6;
15 | --text-gray: #9ca3af;
16 | --card-bg: #252525;
17 | --card-hover: #2e2e2e;
18 | --border-radius: 12px;
19 | --shadow-sm: 0 4px 6px rgba(0, 0, 0, 0.1);
20 | --shadow-md: 0 10px 15px rgba(0, 0, 0, 0.1);
21 | --shadow-lg: 0 20px 25px rgba(0, 0, 0, 0.15);
22 | --border-light: rgba(255, 255, 255, 0.05);
23 | --bg-overlay: rgba(255, 255, 255, 0.1);
24 | --text-dark: #000;
25 | --shadow-color-primary: rgba(139, 92, 246, 0.3);
26 | --shadow-color-primary-hover: rgba(139, 92, 246, 0.4);
27 | }
28 |
29 | body {
30 | background-color: var(--bg-dark);
31 | color: var(--text-light);
32 | min-height: 100vh;
33 | display: flex;
34 | justify-content: center;
35 | align-items: center;
36 | padding: 50px 15px;
37 | line-height: 1.6;
38 | }
39 |
40 | .container {
41 | width: 100%;
42 | max-width: 1200px;
43 | margin: 0 auto;
44 | }
45 |
46 | header {
47 | text-align: center;
48 | margin-bottom: 60px;
49 | }
50 |
51 | header h1 {
52 | font-size: 2.7rem;
53 | margin-bottom: 15px;
54 | background: linear-gradient(to right, var(--primary-color), var(--primary-light));
55 | background-clip: text;
56 | -webkit-background-clip: text; /* For Safari*/
57 | color: transparent;
58 | display: inline-block;
59 | position: relative;
60 | }
61 |
62 | header h1::after {
63 | content: "";
64 | position: absolute;
65 | width: 60px;
66 | height: 4px;
67 | background: var(--primary-gradient);
68 | bottom: -10px;
69 | left: 50%;
70 | transform: translateX(-50%);
71 | border-radius: 2px;
72 | }
73 |
74 | header p {
75 | color: var(--text-gray);
76 | font-size: 1.1rem;
77 | max-width: 600px;
78 | margin: 0 auto;
79 | margin-top: 20px;
80 | }
81 |
82 | .team-grid {
83 | display: grid;
84 | grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
85 | gap: 30px;
86 | margin-bottom: 60px;
87 | }
88 |
89 | .team-card {
90 | background-color: var(--card-bg);
91 | border-radius: var(--border-radius);
92 | overflow: hidden;
93 | transition: all 0.3s ease;
94 | box-shadow: var(--shadow-md);
95 | height: 100%;
96 | position: relative;
97 | display: flex;
98 | flex-direction: column;
99 | }
100 |
101 | .team-card:hover {
102 | transform: translateY(-8px);
103 | box-shadow: var(--shadow-lg);
104 | }
105 |
106 | .team-card:hover .profile-bg {
107 | transform: scale(1.1);
108 | }
109 |
110 | .card-top {
111 | position: relative;
112 | padding-top: 70px;
113 | padding-bottom: 30px;
114 | text-align: center;
115 | z-index: 1;
116 | }
117 |
118 | .profile-bg {
119 | position: absolute;
120 | top: 0;
121 | left: 0;
122 | right: 0;
123 | height: 120px;
124 | background: var(--primary-gradient);
125 | z-index: -1;
126 | transition: transform 0.4s ease;
127 | }
128 |
129 | .profile-img {
130 | width: 100px;
131 | height: 100px;
132 | border-radius: 50%;
133 | overflow: hidden;
134 | margin: 0 auto 20px;
135 | border: 4px solid var(--card-bg);
136 | position: relative;
137 | }
138 |
139 | .profile-img img {
140 | width: 100%;
141 | height: 100%;
142 | object-fit: cover;
143 | transition: transform 0.3s ease;
144 | }
145 |
146 | .team-card:hover .profile-img img {
147 | transform: scale(1.1);
148 | }
149 |
150 | .card-top h3 {
151 | font-size: 1.4rem;
152 | margin-bottom: 5px;
153 | font-weight: 600;
154 | }
155 |
156 | .role {
157 | color: var(--primary-color);
158 | font-size: 0.95rem;
159 | font-weight: 500;
160 | }
161 |
162 | .card-bottom {
163 | padding: 20px 25px 25px;
164 | flex-grow: 1;
165 | display: flex;
166 | flex-direction: column;
167 | justify-content: space-between;
168 | border-top: 1px solid var(--border-light);
169 | }
170 |
171 | .bio {
172 | color: var(--text-gray);
173 | font-size: 0.95rem;
174 | margin-bottom: 25px;
175 | flex-grow: 1;
176 | line-height: 1.6;
177 | }
178 |
179 | .social-links {
180 | display: flex;
181 | gap: 15px;
182 | justify-content: center;
183 | }
184 |
185 | .social-icon {
186 | display: flex;
187 | justify-content: center;
188 | align-items: center;
189 | width: 36px;
190 | height: 36px;
191 | border-radius: 50%;
192 | text-decoration: none;
193 | background-color: var(--bg-overlay);
194 | color: var(--text-light);
195 | transition: all 0.3s ease;
196 | }
197 |
198 | .social-icon:hover {
199 | background-color: var(--primary-color);
200 | color: var(--text-dark);
201 | transform: translateY(-3px);
202 | }
203 |
204 | .team-cta {
205 | text-align: center;
206 | margin-top: 30px;
207 | padding: 40px;
208 | background-color: var(--bg-overlay);
209 | border-radius: var(--border-radius);
210 | position: relative;
211 | overflow: hidden;
212 | box-shadow: var(--shadow-md);
213 | }
214 |
215 | .team-cta::before {
216 | content: "";
217 | position: absolute;
218 | top: 0;
219 | left: 0;
220 | background: var(--primary-gradient);
221 | width: 100%;
222 | height: 5px;
223 | }
224 |
225 | .team-cta h2 {
226 | font-size: 1.8rem;
227 | margin-bottom: 15px;
228 | color: var(--primary-light);
229 | }
230 |
231 | .team-cta p {
232 | color: var(--text-gray);
233 | max-width: 500px;
234 | margin: 0 auto 25px;
235 | }
236 |
237 | .cta-button {
238 | display: inline-block;
239 | padding: 12px 25px;
240 | background: var(--primary-gradient);
241 | text-decoration: none;
242 | color: var(--text-dark);
243 | font-weight: 600;
244 | font-size: 1rem;
245 | border-radius: 30px;
246 | transition: all 0.3s ease-in-out;
247 | box-shadow: 0 5px 15px var(--shadow-color-primary);
248 | }
249 |
250 | .cta-button:hover {
251 | transform: translateY(-3px);
252 | box-shadow: 0 8px 20px var(--shadow-color-primary-hover);
253 | }
254 |
255 | @media (max-width: 768px) {
256 | header {
257 | margin-bottom: 40px;
258 | }
259 |
260 | header h1 {
261 | font-size: 2.2rem;
262 | }
263 |
264 | .team-grid {
265 | gap: 20px;
266 | }
267 |
268 | .team-cta {
269 | padding: 30px 20px;
270 | }
271 |
272 | .team-cta h2 {
273 | font-size: 1.5rem;
274 | }
275 | }
276 |
277 | @media (max-width: 480px) {
278 | body {
279 | padding: 30px 15px;
280 | }
281 |
282 | header h1 {
283 | font-size: 1.9rem;
284 | }
285 |
286 | .profile-img {
287 | width: 90px;
288 | height: 90px;
289 | }
290 |
291 | .card-top h3 {
292 | font-size: 1.3rem;
293 | }
294 |
295 | .team-cta {
296 | padding: 25px 15px;
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/12-recipe-finder/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Recipe Finder App
7 |
8 |
15 |
16 |
17 |
18 |
22 |
23 |
Data from TheMealDB 🧑🍳
24 |
25 |
26 |
27 | Search
28 |
29 |
30 |
31 |
No meals found. Try another search term!
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Back to recipes
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/12-recipe-finder/script.js:
--------------------------------------------------------------------------------
1 | // DOM Elements
2 | const searchInput = document.getElementById("search-input");
3 | const searchBtn = document.getElementById("search-btn");
4 | const mealsContainer = document.getElementById("meals");
5 | const resultHeading = document.getElementById("result-heading");
6 | const errorContainer = document.getElementById("error-container");
7 | const mealDetails = document.getElementById("meal-details");
8 | const mealDetailsContent = document.querySelector(".meal-details-content");
9 | const backBtn = document.getElementById("back-btn");
10 |
11 | const BASE_URL = "https://www.themealdb.com/api/json/v1/1/";
12 | const SEARCH_URL = `${BASE_URL}search.php?s=`;
13 | const LOOKUP_URL = `${BASE_URL}lookup.php?i=`;
14 |
15 | searchBtn.addEventListener("click", searchMeals);
16 |
17 | mealsContainer.addEventListener("click", handleMealClick);
18 |
19 | backBtn.addEventListener("click", () => mealDetails.classList.add("hidden"));
20 |
21 | searchInput.addEventListener("keypress", (e) => {
22 | if (e.key === "Enter") searchMeals();
23 | });
24 |
25 | async function searchMeals() {
26 | const searchTerm = searchInput.value.trim();
27 |
28 | // handled the edge case
29 | if (!searchTerm) {
30 | errorContainer.textContent = "Please enter a search term";
31 | errorContainer.classList.remove("hidden");
32 | return;
33 | }
34 |
35 | try {
36 | resultHeading.textContent = `Searching for "${searchTerm}"...`;
37 | mealsContainer.innerHTML = "";
38 | errorContainer.classList.add("hidden");
39 |
40 | // fetch meals from API
41 | // www.themealdb.com/api/json/v1/1/search.php?s=chicken
42 | const response = await fetch(`${SEARCH_URL}${searchTerm}`);
43 | const data = await response.json();
44 |
45 | if (data.meals === null) {
46 | // no meals found
47 | resultHeading.textContent = ``;
48 | mealsContainer.innerHTML = "";
49 | errorContainer.textContent = `No recipes found for "${searchTerm}". Try another search term!`;
50 | errorContainer.classList.remove("hidden");
51 | } else {
52 | resultHeading.textContent = `Search results for "${searchTerm}":`;
53 | displayMeals(data.meals);
54 | searchInput.value = "";
55 | }
56 | } catch (error) {
57 | errorContainer.textContent = "Something went wrong. Please try again later.";
58 | errorContainer.classList.remove("hidden");
59 | }
60 | }
61 |
62 | function displayMeals(meals) {
63 | mealsContainer.innerHTML = "";
64 |
65 | // loop through meals and create a card for each meal
66 | meals.forEach((meal) => {
67 | mealsContainer.innerHTML += `
68 |
69 |
70 |
71 |
${meal.strMeal}
72 | ${meal.strCategory ? `
${meal.strCategory}
` : ""}
73 |
74 |
75 | `;
76 | });
77 | }
78 |
79 | async function handleMealClick(e) {
80 | const mealEl = e.target.closest(".meal");
81 | if (!mealEl) return;
82 |
83 | const mealId = mealEl.getAttribute("data-meal-id");
84 |
85 | try {
86 | const response = await fetch(`${LOOKUP_URL}${mealId}`);
87 | const data = await response.json();
88 |
89 | if (data.meals && data.meals[0]) {
90 | const meal = data.meals[0];
91 |
92 | const ingredients = [];
93 |
94 | for (let i = 1; i <= 20; i++) {
95 | if (meal[`strIngredient${i}`] && meal[`strIngredient${i}`].trim() !== "") {
96 | ingredients.push({
97 | ingredient: meal[`strIngredient${i}`],
98 | measure: meal[`strMeasure${i}`],
99 | });
100 | }
101 | }
102 |
103 | // display meal details
104 | mealDetailsContent.innerHTML = `
105 |
106 | ${meal.strMeal}
107 |
108 | ${meal.strCategory || "Uncategorized"}
109 |
110 |
111 |
Instructions
112 |
${meal.strInstructions}
113 |
114 |
115 |
Ingredients
116 |
117 | ${ingredients
118 | .map(
119 | (item) => `
120 | ${item.measure} ${item.ingredient}
121 | `
122 | )
123 | .join("")}
124 |
125 |
126 | ${
127 | meal.strYoutube
128 | ? `
129 |
130 | Watch Video
131 |
132 | `
133 | : ""
134 | }
135 | `;
136 |
137 | mealDetails.classList.remove("hidden");
138 | mealDetails.scrollIntoView({ behavior: "smooth" });
139 | }
140 | } catch (error) {
141 | errorContainer.textContent = "Could not load recipe details. Please try again later.";
142 | errorContainer.classList.remove("hidden");
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/12-recipe-finder/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | /* Color Palette */
8 | :root {
9 | --primary: #ff7e5f;
10 | --primary-dark: #eb5e41;
11 | --primary-light: #ffb199;
12 | --secondary: #0ba360;
13 | --text-dark: #333333;
14 | --text-light: #f8f9fa;
15 | --background: #ffffff;
16 | --background-light: #f8f9fa;
17 | --card-bg: #ffffff;
18 | --border-radius: 8px;
19 | --shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
20 | --transition: all 0.3s ease;
21 | }
22 |
23 | body {
24 | font-family: sans-serif;
25 | background-color: var(--background-light);
26 | color: var(--text-dark);
27 | line-height: 1.6;
28 | }
29 |
30 | .container {
31 | width: 100%;
32 | max-width: 1000px;
33 | margin: 0 auto;
34 | padding: 2rem 1rem;
35 | }
36 |
37 | header {
38 | text-align: center;
39 | margin-bottom: 2rem;
40 | }
41 |
42 | h1 {
43 | font-size: 2.2rem;
44 | color: var(--primary);
45 | margin-bottom: 0.5rem;
46 | }
47 |
48 | h1 i {
49 | margin-right: 10px;
50 | }
51 |
52 | header p {
53 | color: var(--text-dark);
54 | opacity: 0.7;
55 | }
56 |
57 | .search-container {
58 | display: flex;
59 | margin-bottom: 2rem;
60 | gap: 10px;
61 | max-width: 500px;
62 | margin-left: auto;
63 | margin-right: auto;
64 | }
65 |
66 | input[type="text"] {
67 | flex: 1;
68 | padding: 10px 16px;
69 | font-size: 1rem;
70 | border-radius: var(--border-radius);
71 | border: 1px solid #ddd;
72 | background-color: var(--background);
73 | color: var(--text-dark);
74 | outline: none;
75 | }
76 |
77 | input[type="text"]:focus {
78 | border-color: var(--primary);
79 | }
80 |
81 | button {
82 | background-color: var(--primary);
83 | color: white;
84 | border: none;
85 | border-radius: var(--border-radius);
86 | padding: 0 20px;
87 | font-size: 1rem;
88 | font-weight: 600;
89 | cursor: pointer;
90 | transition: var(--transition);
91 | }
92 |
93 | button:hover {
94 | background-color: var(--primary-dark);
95 | }
96 |
97 | #result-heading {
98 | text-align: center;
99 | margin-bottom: 1.5rem;
100 | font-size: 1.2rem;
101 | color: var(--text-dark);
102 | }
103 |
104 | .meals-container {
105 | display: grid;
106 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
107 | gap: 1.5rem;
108 | }
109 |
110 | .meal {
111 | background-color: var(--card-bg);
112 | border-radius: var(--border-radius);
113 | box-shadow: var(--shadow);
114 | overflow: hidden;
115 | transition: var(--transition);
116 | cursor: pointer;
117 | position: relative;
118 | }
119 |
120 | .meal:hover {
121 | transform: translateY(-5px);
122 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
123 | }
124 |
125 | .meal img {
126 | width: 100%;
127 | height: 180px;
128 | object-fit: cover;
129 | }
130 |
131 | .meal-info {
132 | padding: 1rem;
133 | }
134 |
135 | .meal-title {
136 | font-size: 1.1rem;
137 | margin-bottom: 0.5rem;
138 | color: var(--text-dark);
139 | }
140 |
141 | .meal-category {
142 | display: inline-block;
143 | background-color: var(--primary-light);
144 | color: var(--text-dark);
145 | padding: 3px 8px;
146 | font-size: 0.8rem;
147 | border-radius: 20px;
148 | margin-bottom: 10px;
149 | }
150 |
151 | #meal-details {
152 | background-color: var(--background);
153 | border-radius: var(--border-radius);
154 | box-shadow: var(--shadow);
155 | padding: 1.5rem;
156 | margin: 2rem 0;
157 | }
158 |
159 | #back-btn {
160 | margin-bottom: 1.5rem;
161 | background-color: transparent;
162 | color: var(--primary);
163 | border: 1px solid var(--primary);
164 | padding: 8px 16px;
165 | }
166 |
167 | #back-btn:hover {
168 | background-color: var(--primary);
169 | color: white;
170 | }
171 |
172 | .meal-details-content {
173 | display: flex;
174 | flex-direction: column;
175 | align-items: center;
176 | }
177 |
178 | .meal-details-img {
179 | width: 100%;
180 | max-width: 400px;
181 | border-radius: var(--border-radius);
182 | margin-bottom: 1.5rem;
183 | }
184 |
185 | .meal-details-title {
186 | font-size: 1.8rem;
187 | color: var(--primary);
188 | margin-bottom: 0.5rem;
189 | text-align: center;
190 | }
191 |
192 | .meal-details-category {
193 | margin-bottom: 1rem;
194 | text-align: center;
195 | }
196 |
197 | .meal-details-category span {
198 | background-color: var(--primary-light);
199 | padding: 4px 12px;
200 | border-radius: 20px;
201 | font-size: 0.9rem;
202 | }
203 |
204 | .meal-details-instructions {
205 | line-height: 1.7;
206 | margin-bottom: 1.5rem;
207 | }
208 |
209 | .meal-details-instructions h3 {
210 | margin-bottom: 0.5rem;
211 | }
212 |
213 | .ingredients-list {
214 | list-style-type: none;
215 | display: grid;
216 | grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
217 | gap: 8px;
218 | margin-bottom: 1.5rem;
219 | width: 100%;
220 | }
221 |
222 | .ingredients-list li {
223 | display: flex;
224 | align-items: center;
225 | padding: 6px 10px;
226 | background-color: var(--background-light);
227 | border-radius: var(--border-radius);
228 | font-size: 0.9rem;
229 | }
230 |
231 | .ingredients-list li i {
232 | color: var(--secondary);
233 | margin-right: 8px;
234 | }
235 |
236 | .youtube-link {
237 | display: inline-block;
238 | background-color: #ff0000;
239 | color: white;
240 | padding: 8px 16px;
241 | border-radius: 4px;
242 | text-decoration: none;
243 | font-weight: 600;
244 | margin-top: 1rem;
245 | font-size: 0.9rem;
246 | }
247 |
248 | .youtube-link i {
249 | margin-right: 8px;
250 | }
251 |
252 | #error-container {
253 | background-color: rgba(255, 126, 95, 0.1);
254 | border: 1px solid var(--primary);
255 | color: var(--primary-dark);
256 | padding: 1rem;
257 | border-radius: var(--border-radius);
258 | text-align: center;
259 | margin-bottom: 1.5rem;
260 | }
261 |
262 | .api-link {
263 | text-align: center;
264 | padding-bottom: 1.5rem;
265 | color: var(--text-dark);
266 | opacity: 0.7;
267 | font-size: 0.9rem;
268 | }
269 |
270 | .api-link a {
271 | color: var(--primary);
272 | text-decoration: none;
273 | }
274 |
275 | .hidden {
276 | display: none;
277 | }
278 |
279 | @media (max-width: 600px) {
280 | .search-container {
281 | flex-direction: column;
282 | }
283 |
284 | button#search-btn {
285 | width: 100%;
286 | padding: 10px;
287 | }
288 |
289 | .meals-container {
290 | grid-template-columns: 1fr;
291 | }
292 |
293 | .meal-details-img {
294 | max-width: 100%;
295 | }
296 |
297 | .ingredients-list {
298 | grid-template-columns: 1fr;
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/13-currency-converter/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Curreny Converter App
7 |
8 |
12 |
13 |
14 |
15 |
Currency Converter
16 |
17 |
18 |
19 | Amount
20 |
21 |
22 |
23 |
24 | From
25 |
26 |
27 |
28 |
29 |
30 |
31 | To
32 |
33 |
34 |
35 |
36 |
37 | Convert
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/13-currency-converter/script.js:
--------------------------------------------------------------------------------
1 | const converterForm = document.getElementById("converter-form");
2 | const fromCurrency = document.getElementById("from-currency");
3 | const toCurrency = document.getElementById("to-currency");
4 | const amountInput = document.getElementById("amount");
5 | const resultDiv = document.getElementById("result");
6 |
7 | window.addEventListener("load", fetchCurrencies);
8 |
9 | converterForm.addEventListener("submit", convertCurrency);
10 |
11 | async function fetchCurrencies() {
12 | // https://api.exchangerate-api.com/v4/latest/USD
13 | const response = await fetch("https://api.exchangerate-api.com/v4/latest/USD");
14 | const data = await response.json();
15 |
16 | console.log(data);
17 | const currencyOptions = Object.keys(data.rates);
18 |
19 | currencyOptions.forEach((currency) => {
20 | const option1 = document.createElement("option");
21 | option1.value = currency;
22 | option1.textContent = currency;
23 | fromCurrency.appendChild(option1);
24 |
25 | const option2 = document.createElement("option");
26 | option2.value = currency;
27 | option2.textContent = currency;
28 | toCurrency.appendChild(option2);
29 | });
30 | }
31 |
32 | async function convertCurrency(e) {
33 | e.preventDefault();
34 |
35 | const amount = parseFloat(amountInput.value);
36 | const fromCurrencyValue = fromCurrency.value;
37 | const toCurrencyValue = toCurrency.value;
38 |
39 | if (amount < 0) {
40 | alert("Please ener a valid amount");
41 | return;
42 | }
43 |
44 | const response = await fetch(`https://api.exchangerate-api.com/v4/latest/${fromCurrencyValue}`);
45 | const data = await response.json();
46 |
47 | const rate = data.rates[toCurrencyValue];
48 | const convertedAmount = (amount * rate).toFixed(2);
49 |
50 | resultDiv.textContent = `${amount} ${fromCurrencyValue} = ${convertedAmount} ${toCurrencyValue}`;
51 | }
52 |
--------------------------------------------------------------------------------
/13-currency-converter/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | --bg-primary: #0f172a;
9 | --bg-secondary: #1e293b;
10 | --bg-input: #334155;
11 | --text-primary: #e2e8f0;
12 | --text-secondary: #94a3b8;
13 | --border-color: #475569;
14 | --accent-primary: #3b82f6;
15 | --accent-hover: #2563eb;
16 | }
17 |
18 | body {
19 | background-color: var(--bg-primary);
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 | min-height: 100vh;
24 | padding: 20px;
25 | font-family: "Poppins", sans-serif;
26 | }
27 |
28 | .container {
29 | width: 100%;
30 | max-width: 400px;
31 | background-color: var(--bg-secondary);
32 | padding: 30px;
33 | border-radius: 15px;
34 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
35 | }
36 |
37 | h1 {
38 | text-align: center;
39 | color: var(--text-primary);
40 | margin-bottom: 20px;
41 | }
42 |
43 | .form-group {
44 | margin-bottom: 15px;
45 | }
46 |
47 | label {
48 | display: block;
49 | margin-bottom: 5px;
50 | color: var(--text-secondary);
51 | }
52 |
53 | input,
54 | select {
55 | width: 100%;
56 | padding: 10px;
57 | background-color: var(--bg-input);
58 | border: 1px solid var(--border-color);
59 | border-radius: 5px;
60 | font-size: 1rem;
61 | color: var(--text-primary);
62 | }
63 |
64 | input:focus,
65 | select:focus {
66 | outline: none;
67 | border-color: var(--accent-primary);
68 | box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
69 | }
70 |
71 | button {
72 | width: 100%;
73 | padding: 12px;
74 | background-color: var(--accent-primary);
75 | color: #fff;
76 | border: none;
77 | border-radius: 5px;
78 | font-size: 1rem;
79 | cursor: pointer;
80 | transition: all 0.3s ease;
81 | }
82 |
83 | button:hover {
84 | background-color: var(--accent-hover);
85 | transform: translateY(-2px);
86 | }
87 |
88 | #result {
89 | margin-top: 20px;
90 | text-align: center;
91 | font-size: 1.2rem;
92 | color: var(--text-primary);
93 | }
94 |
--------------------------------------------------------------------------------
/14-github-finder/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Github User Finder
7 |
8 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
30 | Search
31 |
32 |
33 |
34 |
54 |
55 |
56 |
57 |
58 | followers
59 |
60 |
61 |
62 |
63 | following
64 |
65 |
66 |
67 | repositories
68 |
69 |
70 |
71 |
72 |
73 |
74 | Not specified
75 |
76 |
77 |
78 |
No website
79 |
80 |
84 |
85 |
86 |
87 |
Latest Repositories
88 |
89 |
90 |
91 |
Loading repositories...
92 |
93 |
94 |
95 |
96 |
97 |
No user found. Please try another username.
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/14-github-finder/script.js:
--------------------------------------------------------------------------------
1 | const searchInput = document.getElementById("search");
2 | const searchBtn = document.getElementById("search-btn");
3 | const profileContainer = document.getElementById("profile-container");
4 | const errorContainer = document.getElementById("error-container");
5 | const avatar = document.getElementById("avatar");
6 | const nameElement = document.getElementById("name");
7 | const usernameElement = document.getElementById("username");
8 | const bioElement = document.getElementById("bio");
9 | const locationElement = document.getElementById("location");
10 | const joinedDateElement = document.getElementById("joined-date");
11 | const profileLink = document.getElementById("profile-link");
12 | const followers = document.getElementById("followers");
13 | const following = document.getElementById("following");
14 | const repos = document.getElementById("repos");
15 | const companyElement = document.getElementById("company");
16 | const blogElement = document.getElementById("blog");
17 | const twitterElement = document.getElementById("twitter");
18 | const companyContainer = document.getElementById("company-container");
19 | const blogContainer = document.getElementById("blog-container");
20 | const twitterContainer = document.getElementById("twitter-container");
21 | const reposContainer = document.getElementById("repos-container");
22 |
23 | searchBtn.addEventListener("click", searchUser);
24 | searchInput.addEventListener("keypress", (e) => {
25 | if (e.key === "Enter") searchUser();
26 | });
27 |
28 | async function searchUser() {
29 | const username = searchInput.value.trim();
30 |
31 | if (!username) return alert("Please enter a username");
32 |
33 | try {
34 | // reset the ui
35 | profileContainer.classList.add("hidden");
36 | errorContainer.classList.add("hidden");
37 |
38 | // https://api.github.com/users/burakorkmez
39 | const response = await fetch(`https://api.github.com/users/${username}`);
40 | if (!response.ok) throw new Error("User not found");
41 |
42 | const userData = await response.json();
43 | console.log("user data is here", userData);
44 |
45 | displayUserData(userData);
46 |
47 | fetchRepositories(userData.repos_url);
48 | } catch (error) {
49 | showError();
50 | }
51 | }
52 |
53 | async function fetchRepositories(reposUrl) {
54 | reposContainer.innerHTML = 'Loading repositories...
';
55 |
56 | try {
57 | const response = await fetch(reposUrl + "?per_page=6");
58 | const repos = await response.json();
59 | displayRepos(repos);
60 | } catch (error) {
61 | reposContainer.innerHTML = `${error.message}
`;
62 | }
63 | }
64 |
65 | function displayRepos(repos) {
66 | if (repos.length === 0) {
67 | reposContainer.innerHTML = 'No repositories found
';
68 | return;
69 | }
70 |
71 | reposContainer.innerHTML = "";
72 |
73 | repos.forEach((repo) => {
74 | const repoCard = document.createElement("div");
75 | repoCard.className = "repo-card";
76 |
77 | const updatedAt = formatDate(repo.updated_at);
78 |
79 | repoCard.innerHTML = `
80 |
81 | ${repo.name}
82 |
83 | ${repo.description || "No description available"}
84 |
104 | `;
105 |
106 | reposContainer.appendChild(repoCard);
107 | });
108 | }
109 |
110 | function displayUserData(user) {
111 | avatar.src = user.avatar_url;
112 | nameElement.textContent = user.name || user.login;
113 | usernameElement.textContent = `@${user.login}`;
114 | bioElement.textContent = user.bio || "No bio available";
115 |
116 | locationElement.textContent = user.location || "Not specified";
117 | joinedDateElement.textContent = formatDate(user.created_at);
118 |
119 | profileLink.href = user.html_url;
120 | followers.textContent = user.followers;
121 | following.textContent = user.following;
122 | repos.textContent = user.public_repos;
123 |
124 | if (user.company) companyElement.textContent = user.company;
125 | else companyElement.textContent = "Not specified";
126 |
127 | if (user.blog) {
128 | blogElement.textContent = user.blog;
129 | blogElement.href = user.blog.startsWith("http") ? user.blog : `https://${user.blog}`;
130 | } else {
131 | blogElement.textContent = "No website";
132 | blogElement.href = "#";
133 | }
134 |
135 | blogContainer.style.display = "flex";
136 |
137 | if (user.twitter_username) {
138 | twitterElement.textContent = `@${user.twitter_username}`;
139 | twitterElement.href = `https://twitter.com/${user.twitter_username}`;
140 | } else {
141 | twitterElement.textContent = "No Twitter";
142 | twitterElement.href = "#";
143 | }
144 |
145 | twitterContainer.style.display = "flex";
146 |
147 | // show the profile
148 | profileContainer.classList.remove("hidden");
149 | }
150 |
151 | function showError() {
152 | errorContainer.classList.remove("hidden");
153 | profileContainer.classList.add("hidden");
154 | }
155 |
156 | function formatDate(dateString) {
157 | return new Date(dateString).toLocaleDateString("en-US", {
158 | year: "numeric",
159 | month: "short",
160 | day: "numeric",
161 | });
162 | }
163 |
164 | searchInput.value = "burakorkmez";
165 | searchUser();
166 |
--------------------------------------------------------------------------------
/14-github-finder/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | --primary-color: #a78bfa;
9 | --primary-dark: #8b5cf6;
10 | --primary-light: #c4b5fd;
11 | --bg-dark: #1a1a1a;
12 | --card-bg: #252525;
13 | --text-light: #f3f4f6;
14 | --text-gray: #9ca3af;
15 | --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
16 | --border-radius: 10px;
17 | }
18 |
19 | body {
20 | background-color: var(--bg-dark);
21 | color: var(--text-light);
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | min-height: 100vh;
26 | padding: 30px 15px;
27 | font-family: sans-serif;
28 | line-height: 1.6;
29 | }
30 |
31 | .container {
32 | width: 100%;
33 | max-width: 800px;
34 | }
35 |
36 | header {
37 | text-align: center;
38 | margin-bottom: 30px;
39 | }
40 |
41 | h1 {
42 | font-size: 2.2rem;
43 | margin-bottom: 10px;
44 | color: var(--primary-color);
45 | }
46 |
47 | header p {
48 | color: var(--text-gray);
49 | font-size: 1rem;
50 | }
51 |
52 | .search-container {
53 | display: flex;
54 | margin-bottom: 30px;
55 | gap: 10px;
56 | }
57 |
58 | input[type="text"] {
59 | flex: 1;
60 | padding: 12px 16px;
61 | font-size: 1rem;
62 | border-radius: var(--border-radius);
63 | border: none;
64 | background-color: var(--card-bg);
65 | color: var(--text-light);
66 | outline: none;
67 | }
68 |
69 | input[type="text"]:focus {
70 | box-shadow: 0 0 0 2px var(--primary-color);
71 | }
72 |
73 | button {
74 | background-color: var(--primary-color);
75 | color: #000;
76 | border: none;
77 | border-radius: var(--border-radius);
78 | padding: 0 20px;
79 | font-size: 1rem;
80 | font-weight: 600;
81 | cursor: pointer;
82 | transition: background-color 0.3s;
83 | }
84 |
85 | button:hover {
86 | background-color: var(--primary-dark);
87 | }
88 |
89 | #profile-container {
90 | background-color: var(--card-bg);
91 | border-radius: var(--border-radius);
92 | padding: 25px;
93 | box-shadow: var(--shadow-md);
94 | margin-bottom: 20px;
95 | }
96 |
97 | .profile-header {
98 | display: flex;
99 | align-items: flex-start;
100 | gap: 25px;
101 | margin-bottom: 25px;
102 | }
103 |
104 | #avatar {
105 | width: 90px;
106 | height: 90px;
107 | border-radius: 50%;
108 | object-fit: cover;
109 | border: 3px solid var(--primary-color);
110 | }
111 |
112 | .profile-info {
113 | flex: 1;
114 | }
115 |
116 | #name {
117 | font-size: 1.5rem;
118 | margin-bottom: 5px;
119 | }
120 |
121 | #username {
122 | color: var(--primary-color);
123 | margin-bottom: 10px;
124 | }
125 |
126 | .bio {
127 | color: var(--text-gray);
128 | margin-bottom: 15px;
129 | }
130 |
131 | .location-date {
132 | display: flex;
133 | gap: 20px;
134 | margin-bottom: 15px;
135 | color: var(--text-gray);
136 | font-size: 0.9rem;
137 | }
138 |
139 | .location-date i {
140 | color: var(--primary-color);
141 | margin-right: 5px;
142 | }
143 |
144 | .btn {
145 | display: inline-block;
146 | padding: 8px 16px;
147 | background-color: var(--primary-color);
148 | color: #000;
149 | text-decoration: none;
150 | border-radius: 20px;
151 | font-weight: 600;
152 | font-size: 0.9rem;
153 | transition: background-color 0.3s;
154 | }
155 |
156 | .btn:hover {
157 | background-color: var(--primary-dark);
158 | }
159 |
160 | /* Stats */
161 | .stats {
162 | display: flex;
163 | gap: 15px;
164 | justify-content: space-between;
165 | }
166 |
167 | .stat {
168 | background-color: rgba(255, 255, 255, 0.05);
169 | border-radius: var(--border-radius);
170 | padding: 12px;
171 | text-align: center;
172 | flex: 1;
173 | }
174 |
175 | .stat i {
176 | color: var(--primary-color);
177 | margin-right: 5px;
178 | }
179 |
180 | #error-container {
181 | background-color: rgba(239, 68, 68, 0.2);
182 | color: #ef4444;
183 | padding: 15px;
184 | border-radius: var(--border-radius);
185 | text-align: center;
186 | }
187 |
188 | .hidden {
189 | display: none;
190 | }
191 |
192 | .additional-info {
193 | margin-top: 25px;
194 | display: grid;
195 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
196 | gap: 15px;
197 | border-top: 1px solid rgba(255, 255, 255, 0.05);
198 | padding-top: 20px;
199 | }
200 |
201 | .info-item {
202 | display: flex;
203 | align-items: center;
204 | gap: 10px;
205 | color: var(--text-gray);
206 | font-size: 0.9rem;
207 | }
208 |
209 | .info-item i {
210 | color: var(--primary-color);
211 | width: 20px;
212 | text-align: center;
213 | }
214 |
215 | .info-item a {
216 | color: var(--text-gray);
217 | text-decoration: none;
218 | word-break: break-all;
219 | }
220 |
221 | .info-item a:hover {
222 | color: var(--primary-light);
223 | text-decoration: underline;
224 | }
225 |
226 | .repos-section {
227 | margin-top: 30px;
228 | }
229 |
230 | .repos-section h3 {
231 | font-size: 1.2rem;
232 | margin-bottom: 15px;
233 | color: var(--text-light);
234 | padding-bottom: 10px;
235 | border-bottom: 1px solid rgba(255, 255, 255, 0.05);
236 | }
237 |
238 | .repos-container {
239 | display: grid;
240 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
241 | gap: 15px;
242 | }
243 |
244 | .repo-card {
245 | background-color: rgba(255, 255, 255, 0.03);
246 | border-radius: var(--border-radius);
247 | padding: 15px;
248 | border: 1px solid rgba(255, 255, 255, 0.05);
249 | transition: transform 0.2s, background-color 0.2s;
250 | }
251 |
252 | .repo-card:hover {
253 | transform: translateY(-3px);
254 | background-color: rgba(255, 255, 255, 0.05);
255 | }
256 |
257 | .repo-name {
258 | font-weight: 600;
259 | margin-bottom: 8px;
260 | color: var(--primary-light);
261 | text-decoration: none;
262 | display: block;
263 | }
264 |
265 | .repo-name:hover {
266 | text-decoration: underline;
267 | }
268 |
269 | .repo-description {
270 | color: var(--text-gray);
271 | font-size: 0.9rem;
272 | margin-bottom: 12px;
273 | overflow: hidden;
274 | min-height: 35px;
275 | }
276 |
277 | .repo-meta {
278 | display: flex;
279 | gap: 15px;
280 | font-size: 0.85rem;
281 | color: var(--text-gray);
282 | }
283 |
284 | .repo-meta-item {
285 | display: flex;
286 | align-items: center;
287 | gap: 5px;
288 | }
289 |
290 | .repo-meta-item i {
291 | color: var(--primary-color);
292 | }
293 |
294 | .loading-repos {
295 | color: var(--text-gray);
296 | text-align: center;
297 | padding: 20px;
298 | grid-column: 1 / -1;
299 | }
300 |
301 | .no-repos {
302 | color: var(--text-gray);
303 | text-align: center;
304 | padding: 30px;
305 | grid-column: 1 / -1;
306 | border: 1px dashed rgba(255, 255, 255, 0.1);
307 | border-radius: var(--border-radius);
308 | }
309 |
310 | /* Responsive design for mobile devices */
311 | @media (max-width: 600px) {
312 | .profile-header {
313 | flex-direction: column;
314 | align-items: center;
315 | text-align: center;
316 | }
317 |
318 | .location-date {
319 | justify-content: center;
320 | flex-direction: column;
321 | gap: 5px;
322 | }
323 |
324 | .stats,
325 | .additional-info {
326 | flex-direction: column;
327 | grid-template-columns: 1fr;
328 | }
329 |
330 | .info-item {
331 | justify-content: center;
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/15-404-page/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/html-css-js-projects/d3ab2ffa9d3b8d794fb98ed25f1b14ad14b2e236/15-404-page/404.png
--------------------------------------------------------------------------------
/15-404-page/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Custom 404 Page
7 |
8 |
9 |
10 |
11 | Oops! Page not found 👀
12 | Looks like you’re fishing in the wrong hole. Let’s head back!
13 | Back to home
14 |
15 |
16 |
--------------------------------------------------------------------------------
/15-404-page/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | background-color: #111;
9 | color: #fff;
10 | display: flex;
11 | flex-direction: column;
12 | justify-content: center;
13 | align-items: center;
14 | height: 100vh;
15 | font-family: sans-serif;
16 | }
17 |
18 | img {
19 | width: 100%;
20 | max-width: 550px;
21 | margin-bottom: 1.5rem;
22 | }
23 |
24 | h1 {
25 | font-size: 2rem;
26 | margin-bottom: 1rem;
27 | }
28 |
29 | p {
30 | font-size: 1rem;
31 | color: #aaa;
32 | margin-bottom: 2rem;
33 | }
34 |
35 | a {
36 | background-color: #3ecf8e;
37 | color: #111;
38 | text-decoration: none;
39 | padding: 0.75rem 1.5rem;
40 | border-radius: 8px;
41 | font-weight: bold;
42 | transition: background-color 0.3s;
43 | }
44 |
45 | a:hover {
46 | background-color: #04fc8d;
47 | }
48 |
49 | @media (max-width: 600px) {
50 | img {
51 | max-width: 400px;
52 | }
53 |
54 | h1 {
55 | font-size: 1.5rem;
56 | }
57 |
58 | p {
59 | font-size: 0.9rem;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/16-newsletter-signup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Newsletter UI
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Join Our Newsletter
22 |
Get the latest updates and coding tips straight to your inbox.
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Subscribe
31 |
32 |
33 |
34 |
Weekly coding tips
35 |
No spam, unsubscribe anytime
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/16-newsletter-signup/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | :root {
8 | --bg-color: #121212;
9 | --container-bg: #1e1e1e;
10 | --primary-color: #bb86fc;
11 | --secondary-color: #03dac6;
12 | --text-primary: #e1e1e1;
13 | --text-secondary: #b0b0b0;
14 | --border-color: #2c2c2c;
15 | --input-bg: #2c2c2c;
16 | --shadow-color: rgba(0, 0, 0, 0.3);
17 | }
18 |
19 | body {
20 | font-family: sans-serif;
21 | background-color: var(--bg-color);
22 | height: 100vh;
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | color: var(--text-primary);
27 | }
28 |
29 | .signup-container {
30 | background-color: var(--container-bg);
31 | padding: 2.5rem;
32 | border-radius: 1rem;
33 | box-shadow: 0 10px 30px var(--shadow-color);
34 | text-align: center;
35 | max-width: 400px;
36 | width: 90%;
37 | border: 1px solid var(--border-color);
38 | }
39 |
40 | .icon-container {
41 | margin-bottom: 1.5rem;
42 | font-size: 2.5rem;
43 | color: var(--primary-color);
44 | }
45 |
46 | h2 {
47 | margin-bottom: 0.75rem;
48 | color: var(--text-primary);
49 | font-weight: 600;
50 | font-size: 1.5rem;
51 | }
52 |
53 | p {
54 | font-size: 0.95rem;
55 | color: var(--text-secondary);
56 | line-height: 1.5;
57 | margin-bottom: 1.5rem;
58 | }
59 |
60 | .input-group {
61 | margin-bottom: 1rem;
62 | position: relative;
63 | }
64 |
65 | .input-group i {
66 | position: absolute;
67 | left: 12px;
68 | top: 50%;
69 | transform: translateY(-50%);
70 | color: var(--text-secondary);
71 | }
72 |
73 | input {
74 | width: 100%;
75 | padding: 0.85rem 0.85rem 0.85rem 2.5rem;
76 | background-color: var(--input-bg);
77 | border: 1px solid var(--border-color);
78 | color: var(--text-primary);
79 | font-size: 1rem;
80 | transition: all 0.3s ease;
81 | border-radius: 8px;
82 | }
83 |
84 | input:focus {
85 | outline: none;
86 | border-color: var(--primary-color);
87 | box-shadow: 0 0 0 2px rgba(187, 134, 252, 0.25);
88 | }
89 |
90 | input::placeholder {
91 | color: var(--text-secondary);
92 | opacity: 0.7;
93 | }
94 |
95 | button {
96 | width: 100%;
97 | padding: 0.85rem;
98 | background-color: var(--primary-color);
99 | color: black;
100 | font-size: 1rem;
101 | font-weight: 500;
102 | border: none;
103 | border-radius: 8px;
104 | cursor: pointer;
105 | display: flex;
106 | justify-content: center;
107 | align-items: center;
108 | gap: 8px;
109 | transition: all 0.3s ease;
110 | }
111 |
112 | button:hover {
113 | background-color: #a66ae9;
114 | transform: translateY(-2px);
115 | }
116 |
117 | button i {
118 | font-size: 0.9rem;
119 | transition: transform 0.3s ease;
120 | }
121 |
122 | button:hover i {
123 | transform: translateX(3px);
124 | }
125 |
126 | .benefits {
127 | margin-top: 1.5rem;
128 | text-align: left;
129 | border-top: 1px solid var(--border-color);
130 | padding-top: 1.5rem;
131 | }
132 |
133 | .benefit-item {
134 | display: flex;
135 | align-items: center;
136 | gap: 8px;
137 | margin-bottom: 0.5rem;
138 | font-size: 0.9rem;
139 | }
140 |
141 | .benefit-item i {
142 | color: var(--secondary-color);
143 | font-size: 0.8rem;
144 | }
145 |
146 | @media (max-width: 500px) {
147 | .signup-container {
148 | padding: 2rem 1.5rem;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------