├── design
├── active-states.jpg
├── mobile-design.jpg
├── desktop-preview.jpg
├── desktop-design-empty.jpg
└── desktop-design-completed.jpg
├── images
├── favicon-32x32.png
├── Screenshot 2023-01-27 at 01-12-08 Tip calculator app.png
├── icon-person.svg
├── icon-dollar.svg
└── logo.svg
├── assests
└── fonts
│ ├── SpaceMono-Bold.ttf
│ └── SpaceMono-Regular.ttf
├── .gitignore
├── style-guide.md
├── README.md
├── index.html
├── script.js
└── styles.css
/design/active-states.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/design/active-states.jpg
--------------------------------------------------------------------------------
/design/mobile-design.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/design/mobile-design.jpg
--------------------------------------------------------------------------------
/images/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/images/favicon-32x32.png
--------------------------------------------------------------------------------
/design/desktop-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/design/desktop-preview.jpg
--------------------------------------------------------------------------------
/assests/fonts/SpaceMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/assests/fonts/SpaceMono-Bold.ttf
--------------------------------------------------------------------------------
/design/desktop-design-empty.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/design/desktop-design-empty.jpg
--------------------------------------------------------------------------------
/assests/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/assests/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/design/desktop-design-completed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/design/desktop-design-completed.jpg
--------------------------------------------------------------------------------
/images/Screenshot 2023-01-27 at 01-12-08 Tip calculator app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikennarichard/Tip-calculator-app/HEAD/images/Screenshot 2023-01-27 at 01-12-08 Tip calculator app.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Avoid accidental upload of the Sketch and Figma design files
2 | #####################################################
3 | ## Please do not remove lines 5 and 6 - thanks! 🙂 ##
4 | #####################################################
5 | *.sketch
6 | *.fig
7 |
8 | # Avoid accidental XD upload if you convert the design file
9 | ###############################################
10 | ## Please do not remove line 12 - thanks! 🙂 ##
11 | ###############################################
12 | *.xd
13 |
14 | # Avoid your project being littered with annoying .DS_Store files!
15 | .DS_Store
16 | .prettierignore
--------------------------------------------------------------------------------
/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 | - Strong cyan: hsl(172, 67%, 45%)
15 |
16 | ### Neutral
17 |
18 | - Very dark cyan: hsl(183, 100%, 15%)
19 | - Dark grayish cyan: hsl(186, 14%, 43%)
20 | - Grayish cyan: hsl(184, 14%, 56%)
21 | - Light grayish cyan: hsl(185, 41%, 84%)
22 | - Very light grayish cyan: hsl(189, 41%, 97%)
23 | - White: hsl(0, 0%, 100%)
24 |
25 | ## Typography
26 |
27 | ### Body Copy
28 |
29 | - Font size (form inputs): 24px
30 |
31 | ### Font
32 |
33 | - Family: [Space Mono](https://fonts.google.com/specimen/Space+Mono)
34 | - Weights: 700
35 |
--------------------------------------------------------------------------------
/images/icon-person.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/icon-dollar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Frontend Mentor - Tip calculator app solution
2 |
3 | This is a solution to the [Tip calculator app challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/tip-calculator-app-ugJNGbJUX). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
4 |
5 | - [app](https://ikennarichard.github.io/Tip-calculator-app/)
6 |
7 | ## Table of contents
8 |
9 | - [Overview](#overview)
10 | - [The challenge](#the-challenge)
11 | - [Screenshot](#screenshot)
12 | - [Links](#links)
13 | - [Built with](#built-with)
14 |
15 | - [Author](#author)
16 |
17 | ## Overview
18 |
19 | ### The challenge
20 |
21 | Users should be able to:
22 |
23 | - View the optimal layout for the app depending on their device's screen size
24 | - See hover states for all interactive elements on the page
25 | - Calculate the correct tip and total cost of the bill per person
26 |
27 | ### Screenshot
28 |
29 | 
30 |
31 |
32 | ### Links
33 |
34 | - [github](https://github.com/ikennarichard/Tip-calculator-app)
35 |
36 |
37 |
38 | ## My process
39 |
40 | ### Built with
41 |
42 | - Semantic HTML5 markup
43 | - CSS custom properties
44 | - Flexbox
45 | - CSS Grid
46 | - Mobile-first workflow
47 |
48 |
49 | ## Author
50 |
51 | - Website - [ikennarichard](https://www.github.com/ikennarichard)
52 | - Frontend Mentor - [@ikennarichard](https://www.frontendmentor.io/profile/yourusername)
53 | - Twitter - [@ikennarichard_](https://www.twitter.com/@ikennarichard_)
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Tip calculator app
9 |
10 |
11 |
12 |
15 |
16 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
Tip Amount
53 |
/ person
54 |
55 |
56 | $0.00
57 |
58 |
59 |
60 |
61 |
62 |
63 |
Total
64 |
/ person
65 |
66 |
67 | $0.00
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | // dom elements
2 | const bill = document.querySelector("#bill");
3 |
4 | const tips = [...document.querySelectorAll(".select_btn")];
5 |
6 | const numberFields = [...document.querySelectorAll("input[type=number]")];
7 |
8 | const tipAmount = document.querySelector(".tip_amount > strong");
9 |
10 | const totalTip = document.querySelector(".total_tip > strong");
11 |
12 | const resetBtn = document.querySelector(".reset");
13 |
14 |
15 |
16 |
17 | // global variables
18 | let tipValue;
19 |
20 |
21 | // bill
22 | let billAmount = () => bill.valueAsNumber;
23 |
24 | // get tip
25 | tips.forEach((tip) => {
26 |
27 | tip.addEventListener("click", (e) => {
28 | toggleStyle(e);
29 | document.querySelector(".custom_tip").value = "";
30 | tipValue = e.target.value.slice(0, -1);
31 |
32 | let noOfPeople = document.querySelector("#no_of_people").valueAsNumber;
33 |
34 |
35 | let eachPersonsBill = billAmount() / noOfPeople;
36 |
37 | let tip_amount = eachPersonsBill * (Number(tipValue) / 100);
38 |
39 | let total = eachPersonsBill + tip_amount;
40 |
41 | if (!tip_amount){
42 | tipAmount.textContent = `$0.00`;
43 | totalTip.textContent = `$0.00`;
44 | } else {
45 | tipAmount.textContent = `$${tip_amount.toFixed(2)}`;
46 | totalTip.textContent = `$${total.toFixed(2)}`;
47 | }
48 |
49 | }
50 | )});
51 |
52 |
53 |
54 | numberFields.forEach((field) => {
55 | field.addEventListener("input", (e) => {
56 |
57 | if (e.target.value == "") {
58 | e.target.classList.remove("no_error");
59 | e.target.classList.add("error");
60 |
61 |
62 | } else {
63 | e.target.classList.add("no_error");
64 |
65 | }
66 |
67 | if (field['id'] == "no_of_people"){
68 | // display error message
69 | let errorMessage = document.querySelector(".error_message");
70 |
71 | if(field.value == "") {
72 |
73 | errorMessage.style.visibility = "visible";
74 | } else {
75 | errorMessage.style.visibility = "hidden";
76 | }
77 |
78 |
79 | // calculate tip
80 | let eachPersonsBill = billAmount() / e.target.valueAsNumber;
81 |
82 | let tip_amount = eachPersonsBill * (Number(tipValue) / 100);
83 |
84 |
85 |
86 |
87 | let total = eachPersonsBill + tip_amount;
88 |
89 | if (!tip_amount){
90 | tipAmount.textContent = `$0.00`;
91 | totalTip.textContent = `$0.00`;
92 | } else {
93 | tipAmount.textContent = `$${tip_amount.toFixed(2)}`;;
94 | totalTip.textContent = `$${total.toFixed(2)}`;
95 | }
96 |
97 |
98 | }
99 |
100 | if (field['id'] === "select_tip") {
101 |
102 | tipValue = document.querySelector(".custom_tip").valueAsNumber;
103 |
104 | let noOfPeople = document.querySelector("#no_of_people").valueAsNumber;
105 |
106 |
107 | let eachPersonsBill = billAmount() / noOfPeople;
108 |
109 |
110 | let tip_amount = eachPersonsBill * (Number(tipValue) / 100);
111 |
112 | let total = eachPersonsBill + tip_amount;
113 |
114 | if (!tip_amount){
115 | tipAmount.textContent = `$0.00`;
116 | totalTip.textContent = `$0.00`;
117 | } else {
118 | tipAmount.textContent = `$${tip_amount.toFixed(2)}`;;
119 | totalTip.textContent = `$${total.toFixed(2)}`;
120 | }
121 | }
122 | });
123 | });
124 |
125 |
126 | //toggle button background
127 | function toggleStyle(event) {
128 |
129 | tips.forEach((tip) => {
130 | if (tip == event.target) {
131 | tip.classList.add("button_color");
132 | } else {
133 | tip.classList.remove("button_color");
134 | }
135 | });
136 |
137 | };
138 |
139 |
140 | //remove style when field is clicked
141 | document.querySelector(".custom_tip").addEventListener("click", ()=>{
142 | tips.forEach(tip => {
143 | tip.classList.remove("button_color");
144 | })
145 | })
146 |
147 |
148 | // reset button
149 | resetBtn.addEventListener ("click", ()=> {
150 |
151 | bill.value = "";
152 | document.querySelector(".no_of_people").value = "";
153 | document.querySelector(".custom_tip").value = "";
154 | window.location.reload();
155 | })
156 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | /* font */
2 | @font-face {
3 | font-family: --space-bold;
4 | src: url(./assests/fonts/SpaceMono-Bold.ttf);
5 | font-weight: 700;
6 | }
7 |
8 | @font-face {
9 | font-family: --space-regular;
10 | src: url(./assests/fonts/SpaceMono-Regular.ttf);
11 | }
12 |
13 | /* styles */
14 | :root {
15 | --strong-cyan: hsl(172, 67%, 45%);
16 | --very-dark-cyan: hsl(183, 100%, 15%);
17 | --dark-grayish-cyan: hsl(186, 14%, 43%);
18 | --grayish-cyan: hsl(184, 14%, 56%);
19 | --light-grayish-cyan: hsl(185, 41%, 84%);
20 | --very-light-grayish-cyan: hsl(189, 41%, 97%);
21 | --white: hsl(0, 0%, 100%);
22 | --soft-red: #F47174;
23 | }
24 |
25 |
26 | * {
27 | box-sizing: border-box;
28 | margin: 0;
29 | padding: 0;
30 | line-height: 1.5;
31 | }
32 |
33 |
34 | html {
35 | font-size: 62.5%;
36 | }
37 |
38 |
39 | img {
40 | max-width: 100%;
41 | }
42 |
43 |
44 |
45 | body {
46 | display: grid;
47 | place-items: center;
48 | min-height: 100vh;
49 | font-family: --space-regular, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, monospace;
50 | font-weight: 700;
51 | background-color: var(--light-grayish-cyan);
52 | }
53 |
54 |
55 | h1 {
56 | text-align: center;
57 | font-family: --space-bold;
58 | /* border: 1px solid; */
59 | word-wrap: break-word;
60 | width: 3.8em;
61 | letter-spacing: 5px;
62 | color: var(--dark-grayish-cyan);
63 | }
64 |
65 |
66 | input {
67 | font-family: --space-bold;
68 | width: 100%;
69 | }
70 |
71 |
72 |
73 | .wrapper {
74 | display: grid;
75 | place-items: center;
76 | width: 100%;
77 |
78 | gap: 20px;
79 | padding-block-start: 4em;
80 | /* border: 5px solid burlywood; */
81 | }
82 |
83 |
84 | main {
85 | padding-block-start: 2em;
86 | width: 100%;
87 | min-width: 228px;
88 |
89 | display: grid;
90 | place-items: center;
91 | background-color: var(--white);
92 | padding: 2em 1.5em;
93 | gap: 20px;
94 | border-top-left-radius: 15px;
95 | border-top-right-radius: 15px;
96 | }
97 |
98 |
99 | .calculator {
100 | width: 100%;
101 | display: flex;
102 | flex-direction: column;
103 | align-items: center;
104 | gap: 20px;
105 | }
106 |
107 |
108 |
109 | .no_of_people, .bill, .custom_tip {
110 | display: block;
111 | text-align: right;
112 | padding-inline-end: 9px;
113 | border: none;
114 | outline: none;
115 | padding-block: 5px;
116 | border-radius: 3px;
117 | background-color: var(--very-light-grayish-cyan);
118 | color: var(--very-dark-cyan);
119 | appearance: textfield;
120 | }
121 |
122 |
123 | .bill, .no_of_people {
124 | margin-block-start: .7em;
125 | }
126 |
127 | .error {
128 | outline: 2px solid var(--soft-red);
129 | }
130 |
131 | .no_error {
132 | outline: 1px solid var(--strong-cyan);
133 | }
134 |
135 |
136 | .calculator > div {
137 | width: 100%;
138 | }
139 |
140 |
141 |
142 | .dollar_sign {
143 | height: 12px;
144 | position: relative;
145 | top: 34px;
146 | right: 25px;
147 | }
148 |
149 |
150 |
151 | .tip_percent {
152 | display: grid;
153 | grid-template-columns: repeat(autofit);
154 | gap: 10px;
155 | width: 100%;
156 | }
157 |
158 |
159 | label {
160 | letter-spacing: 1px;
161 | }
162 |
163 | .tip_percent > label{
164 | grid-column: span 2;
165 | }
166 |
167 |
168 | .select_btn {
169 | border: none;
170 | outline: none;
171 | padding: .4em 2em;
172 | background-color: var(--very-dark-cyan);
173 | color: var(--very-light-grayish-cyan);
174 | cursor: pointer;
175 | border-radius: 4px;
176 | transition: background-color, 0.2s ease-in-out;
177 | }
178 |
179 |
180 |
181 | input[type=button]:hover {
182 | background-color: var(--strong-cyan);
183 | color: var(--very-dark-cyan);
184 | }
185 |
186 |
187 | .button_color {
188 | background-color: var(--strong-cyan);
189 | color: var(--very-dark-cyan);
190 | }
191 |
192 | .num_people {
193 | position: relative;
194 | }
195 |
196 |
197 | .error_message {
198 | word-spacing: 2px;
199 | position: absolute;
200 | right: 0;
201 | visibility: hidden;
202 | color: var(--soft-red) ;
203 | }
204 |
205 |
206 |
207 | .figure_icon {
208 | position: relative;
209 | top: 34px;
210 | right: 110px;
211 | height: 12px;
212 | }
213 |
214 |
215 | .total_tip_section {
216 | background-color: var(--very-dark-cyan);
217 | display: flex;
218 | flex-direction: column;
219 | gap: 20px;
220 | width: 100%;
221 | padding: 1.4em 1.5em;
222 | border-radius: 1em;
223 | }
224 |
225 | .view {
226 | display: flex;
227 | flex-direction: column;
228 | gap: 15px;
229 | }
230 |
231 |
232 | .tip, .total {
233 | display: flex;
234 | justify-content: space-between;
235 | align-items: center;
236 | flex-wrap: wrap;
237 | }
238 |
239 |
240 | .tip_person, .total_person {
241 | color: var(--light-grayish-cyan) ;
242 | }
243 |
244 | .tip_person > span, .total_person > span {
245 | color: var(--dark-grayish-cyan)
246 | }
247 |
248 | strong {
249 | font-size: 2.5rem;
250 | color: var(--strong-cyan);
251 | font-family: --space-bold;
252 | }
253 |
254 |
255 | button {
256 | background-color: var(--strong-cyan);
257 | outline: none;
258 | border: none;
259 | color: var(--very-dark-cyan);
260 | font-weight: 700;
261 | padding: .5em 4em;
262 | border-radius: .4em;
263 |
264 | }
265 |
266 | button:hover {
267 | cursor: pointer;
268 | filter: brightness(150%);
269 | }
270 |
271 |
272 | @media (min-width: 550px) {
273 |
274 | .wrapper {
275 | max-width: 620px
276 | }
277 |
278 |
279 | main {
280 | grid-template-columns: 1fr 1fr;
281 | margin-block-start: 25px;
282 | }
283 |
284 | .tip_percent {
285 | grid-template-columns: repeat(3, 1fr);
286 | }
287 |
288 | .tip_percent > label{
289 | grid-column: span 3;
290 | }
291 |
292 | .total_tip_section {
293 | overflow: hidden;
294 | text-overflow: ellipsis;
295 | white-space: nowrap;
296 | height: 100%;
297 | justify-content: space-between;
298 | }
299 |
300 | .view {
301 | padding-top: 1.5em;
302 | gap: 27px;
303 | }
304 |
305 | strong {
306 | font-size: 3rem;
307 | }
308 |
309 | button {
310 | margin-block-end: 10px;
311 | }
312 | }
--------------------------------------------------------------------------------