Project: JavaScript Flashcard Study App (JSON + Progress + Save Progress)
https://github.com/lsvekis/JavaScript-Projects

A flashcard app that:
- loads cards from
cards.json - shows Front (question/term) → click Flip to reveal Back (answer/definition)
- buttons: Know, Still Learning, Next, Previous
- tracks progress: known / learning / total
- saves progress in localStorage so it persists after refresh
1) cards.json
{
"title": "JavaScript Flashcards",
"cards": [
{ "id": 1, "front": "What is the DOM?", "back": "Document Object Model" },
{ "id": 2, "front": "What does querySelector() return?", "back": "The first element that matches a CSS selector" },
{ "id": 3, "front": "What is a callback?", "back": "A function passed into another function to run later" }
]
}
2) index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Flashcards</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="wrap">
<header class="header">
<h1 id="title">Flashcards</h1>
<p class="meta" id="meta"></p>
</header> <section class="card">
<div class="flashcard" id="flashcard" tabindex="0" aria-label="Flashcard">
<p id="cardText">Loading…</p>
</div> <div class="row">
<button class="btn" id="prevBtn">Previous</button>
<button class="btn" id="flipBtn">Flip</button>
<button class="btn" id="nextBtn">Next</button>
</div> <div class="row">
<button class="btn success" id="knowBtn">Know ✅</button>
<button class="btn" id="learnBtn">Still Learning</button>
<button class="btn danger" id="resetBtn">Reset</button>
</div> <div class="progress">
<div class="bar" id="bar"></div>
</div>
<p class="meta" id="progressText"></p>
</section>
</main> <script type="module" src="app.js"></script>
</body>
</html>
3) styles.css (simple)
body{font-family:Arial;background:#f5f5f5;margin:0;padding:20px}
.wrap{max-width:700px;margin:auto}
.header{margin-bottom:12px}
.meta{margin:0;color:#666;font-size:14px}
.card{background:#fff;border:1px solid #ddd;border-radius:8px;padding:18px}
.flashcard{
border:1px solid #ccc;
background:#eee;
border-radius:8px;
padding:24px;
min-height:140px;
display:flex;
align-items:center;
justify-content:center;
cursor:pointer;
user-select:none;
text-align:center;
font-size:18px;
}
.row{display:flex;gap:10px;flex-wrap:wrap;margin-top:10px}
.btn{padding:8px 14px;border:1px solid #bbb;border-radius:6px;background:#eee;cursor:pointer}
.btn:disabled{opacity:.5;cursor:not-allowed}
.btn.success{background:#e7fff1;border-color:#31b36b}
.btn.danger{background:#ffe7ea;border-color:#d93b4d}
.progress{height:10px;background:#ddd;border-radius:5px;overflow:hidden;margin-top:12px}
.bar{height:10px;width:0;background:#2b7de9;transition:width .2s ease}
4) app.js (modern, beginner-friendly)
const titleEl = document.getElementById("title");
const metaEl = document.getElementById("meta");
const cardEl = document.getElementById("flashcard");
const cardTextEl = document.getElementById("cardText");const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
const flipBtn = document.getElementById("flipBtn");const knowBtn = document.getElementById("knowBtn");
const learnBtn = document.getElementById("learnBtn");
const resetBtn = document.getElementById("resetBtn");const barEl = document.getElementById("bar");
const progressTextEl = document.getElementById("progressText");let data = null;
let index = 0;
let showBack = false;// status map: id -> "know" | "learn" | null
let status = loadStatus();init();async function init() {
const res = await fetch("./cards.json", { cache: "no-store" });
data = await res.json(); titleEl.textContent = data.title || "Flashcards";
hookEvents();
render();
}function hookEvents() {
prevBtn.addEventListener("click", () => {
if (index > 0) { index--; showBack = false; render(); }
}); nextBtn.addEventListener("click", () => {
if (index < data.cards.length - 1) { index++; showBack = false; render(); }
}); flipBtn.addEventListener("click", () => {
showBack = !showBack;
renderCardText();
}); cardEl.addEventListener("click", () => {
showBack = !showBack;
renderCardText();
}); knowBtn.addEventListener("click", () => setStatus("know"));
learnBtn.addEventListener("click", () => setStatus("learn")); resetBtn.addEventListener("click", () => {
status = {};
saveStatus(status);
render();
}); // Optional keyboard: space flips, arrows navigate
document.addEventListener("keydown", (e) => {
if (e.key === " ") { e.preventDefault(); showBack = !showBack; renderCardText(); }
if (e.key === "ArrowLeft") prevBtn.click();
if (e.key === "ArrowRight") nextBtn.click();
});
}function render() {
const total = data.cards.length;
metaEl.textContent = `Card ${index + 1} of ${total}`; renderCardText();
updateButtons();
updateProgress();
}function renderCardText() {
const card = data.cards[index];
cardTextEl.textContent = showBack ? card.back : card.front; // Show current card status in border color (simple feedback)
const s = status[card.id] || null;
cardEl.style.borderColor = s === "know" ? "#31b36b" : s === "learn" ? "#2b7de9" : "#ccc";
}function updateButtons() {
prevBtn.disabled = index === 0;
nextBtn.disabled = index === data.cards.length - 1;
}function setStatus(value) {
const card = data.cards[index];
status[card.id] = value;
saveStatus(status);
render();
}function updateProgress() {
const total = data.cards.length;
const ids = data.cards.map(c => c.id); let know = 0, learn = 0;
ids.forEach(id => {
if (status[id] === "know") know++;
if (status[id] === "learn") learn++;
}); const done = know + learn;
const percent = Math.round((done / total) * 100); barEl.style.width = percent + "%";
progressTextEl.textContent = `Know: ${know} • Still learning: ${learn} • Reviewed: ${done}/${total} (${percent}%)`;
}function loadStatus() {
try {
return JSON.parse(localStorage.getItem("flashcardStatus") || "{}");
} catch {
return {};
}
}function saveStatus(obj) {
localStorage.setItem("flashcardStatus", JSON.stringify(obj));
}