├── .gitignore ├── README.md ├── app.js ├── design ├── active-states.jpg ├── desktop-design.jpg ├── desktop-preview.jpg └── mobile-design.jpg ├── images ├── bg-intro-desktop.png ├── bg-intro-mobile.png ├── favicon-32x32.png └── icon-error.svg ├── index.html ├── style-guide.md └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Avoid accidental Sketch file upload 2 | ############################################### 3 | ## Please do not remove line 5 - thanks! 🙂 ## 4 | ############################################### 5 | *.sketch 6 | 7 | # Avoid accidental XD or Figma upload if you convert the design file 8 | ####################################################### 9 | ## Please do not remove lines 11 and 12 - thanks! 🙂 ## 10 | ####################################################### 11 | *.xd 12 | *.fig 13 | 14 | # Avoid your project being littered with annoying .DS_Store files! 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frontend Mentor - Intro component with sign up form solution 2 | 3 | This is a solution to the [Intro component with sign up form challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/intro-component-with-signup-form-5cf91bd49edda32581d28fd1). Frontend Mentor challenges help you improve your coding skills by building realistic projects. 4 | 5 | ## Table of contents 6 | 7 | - [Overview](#overview) 8 | - [The challenge](#the-challenge) 9 | - [Screenshot](#screenshot) 10 | - [Links](#links) 11 | - [My process](#my-process) 12 | - [Built with](#built-with) 13 | - [Author](#author) 14 | 15 | ## Overview 16 | 17 | ### The challenge 18 | 19 | Users should be able to: 20 | 21 | - View the optimal layout for the site depending on their device's screen size 22 | - See hover states for all interactive elements on the page 23 | - Receive an error message when the `form` is submitted if: 24 | - Any `input` field is empty. The message for this error should say _"[Field Name] cannot be empty"_ 25 | - The email address is not formatted correctly (i.e. a correct email address should have this structure: `name@host.tld`). The message for this error should say _"Looks like this is not an email"_ 26 | 27 | ### Screenshot 28 | 29 | ![Project screenshot](design/desktop-preview.jpg) 30 | 31 | ### Links 32 | 33 | - Solution URL: [https://github.com/JohnMwendwa/intro-component-with-signup-form](https://github.com/JohnMwendwa/intro-component-with-signup-form) 34 | - Live Site URL: [https://johnmwendwa.github.io/intro-component-with-signup-form](https://johnmwendwa.github.io/intro-component-with-signup-form/) 35 | 36 | ## My process 37 | 38 | ### Built with 39 | 40 | - Semantic HTML5 markup 41 | - CSS custom properties 42 | - CSS flexbox 43 | - CSS Grid 44 | - Mobile-first workflow 45 | - JavaScript 46 | 47 | ## Author 48 | 49 | - Website - [John Mwendwa](https://johnmwendwa.vercel.app/) 50 | - Frontend Mentor - [@JohnMwendwa](https://www.frontendmentor.io/profile/JohnMwendwa) 51 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const form = document.querySelector("form"); 2 | const firstName = document.querySelector("#first_name"); 3 | const lastName = document.querySelector("#last_name"); 4 | const email = document.querySelector("#email"); 5 | const password = document.querySelector("#password"); 6 | const submitButton = document.querySelector("form button"); 7 | 8 | form.addEventListener("submit", (e) => { 9 | e.preventDefault(); 10 | 11 | checkInputs(); 12 | }); 13 | 14 | function checkInputs() { 15 | console.log(firstName); 16 | const firstNameValue = firstName.value.trim(); 17 | const lastNameValue = lastName.value.trim(); 18 | const emailValue = email.value.trim(); 19 | const passwordValue = password.value.trim(); 20 | const isValidEmail = (email) => { 21 | return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); 22 | }; 23 | 24 | const firtNameError = document.querySelector(".first_name"); 25 | const lastNameError = document.querySelector(".last_name"); 26 | const emailError = document.querySelector(".email"); 27 | const passwordError = document.querySelector(".password"); 28 | 29 | // Validate first name 30 | if (firstNameValue === "") { 31 | firstName.style.outline = "1px solid red"; 32 | firtNameError.style.display = "block"; 33 | document.querySelector(".error.first_name").textContent = 34 | "First Name cannot be empty"; 35 | return; 36 | } else { 37 | firstName.style.outline = "1px solid var(--grayishBlue)"; 38 | firtNameError.style.display = "none"; 39 | document.querySelector(".error.first_name").textContent = ""; 40 | } 41 | 42 | // Validate last name 43 | if (lastNameValue === "") { 44 | lastName.style.outline = "1px solid red"; 45 | lastNameError.style.display = "block"; 46 | document.querySelector(".error.last_name").textContent = 47 | "Last Name cannot be empty"; 48 | return; 49 | } else { 50 | lastName.style.outline = "1px solid var(--grayishBlue)"; 51 | lastNameError.style.display = "none"; 52 | document.querySelector(".error.last_name").textContent = ""; 53 | } 54 | 55 | // Validate email 56 | if (emailValue === "") { 57 | email.style.outline = "1px solid red"; 58 | emailError.style.display = "block"; 59 | document.querySelector(".error.email").textContent = 60 | "Email cannot be empty"; 61 | return; 62 | } else if (!isValidEmail(emailValue)) { 63 | email.style.outline = "1px solid red"; 64 | emailError.style.display = "block"; 65 | document.querySelector(".error.email").textContent = 66 | "Looks like this is not an email"; 67 | return; 68 | } else { 69 | email.style.outline = "1px solid var(--grayishBlue)"; 70 | emailError.style.display = "none"; 71 | document.querySelector(".error.email").textContent = ""; 72 | } 73 | 74 | // Validate password 75 | if (passwordValue === "") { 76 | password.style.outline = "1px solid red"; 77 | passwordError.style.display = "block"; 78 | document.querySelector(".error.password").textContent = 79 | "Password cannot be empty"; 80 | return; 81 | } else { 82 | password.style.outline = "1px solid var(--grayishBlue)"; 83 | passwordError.style.display = "none"; 84 | document.querySelector(".error.password").textContent = ""; 85 | } 86 | 87 | // change button text to "Submitted successfully" 88 | submitButton.textContent = "Submitted successfully"; 89 | 90 | // Reset form after 3 seconds 91 | setTimeout(() => { 92 | form.reset(); 93 | submitButton.textContent = "Claim your free trial"; 94 | }, 5000); 95 | } 96 | -------------------------------------------------------------------------------- /design/active-states.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnMwendwa/intro-component-with-signup-form/8922d5ad3859307a48d0c9616df20ea4fcd8f97a/design/active-states.jpg -------------------------------------------------------------------------------- /design/desktop-design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnMwendwa/intro-component-with-signup-form/8922d5ad3859307a48d0c9616df20ea4fcd8f97a/design/desktop-design.jpg -------------------------------------------------------------------------------- /design/desktop-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnMwendwa/intro-component-with-signup-form/8922d5ad3859307a48d0c9616df20ea4fcd8f97a/design/desktop-preview.jpg -------------------------------------------------------------------------------- /design/mobile-design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnMwendwa/intro-component-with-signup-form/8922d5ad3859307a48d0c9616df20ea4fcd8f97a/design/mobile-design.jpg -------------------------------------------------------------------------------- /images/bg-intro-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnMwendwa/intro-component-with-signup-form/8922d5ad3859307a48d0c9616df20ea4fcd8f97a/images/bg-intro-desktop.png -------------------------------------------------------------------------------- /images/bg-intro-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnMwendwa/intro-component-with-signup-form/8922d5ad3859307a48d0c9616df20ea4fcd8f97a/images/bg-intro-mobile.png -------------------------------------------------------------------------------- /images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnMwendwa/intro-component-with-signup-form/8922d5ad3859307a48d0c9616df20ea4fcd8f97a/images/favicon-32x32.png -------------------------------------------------------------------------------- /images/icon-error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | Frontend Mentor | Intro component with sign up form 17 | 18 | 19 |
20 |
21 |

Learn to code by watching others

22 |

23 | See how experienced developers solve problems in real-time. Watching 24 | scripted tutorials is great, but understanding how developers think is 25 | invaluable. 26 |

27 |
28 | 29 |
30 | 33 |
34 |
35 | 36 | error icon 41 |

42 |
43 |
44 | 45 | error icon 50 |

51 |
52 |
53 | 54 | 59 | 60 |
61 | 62 |
63 | 64 | error icon 69 |

70 |
71 | 72 | 73 |

74 | By clicking the button, you are agreeing to our 75 | Terms and Services 76 |

77 |
78 |
79 |
80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /style-guide.md: -------------------------------------------------------------------------------- 1 | # Front-end Style Guide 2 | 3 | ## Layout 4 | 5 | The designs were created to the following widths: 6 | 7 | - Mobile: 375px 8 | - Desktop: 1440px 9 | 10 | ## Colors 11 | 12 | ### Primary 13 | 14 | - Red: hsl(0, 100%, 74%) 15 | - Green: hsl(154, 59%, 51%) 16 | 17 | ### Accent 18 | 19 | - Blue: hsl(248, 32%, 49%) 20 | 21 | ### Neutral 22 | 23 | - Dark Blue: hsl(249, 10%, 26%) 24 | - Grayish Blue: hsl(246, 25%, 77%) 25 | 26 | ## Typography 27 | 28 | ### Body Copy 29 | 30 | - Font size: 16px 31 | 32 | ### Font 33 | 34 | - Family: [Poppins](https://fonts.google.com/specimen/Poppins) 35 | - Weights: 400, 500, 600, 700 36 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | font-family: "Poppins", sans-serif; 8 | } 9 | 10 | :root { 11 | /* ### Primary */ 12 | --red: hsl(0, 100%, 74%); 13 | --green: hsl(154, 59%, 51%); 14 | /* ### Accent */ 15 | --blue: hsl(248, 32%, 49%); 16 | /* ### Neutral */ 17 | --darkBlue: hsl(249, 10%, 26%); 18 | --grayishBlue: hsl(246, 25%, 77%); 19 | } 20 | 21 | body { 22 | background-color: var(--red); 23 | background-image: url("images/bg-intro-mobile.png"); 24 | background-position: center; 25 | background-repeat: no-repeat; 26 | background-size: cover; 27 | min-height: 100vh; 28 | display: grid; 29 | place-content: center; 30 | color: #fff; 31 | padding: 4rem 2rem; 32 | } 33 | 34 | @media screen and (min-width: 640px) { 35 | body { 36 | background-image: url("images/bg-intro-desktop.png"); 37 | } 38 | } 39 | 40 | main { 41 | display: grid; 42 | place-content: center; 43 | gap: 3rem; 44 | grid-template-columns: 1fr; 45 | } 46 | 47 | @media screen and (min-width: 968px) { 48 | main { 49 | grid-template-columns: 1fr 1fr; 50 | } 51 | } 52 | 53 | section { 54 | max-width: 31rem; 55 | align-self: center; 56 | } 57 | 58 | .section__one { 59 | text-align: center; 60 | } 61 | 62 | .section__one h1 { 63 | font-size: 2rem; 64 | line-height: 1; 65 | margin-bottom: 1rem; 66 | } 67 | 68 | .section__one p { 69 | font-size: 0.8375rem; 70 | } 71 | 72 | @media screen and (min-width: 968px) { 73 | .section__one { 74 | text-align: unset; 75 | } 76 | 77 | .section__one h1 { 78 | font-size: 3rem; 79 | margin-bottom: 2rem; 80 | } 81 | .section__one p { 82 | font-size: 0.9375rem; 83 | } 84 | } 85 | 86 | .section__two .banner { 87 | background-color: var(--blue); 88 | padding: 1rem 2rem; 89 | border-radius: 0.5rem; 90 | box-shadow: 0 0.4rem rgba(0, 0, 0, 0.2); 91 | } 92 | 93 | .section__two h2 { 94 | font-size: 0.935rem; 95 | font-weight: 400; 96 | text-align: center; 97 | } 98 | .section__two h2 span { 99 | color: var(--grayishBlue); 100 | } 101 | 102 | form { 103 | display: grid; 104 | gap: 1rem; 105 | width: 100%; 106 | padding: 1.5rem; 107 | border-radius: 0.5rem; 108 | background-color: #fff; 109 | box-shadow: 0 0.4rem rgba(0, 0, 0, 0.2); 110 | margin-top: 1.5rem; 111 | } 112 | 113 | @media screen and (min-width: 968px) { 114 | form { 115 | padding: 2rem; 116 | } 117 | } 118 | 119 | form > div { 120 | position: relative; 121 | } 122 | 123 | input { 124 | width: 100%; 125 | padding: 0.75rem 1.5rem; 126 | font-size: 0.75rem; 127 | font-weight: 700; 128 | border-radius: 0.35rem; 129 | border: transparent; 130 | color: var(--darkBlue); 131 | outline: 1px solid var(--grayishBlue); 132 | } 133 | input:focus { 134 | outline: 1px solid var(--blue); 135 | } 136 | 137 | .error__icon { 138 | display: none; 139 | position: absolute; 140 | right: 1rem; 141 | top: 50%; 142 | transform: translateY(-50%); 143 | } 144 | 145 | form > div:has(.error) > .error__icon { 146 | top: 32%; 147 | } 148 | 149 | .error { 150 | color: red; 151 | text-align: end; 152 | font-size: 0.75rem; 153 | font-weight: 500; 154 | font-style: italic; 155 | margin-top: 0.5rem; 156 | transition: all 0.3s ease; 157 | } 158 | 159 | button { 160 | width: 100%; 161 | padding: 0.75rem 1rem; 162 | border: transparent; 163 | text-transform: uppercase; 164 | font-weight: 500; 165 | letter-spacing: 0.1rem; 166 | border-radius: 0.35rem; 167 | background-color: var(--green); 168 | box-shadow: 0 0.2rem rgba(0, 0, 0, 0.4); 169 | transition: all 0.3s ease; 170 | color: #fff; 171 | cursor: pointer; 172 | } 173 | 174 | button:hover { 175 | background-color: hsla(154, 59%, 51%, 0.7); 176 | } 177 | .terms { 178 | color: var(--grayishBlue); 179 | font-size: 0.625rem; 180 | text-align: center; 181 | } 182 | 183 | .terms a { 184 | color: hsla(0, 100%, 74%, 0.9); 185 | font-weight: 900; 186 | text-decoration: none; 187 | } 188 | --------------------------------------------------------------------------------