Midterm Project — Tip Calculator MILESTONE
যা শিখেছেন সব দিয়ে একটি বাস্তব app বানান
1. The Brief
Build a single-page Tip Calculator: enter a bill amount, pick a tip percentage, see total + per-person split. Persist last bill in localStorage. Deploy free to GitHub Pages or Netlify.
2. Project Structure
tip-calculator/
├─ index.html
├─ style.css
├─ app.js
└─ README.md
3. index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tip Calculator</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>💸 Tip Calculator</h1>
<label>Bill (৳)
<input id="bill" type="number" min="0" step="0.01" placeholder="0.00">
</label>
<label>Tip %
<div class="tips">
<button data-tip="5">5%</button>
<button data-tip="10" class="active">10%</button>
<button data-tip="15">15%</button>
<button data-tip="20">20%</button>
</div>
</label>
<label>People
<input id="people" type="number" min="1" value="1">
</label>
<section class="result">
<p>Tip: <b id="tip">৳0.00</b></p>
<p>Total: <b id="total">৳0.00</b></p>
<p>Per person: <b id="per">৳0.00</b></p>
</section>
<button id="reset">Reset</button>
</main>
<script src="app.js" defer></script>
</body>
</html>
4. style.css (compact)
* { box-sizing: border-box; }
body {
font-family: system-ui, "Hind Siliguri", sans-serif;
background: #0f172a; color: #e2e8f0;
display: grid; place-items: center; min-height: 100vh;
}
main {
background: #1e293b; padding: 2rem; border-radius: 12px;
width: min(420px, 90%); box-shadow: 0 10px 30px rgba(0,0,0,0.4);
}
h1 { margin: 0 0 1.2rem; }
label { display: block; margin: 0.8rem 0; }
input[type="number"] {
width: 100%; padding: 0.6rem; margin-top: 0.3rem;
border-radius: 6px; border: none; background: #334155;
color: white; font-size: 1rem;
}
.tips { display: flex; gap: 0.4rem; margin-top: 0.4rem; }
.tips button {
flex: 1; padding: 0.5rem; border-radius: 6px;
border: 1px solid #475569; background: transparent; color: white;
cursor: pointer;
}
.tips button.active { background: #39b549; border-color: #39b549; }
.result { margin: 1.2rem 0; padding: 1rem; background: #0f172a; border-radius: 8px; }
#reset {
width: 100%; padding: 0.7rem;
background: #ef4444; color: white; border: none;
border-radius: 6px; cursor: pointer; font-weight: 600;
}
5. app.js — the Brain
// Single source of truth
const state = {
bill: 0,
tipPct: 10,
people: 1,
};
const $ = (id) => document.getElementById(id);
const fmt = (n) => "৳" + n.toFixed(2);
// Load saved state if any
try {
Object.assign(state, JSON.parse(localStorage.getItem("tip-state") || "{}"));
$("bill").value = state.bill || "";
$("people").value = state.people;
} catch {}
function render() {
const tip = state.bill * state.tipPct / 100;
const total = state.bill + tip;
const per = total / Math.max(1, state.people);
$("tip").textContent = fmt(tip);
$("total").textContent = fmt(total);
$("per").textContent = fmt(per);
document.querySelectorAll(".tips button").forEach(b =>
b.classList.toggle("active", +b.dataset.tip === state.tipPct));
localStorage.setItem("tip-state", JSON.stringify(state));
}
$("bill").addEventListener("input", e => {
state.bill = +e.target.value || 0;
render();
});
$("people").addEventListener("input", e => {
state.people = Math.max(1, +e.target.value || 1);
render();
});
document.querySelector(".tips").addEventListener("click", e => {
if (e.target.tagName !== "BUTTON") return;
state.tipPct = +e.target.dataset.tip;
render();
});
$("reset").addEventListener("click", () => {
Object.assign(state, { bill: 0, tipPct: 10, people: 1 });
$("bill").value = ""; $("people").value = 1;
render();
});
render();
6. Runnable Logic Demo
The app uses real DOM, but the compute logic can run inside this sandbox so you can test it before wiring up HTML.
function tipCalc(bill, tipPct, people) {
const tip = bill * tipPct / 100;
const total = bill + tip;
const per = total / Math.max(1, people);
return {
tip: "৳" + tip.toFixed(2),
total: "৳" + total.toFixed(2),
per: "৳" + per.toFixed(2),
};
}
console.log(tipCalc(1500, 10, 3));
console.log(tipCalc(2200, 15, 4));
7. Deploy It
- Create a new GitHub repo, push the three files
- Go to Settings → Pages → Source: main / root
- Wait ~1 min — your app is live at
https://<you>.github.io/<repo>/ - Tell five friends. Done.
8. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| SPA | Single-Page App — one HTML page; JS swaps content. | এক HTML page; JS-ই content পরিবর্তন করে। |
| State | The single object that holds the app's current data. | App-এর বর্তমান data ধরে রাখা single object। |
| Render | Turning state into DOM (called every time state changes). | State থেকে DOM তৈরি — প্রতিবার state বদলালে চলে। |
| Event handler | Function attached to a DOM event (click, input, submit). | DOM event-এ যুক্ত function। |
localStorage | Browser key-value store, persisted across reloads. | Browser-এর key-value store; reload-এর পরও থাকে। |
| Persistence | Saving data so it survives a refresh or close. | Refresh/close-এর পরও data ধরে রাখা। |
| Deployment | Putting your app on a public URL. | App-কে public URL-এ live করা। |
| GitHub Pages | Free static hosting from a GitHub repo. | GitHub repo থেকে free static hosting। |
9. Extension Tasks
- Add a custom-tip text input that lets the user enter any percentage.
✨ Show Answer
Add
<input id="customTip" type="number" placeholder="custom %">and wire itsinputhandler to setstate.tipPct = +e.target.value; reuserender(). - Add a currency selector (BDT / USD / EUR) and reformat with
Intl.NumberFormat.✨ Show Answer
a2.jsconst fmt = (n, code) => new Intl.NumberFormat("en", { style: "currency", currency: code }).format(n); console.log(fmt(1500, "BDT"), fmt(15, "USD"), fmt(15, "EUR")); - Add a tax slider 0–15% on top of the tip.
✨ Show Answer
Add
state.taxPct, an<input type="range" min="0" max="15">wired to update it, and recompute total asbill + tip + bill * taxPct / 100. - Add a dark/light mode toggle that persists in localStorage.
✨ Show Answer
a4.js// Sketch — works inside any page const theme = { get() { return localStorage.getItem("theme") || "dark"; }, toggle() { const next = this.get() === "dark" ? "light" : "dark"; localStorage.setItem("theme", next); return next; } }; console.log(theme.get(), theme.toggle(), theme.toggle()); - Deploy to GitHub Pages and share the URL with three classmates.
✨ Show Answer
Steps:
git init,git add .,git commit -m "init", push to a new GitHub repo, then Settings → Pages, choose main branch, root folder, save. After ~60s the URL is live. There is no #5 answer to copy — you literally have to ship it. That is the assignment.
Summary — Module 19
You shipped a real, deployed JavaScript app from scratch — a single source of truth state, event delegation, localStorage persistence, and a clean render() function. Five extension tasks await — each one teaches you something the lesson didn't.