diff --git a/DevPath/CHANGELOG.md b/DevPath/CHANGELOG.md index f775559a..95f8b9bf 100644 --- a/DevPath/CHANGELOG.md +++ b/DevPath/CHANGELOG.md @@ -17,4 +17,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed -- Fixed an issue where skill chips on the homepage were unclickable due to JavaScript syntax errors \ No newline at end of file +- Fixed an issue where skill chips on the homepage were unclickable due to JavaScript syntax errors diff --git a/DevPath/static/script.js b/DevPath/static/script.js index 1496e90f..c03b83c9 100644 --- a/DevPath/static/script.js +++ b/DevPath/static/script.js @@ -1,6 +1,7 @@ // script.js — DevPath client-side logic // // Responsibilities: +// - Dark mode toggle // - Mobile navigation toggle // - Skill chip manager (add/remove skills) // - Form validation with per-field error messages @@ -8,6 +9,169 @@ // - Result card rendering // - Code viewer panel (detail page) + +// ============================================================ +// THEME ENGINE +// ============================================================ +// The theme system works in three parts: +// +// Part A — Anti-FOUC inline script (in
of each template): +// Sets html[data-theme] synchronously before the stylesheet is +// evaluated, so the browser paints the correct colours on frame 1. +// +// Part B — initTheme() (runs immediately below): +// Syncs the toggle button aria-pressed + aria-label with the +// already-applied theme. Adds the "theme-ready" class on the +// next animation frame so CSS transitions become active only +// AFTER the initial paint (preventing a colour transition flash +// when the page first loads). +// +// Part C — applyTheme(theme) (called on button click): +// The single source of truth for all theme changes. Updates +// data-theme, localStorage, aria-pressed, aria-label, and an +// aria-live region so screen readers announce the change. +// ============================================================ + +(function () { + + // ---- Part B: sync button state once DOM is ready ---------- + function initTheme() { + var html = document.documentElement; + var theme = html.dataset.theme || "light"; + + // Sync every toggle button on the page (desktop + mobile versions) + document.querySelectorAll(".theme-toggle").forEach(function (btn) { + var isDark = theme === "dark"; + // aria-pressed = true when dark mode is ON + btn.setAttribute("aria-pressed", isDark ? "true" : "false"); + // aria-label describes what clicking WILL do (not what IS active), + // which is the recommended accessible pattern for toggle buttons. + btn.setAttribute("aria-label", + isDark ? "Switch to light mode" : "Switch to dark mode" + ); + }); + + // Add .theme-ready on the NEXT frame so CSS transitions are + // suppressed during the initial render (avoids colour flash). + requestAnimationFrame(function () { + html.classList.add("theme-ready"); + }); + } + + // ---- Part C: apply a theme change ------------------------- + function applyTheme(theme) { + var html = document.documentElement; + var isDark = theme === "dark"; + + // 1. Apply via data attribute — CSS [data-theme="dark"] picks this up + html.dataset.theme = theme; + + // 2. Persist the user's choice across sessions + try { localStorage.setItem("theme", theme); } catch (e) { /* private browsing may block */ } + + // 3. Update every toggle button's accessible state + document.querySelectorAll(".theme-toggle").forEach(function (btn) { + btn.setAttribute("aria-pressed", isDark ? "true" : "false"); + btn.setAttribute("aria-label", + isDark ? "Switch to light mode" : "Switch to dark mode" + ); + }); + + // 4. Announce the change to screen readers via a visually-hidden + // aria-live="polite" region injected once into the DOM. + var liveRegion = document.getElementById("theme-announce"); + if (!liveRegion) { + liveRegion = document.createElement("span"); + liveRegion.id = "theme-announce"; + // Visually hidden but readable by screen readers + liveRegion.setAttribute("role", "status"); + liveRegion.setAttribute("aria-live", "polite"); + liveRegion.style.cssText = + "position:absolute;width:1px;height:1px;padding:0;overflow:hidden;" + + "clip:rect(0,0,0,0);white-space:nowrap;border:0;"; + document.body.appendChild(liveRegion); + } + liveRegion.textContent = isDark ? "Dark mode enabled." : "Light mode enabled."; + } + + + document.addEventListener("click", function (evt) { + var btn = evt.target.closest(".theme-toggle"); + if (!btn) return; + var current = document.documentElement.dataset.theme || "light"; + applyTheme(current === "dark" ? "light" : "dark"); + }); + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initTheme); + } else { + initTheme(); + } + +}()); + + +// ============================================================ +// Dark Mode Toggle & Synchronization +// ============================================================ +// UX Behavior Design Note: +// 1. System Preference Sync: By default, the application respects the OS dark/light mode settings +// (using matchMedia("(prefers-color-scheme: dark)")). +// 2. Manual Override (Intentional UX Pattern): Once a user explicitly chooses a theme by clicking the toggle, +// their manual preference is cached in localStorage. This manual choice intentionally takes precedence +// over the system preferences to provide a stable, consistent theme across sessions. +// 3. System Re-sync: If the user wishes to revert back to system tracking, they can clear their browser data/localStorage. +// The media query listener will automatically resume tracking system preferences when no localStorage key exists. +(function initTheme() { + var toggle = document.getElementById("theme-toggle"); + var html = document.documentElement; + var sunIcon = toggle && toggle.querySelector(".theme-toggle-sun"); + var moonIcon = toggle && toggle.querySelector(".theme-toggle-moon"); + + function getPreferredTheme() { + var saved = localStorage.getItem("theme"); + if (saved) return saved; + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + } + + function setTheme(theme) { + html.setAttribute("data-theme", theme); + localStorage.setItem("theme", theme); + if (toggle) { + // Dynamic accessibility tracking using aria-pressed (true if dark mode is active) + toggle.setAttribute("aria-pressed", theme === "dark" ? "true" : "false"); + if (sunIcon && moonIcon) { + if (theme === "dark") { + sunIcon.style.display = "none"; + moonIcon.style.display = "inline"; + } else { + sunIcon.style.display = "inline"; + moonIcon.style.display = "none"; + } + } + } + } + + // Active theme is already initialized in to prevent Flash of Unstyled Content (FOUC). + // We sync buttons and accessibility attributes based on the current state. + var activeTheme = html.getAttribute("data-theme") || getPreferredTheme(); + setTheme(activeTheme); + + if (toggle) { + toggle.addEventListener("click", function () { + var current = html.getAttribute("data-theme") || "light"; + setTheme(current === "dark" ? "light" : "dark"); + }); + } + + window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function (e) { + // Only sync dynamic system changes if no manual preference currently overrides it. + if (!localStorage.getItem("theme")) { + setTheme(e.matches ? "dark" : "light"); + } + }); +})(); + // ============================================================ // Detect which page we are on // ============================================================ @@ -22,13 +186,295 @@ var fetchBtn = document.getElementById('btn-fetch-github'); var githubInput = document.getElementById('github-username'); var errorMsg = document.getElementById('github-modal-error'); +var STORAGE_KEY = "devpathUserProgress"; +var progress = { + searches: 0, + projectViews: 0, + codeOpens: 0, + completions: 0, + points: 0, + viewedProjects: [], + completedProjects: [], + badges: { + first_search: false, + project_explorer: false, + code_starter: false, + completionist: false, + roadmap_runner: false + }, + achievements: [], + bestScore: 0 +}; + +var achievementToast = null; +var achievementToastTimeout = null; + +function loadProgressState() { + try { + var raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return; + var saved = JSON.parse(raw); + if (saved && typeof saved === "object") { + progress = Object.assign(progress, saved); + progress.viewedProjects = Array.isArray(saved.viewedProjects) ? saved.viewedProjects : []; + progress.completedProjects = Array.isArray(saved.completedProjects) ? saved.completedProjects : []; + progress.achievements = Array.isArray(saved.achievements) ? saved.achievements : []; + progress.badges = Object.assign(progress.badges, saved.badges || {}); + } + } catch (err) { + console.warn("Unable to load progress state", err); + } +} + +function saveProgressState() { + try { + progress.bestScore = Math.max(progress.bestScore, progress.points); + localStorage.setItem(STORAGE_KEY, JSON.stringify(progress)); + } catch (err) { + console.warn("Unable to save progress state", err); + } +} + +function resetProgressState() { + progress = { + searches: 0, + projectViews: 0, + codeOpens: 0, + completions: 0, + points: 0, + viewedProjects: [], + completedProjects: [], + badges: { + first_search: false, + project_explorer: false, + code_starter: false, + completionist: false, + roadmap_runner: false + }, + achievements: [], + bestScore: 0 + }; + saveProgressState(); + updateProfileWidgets(); + showAchievementToast("Progress reset", "Your local profile has been cleared."); +} + +function computeProgressPoints() { + progress.points = progress.searches * 5 + progress.projectViews * 10 + progress.codeOpens * 15 + progress.completions * 30; +} + +function addAchievement(title, description) { + if (!title || !description) return; + var exists = progress.achievements.some(function (achievement) { return achievement.title === title; }); + if (exists) return; + + progress.achievements.unshift({ + title: title, + description: description, + date: new Date().toLocaleDateString() + }); + + if (progress.achievements.length > 5) { + progress.achievements.pop(); + } +} + +function showAchievementToast(title, detail) { + if (!achievementToast) { + achievementToast = document.getElementById("achievement-toast"); + } + if (!achievementToast) return; + + achievementToast.innerHTML = "" + title + "" + + "" + detail + ""; + achievementToast.classList.add("show"); + + clearTimeout(achievementToastTimeout); + achievementToastTimeout = setTimeout(function () { + achievementToast.classList.remove("show"); + }, 3200); +} + +function updateProfileWidgets() { + var pointsEl = document.getElementById("progress-points"); + var statsEl = document.getElementById("progress-stats"); + var meterFill = document.getElementById("progress-meter-fill"); + var badgesEl = document.getElementById("progress-badges"); + var achievementList = document.getElementById("achievement-list"); + var leaderboardList = document.getElementById("leaderboard-list"); + var historyList = document.getElementById("completed-history-list"); + var completionBtn = document.getElementById("btn-mark-complete"); + + if (pointsEl) pointsEl.textContent = progress.points; + if (statsEl) { + statsEl.innerHTML = + " element inside the panel where the code will be inserted
+ var codePanelFilename = document.getElementById("code-panel-filename"); // filename display
+ var btnViewCode = document.getElementById("btn-view-code"); // button to open the code panel on desktop
+ var btnViewCodeSm = document.getElementById("btn-view-code-sm"); // button to open the code panel on mobile (could be the same button with different styling, but we have two here for simplicity)
+ var btnClosePanel = document.getElementById("code-panel-close"); // button inside the panel to close it
+
+ // Cache flag so code is only fetched once per page load
+ var codeFetched = false;
+
+ //opens the sliding code panel
+ function openCodePanel() {
+ // Panel element might not exist on every detail page, so check first
+ if (!codePanel) return;
+ codePanel.classList.add("active");
+ if (codePanelOverlay) codePanelOverlay.classList.add("active");
+ // Lock background scroll so the page doesn't scroll behind the panel
+ document.body.style.overflow = "hidden";
+
+ // Only fetch the code on the first open, no need to re-fetch every time
+ if (!codeFetched) fetchStarterCode();
+ }
+ //closes the code panel and hides the overlay
+ function closeCodePanel() {
+ if (!codePanel) return;
+ codePanel.classList.remove("active");
+ if (codePanelOverlay) codePanelOverlay.classList.remove("active");
+ // Restore normal scrolling once the panel is closed
+ document.body.style.overflow = "";
+ }
+ // Render code string as a list of DOM rows where each row contains a
+ // line-number gutter cell and a code cell. Returning DOM nodes instead
+ // of an HTML string avoids innerHTML XSS risks from the code content.
+ function renderCodeWithLineNumbers(code) {
+ var lines = (code || "").split("\n");
+ return lines.map(function (line, index) {
+ var row = document.createElement("div");
+ row.className = "code-line";
+
+ var lineNum = document.createElement("span");
+ lineNum.className = "code-line-number";
+ lineNum.setAttribute("aria-hidden", "true");
+ lineNum.textContent = index + 1;
+
+ var lineCode = document.createElement("span");
+ lineCode.className = "code-line-content";
+ lineCode.textContent = line;
+
+ row.appendChild(lineNum);
+ row.appendChild(lineCode);
+ return row;
+ });
+ }
+
+ //fetches the starter code from the server via an API call
+ //inserts the code into the panel and handles loading/error states
+ function fetchStarterCode() {
+ // Show a loading message while we wait for the API response
+ if (codeContentEl) codeContentEl.textContent = "Loading starter code...";
+
+ fetch("/project/" + PROJECT_ID + "/code")
+ .then(function (res) { return res.json(); })
+ .then(function (data) {
+ if (data.error) {
+ if (codeContentEl) codeContentEl.textContent = "Error: " + data.error;
+ return;
+ }
+ if (codePanelFilename) codePanelFilename.textContent = data.filename;
+ if (codeContentEl) {
+ codeContentEl.textContent = "";
+ renderCodeWithLineNumbers(data.code).forEach(function (row) {
+ codeContentEl.appendChild(row);
+ });
+ }
+ // Mark as fetched so we don't hit the API again on the next open
+ codeFetched = true;
+ })
+ .catch(function () {
+ if (codeContentEl) {
+ codeContentEl.textContent = "Could not load starter code. Try downloading it instead.";
+ }
+ });
+ }
+
+ // ============================================================
+// ROADMAP PROGRESS TRACKER
// ============================================================
-// DETAIL PAGE
-// ============================================================
-if (isDetailPage) {
-
- var codePanel = document.getElementById("code-panel"); // sliding panel that shows the starter code "
- var codePanelOverlay = document.getElementById("code-panel-overlay"); // background overlay
- var codeContentEl = document.getElementById("code-content"); // element inside the panel where the code will be inserted
- var codePanelFilename = document.getElementById("code-panel-filename"); // filename display
- var btnViewCode = document.getElementById("btn-view-code"); // button to open the code panel on desktop
- var btnViewCodeSm = document.getElementById("btn-view-code-sm"); // button to open the code panel on mobile (could be the same button with different styling, but we have two here for simplicity)
- var btnClosePanel = document.getElementById("code-panel-close"); // button inside the panel to close it
-
- // Cache flag so code is only fetched once per page load
- var codeFetched = false;
-
- //opens the sliding code panel
- function openCodePanel() {
- // Panel element might not exist on every detail page, so check first
- if (!codePanel) return;
- codePanel.classList.add("active");
- if (codePanelOverlay) codePanelOverlay.classList.add("active");
- // Lock background scroll so the page doesn't scroll behind the panel
- document.body.style.overflow = "hidden";
-
- // Only fetch the code on the first open, no need to re-fetch every time
- if (!codeFetched) fetchStarterCode();
- }
-
- //closes the code panel and hides the overlay
- function closeCodePanel() {
- if (!codePanel) return;
- codePanel.classList.remove("active");
- if (codePanelOverlay) codePanelOverlay.classList.remove("active");
- // Restore normal scrolling once the panel is closed
- document.body.style.overflow = "";
- }
-
- //fetches the starter code from the server via an API call
- //inserts the code into the panel and handles loading/error states
- function fetchStarterCode() {
- // Show a loading message while we wait for the API response
- if (codeContentEl) codeContentEl.textContent = "Loading starter code...";
-
- fetch("/project/" + PROJECT_ID + "/code")
- .then(function (res) { return res.json(); })
- .then(function (data) {
- if (data.error) {
- if (codeContentEl) codeContentEl.textContent = "Error: " + data.error;
- return;
- }
- if (codePanelFilename) codePanelFilename.textContent = data.filename;
- if (codeContentEl) {
- codeContentEl.textContent = "";
- renderCodeWithLineNumbers(data.code).forEach(function (row) {
- codeContentEl.appendChild(row);
- });
- }
- // Mark as fetched so we don't hit the API again on the next open
- codeFetched = true;
- })
- .catch(function () {
- if (codeContentEl) {
- codeContentEl.textContent = "Could not load starter code. Try downloading it instead.";
+
+
+var roadmapCheckboxes = document.querySelectorAll(
+ ".roadmap-checkbox"
+);
+
+var progressFill = document.getElementById(
+ "roadmap-progress-fill"
+);
+
+var progressText = document.getElementById(
+ "roadmap-progress-text"
+);
+
+var progressBar = document.querySelector(
+ ".roadmap-progress-bar"
+);
+
+// Local storage key
+var roadmapStorageKey =
+ `devpath-roadmap-progress-${PROJECT_ID}`;
+
+
+// ------------------------------------------------------------
+// Restore saved roadmap state
+// ------------------------------------------------------------
+
+var savedRoadmapState =
+ localStorage.getItem(
+ roadmapStorageKey
+ );
+
+if(savedRoadmapState){
+
+ try{
+
+ var parsedState =
+ JSON.parse(savedRoadmapState);
+
+ roadmapCheckboxes.forEach(
+ function(cb,index){
+
+ cb.checked =
+ !!parsedState[index];
+
+ }
+ );
+
+ } catch(error){
+
+ console.error(
+ "Failed to restore roadmap progress",
+ error
+ );
+
+ }
+}
+
+
+// ------------------------------------------------------------
+// Update roadmap progress
+// ------------------------------------------------------------
+
+function updateRoadmapProgress(){
+
+ if(!roadmapCheckboxes.length){
+ return;
+ }
+
+ var completed = 0;
+
+ roadmapCheckboxes.forEach(function(cb){
+
+ var step = cb.closest(
+ ".roadmap-step"
+ );
+
+ if(cb.checked){
+
+ completed++;
+
+ if(step){
+ step.classList.add(
+ "completed"
+ );
+ }
+
+ } else {
+
+ if(step){
+ step.classList.remove(
+ "completed"
+ );
+ }
+
}
- });
- }
- // Attach open/close handlers
- if (btnViewCode) btnViewCode.addEventListener("click", openCodePanel);
- if (btnViewCodeSm) btnViewCodeSm.addEventListener("click", openCodePanel);
- if (btnClosePanel) btnClosePanel.addEventListener("click", closeCodePanel);
+ });
- if (codePanelOverlay) {
- codePanelOverlay.addEventListener("click", closeCodePanel); //clicking on the background overlay to also close the panel
- }
+ var percent = Math.round(
+ (completed / roadmapCheckboxes.length)
+ * 100
+ );
- // Let keyboard users close the panel with Escape — important for accessibility
- document.addEventListener("keydown", function (evt) {
- if (evt.key === "Escape") closeCodePanel(); //esc key to close
- });
+ // Update progress bar fill
+ if(progressFill){
+
+ progressFill.style.width =
+ percent + "%";
+
+ }
+
+ // Update progress text
+ if(progressText){
+
+ progressText.textContent =
+ percent + "% completed";
+
+ }
+
+ // Accessibility update
+ if(progressBar){
+
+ progressBar.setAttribute(
+ "aria-valuenow",
+ percent
+ );
+
+ }
+
+ // Save checkbox state
+ var savedState = [];
+
+ roadmapCheckboxes.forEach(function(cb){
+
+ savedState.push(
+ cb.checked
+ );
- // ----------------------------------------------------------
- // Copy Code button
- // ----------------------------------------------------------
- var btnCopyCode = document.getElementById("btn-copy-code");
- var copyToast = document.getElementById("copy-toast"); //popup msg when copied
- var toastTimeout = null;
-
- //shows the "copied to clipboard" state on the button and the toast message, then resets after a short delay
- function showCopySuccess() {
- if (!btnCopyCode) return;
-
- // Swap icons on the button(copy and checkmark icons)
- var copyIcon = btnCopyCode.querySelector(".copy-icon");
- var checkIcon = btnCopyCode.querySelector(".check-icon");
- var btnLabel = btnCopyCode.querySelector(".copy-btn-label");
-
- if (copyIcon) copyIcon.style.display = "none";
- if (checkIcon) checkIcon.style.display = "inline";
- if (btnLabel) btnLabel.textContent = "Copied!";
- btnCopyCode.classList.add("copied");
- // Disable button so user can't spam click it while toast is showing
- btnCopyCode.disabled = true;
-
- // Show toast
- if (copyToast) {
- copyToast.classList.add("show");
- }
-
- // Auto-reset after 2.5 s
- // Clear any previous timeout first so timers don't stack up
- clearTimeout(toastTimeout);
- toastTimeout = setTimeout(function () {
- if (copyIcon) copyIcon.style.display = "inline";
- if (checkIcon) checkIcon.style.display = "none";
- if (btnLabel) btnLabel.textContent = "Copy Code";
- btnCopyCode.classList.remove("copied");
- btnCopyCode.disabled = false;
- if (copyToast) copyToast.classList.remove("show");
- }, 2500);
- }
-
- if (btnCopyCode) {
- btnCopyCode.addEventListener("click", function () {
- var code = codeContentEl
- ? Array.prototype.slice.call(codeContentEl.querySelectorAll(".line-content"))
- .map(function (el) { return el.textContent; })
- .join("\n")
- : "";
- // Don't copy if the code hasn't loaded yet — just ignore the click
- if (!code || code === "Loading..." || code === "Loading starter code...") return;
-
- // Use Clipboard API with textarea fallback
- if (navigator.clipboard && navigator.clipboard.writeText) {
- navigator.clipboard.writeText(code).then(showCopySuccess).catch(function () {
- fallbackCopy(code); // clipboard api failed, try the old way
- });
- } else {
- fallbackCopy(code); // Clipboard API not supported, use fallback method
- }
});
- }
- // Fallback method to copy text using a hidden textarea and execCommand (for older browsers)
- function fallbackCopy(text) {
- // Some older browsers don't support navigator.clipboard, so we use a hidden textarea instead
- var ta = document.createElement("textarea");
- ta.value = text;
- // Push it off-screen so it's not visible but can still be selected
- ta.style.cssText = "position:fixed;top:-9999px;left:-9999px;opacity:0";
- document.body.appendChild(ta);
- ta.focus();
- ta.select();
- // execCommand is old and deprecated but works as a last resort — fail silently if it doesn't
- try { document.execCommand("copy"); showCopySuccess(); } catch (e) { /* silent fail */ }
- document.body.removeChild(ta);
- }
-} // end isDetailPage
+ localStorage.setItem(
+ roadmapStorageKey,
+ JSON.stringify(savedState)
+ );
+
+}
+
+
+// ------------------------------------------------------------
+// Attach checkbox listeners
+// ------------------------------------------------------------
+
+roadmapCheckboxes.forEach(function(cb){
+
+ cb.addEventListener(
+ "change",
+ updateRoadmapProgress
+ );
+
+});
+
+
+// ------------------------------------------------------------
+// Initial progress render
+// ------------------------------------------------------------
+
+updateRoadmapProgress();
+
+ // Attach open/close handlers
+ if (btnViewCode) btnViewCode.addEventListener("click", openCodePanel);
+ if (btnViewCodeSm) btnViewCodeSm.addEventListener("click", openCodePanel);
+ if (btnClosePanel) btnClosePanel.addEventListener("click", closeCodePanel);
+
+ if (codePanelOverlay) {
+ codePanelOverlay.addEventListener("click", closeCodePanel); //clicking on the background overlay to also close the panel
+ }
+
+ // Let keyboard users close the panel with Escape — important for accessibility
+ document.addEventListener("keydown", function (evt) {
+ if (evt.key === "Escape") closeCodePanel(); //esc key to close
+ });
+
+ // ----------------------------------------------------------
+ // Copy Code button
+ // ----------------------------------------------------------
+ var btnCopyCode = document.getElementById("btn-copy-code");
+ var copyToast = document.getElementById("copy-toast"); //popup msg when copied
+ var toastTimeout = null;
+
+ //shows the "copied to clipboard" state on the button and the toast message, then resets after a short delay
+ function showCopySuccess() {
+ if (!btnCopyCode) return;
+
+ // Swap icons on the button(copy and checkmark icons)
+ var copyIcon = btnCopyCode.querySelector(".copy-icon");
+ var checkIcon = btnCopyCode.querySelector(".check-icon");
+ var btnLabel = btnCopyCode.querySelector(".copy-btn-label");
+
+ if (copyIcon) copyIcon.style.display = "none";
+ if (checkIcon) checkIcon.style.display = "inline";
+ if (btnLabel) btnLabel.textContent = "Copied!";
+ btnCopyCode.classList.add("copied");
+ // Disable button so user can't spam click it while toast is showing
+ btnCopyCode.disabled = true;
+
+ // Show toast
+ if (copyToast) {
+ copyToast.classList.add("show");
+ }
+
+ // Auto-reset after 2.5 s
+ // Clear any previous timeout first so timers don't stack up
+ clearTimeout(toastTimeout);
+ toastTimeout = setTimeout(function () {
+ if (copyIcon) copyIcon.style.display = "inline";
+ if (checkIcon) checkIcon.style.display = "none";
+ if (btnLabel) btnLabel.textContent = "Copy Code";
+ btnCopyCode.classList.remove("copied");
+ btnCopyCode.disabled = false;
+ if (copyToast) copyToast.classList.remove("show");
+ }, 2500);
+ }
-if (
+ if (btnCopyCode) {
+ btnCopyCode.addEventListener("click", function () {
+ var code = codeContentEl
+ ? Array.from(codeContentEl.querySelectorAll(".line-content"))
+ .map(function (el) { return el.textContent; })
+ .join("\n")
+ : "";
+ // Don't copy if the code hasn't loaded yet — just ignore the click
+ if (!code || code === "Loading..." || code === "Loading starter code...") return;
+
+ // Use Clipboard API with textarea fallback
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(code).then(showCopySuccess).catch(function () {
+ fallbackCopy(code); // clipboard api failed, try the old way
+ });
+ } else {
+ fallbackCopy(code); // Clipboard API not supported, use fallback method
+ }
+ });
+ }
+
+ // Fallback method to copy text using a hidden textarea and execCommand (for older browsers)
+ function fallbackCopy(text) {
+ // Some older browsers don't support navigator.clipboard, so we use a hidden textarea instead
+ var ta = document.createElement("textarea");
+ ta.value = text;
+ // Push it off-screen so it's not visible but can still be selected
+ ta.style.cssText = "position:fixed;top:-9999px;left:-9999px;opacity:0";
+ document.body.appendChild(ta);
+ ta.focus();
+ ta.select();
+ // execCommand is old and deprecated but works as a last resort — fail silently if it doesn't
+ try { document.execCommand("copy"); showCopySuccess(); } catch (e) { /* silent fail */ }
+ document.body.removeChild(ta);
+ }
+ } // end isDetailPage
+
+if (isIndexPage) {
+ if (
openModalBtn &&
closeModalBtn &&
modal &&
githubInput &&
fetchBtn &&
errorMsg
-) {
-// 1. Open Github Input Modal
- openModalBtn.addEventListener('click', (e) => {
+ ) {
+ // Opens the GitHub input modal and focuses the input field
+ openModalBtn.addEventListener("click", function(e) {
e.preventDefault();
- modal.classList.add('active');
+ modal.classList.add("active");
githubInput.focus();
- });
+ });
- // 2. Close Github Input Modal
- const closeGithubModal = () => {
- modal.classList.remove('active');
- githubInput.value = '';
- errorMsg.textContent = '';
- };
+ // Closes the GitHub input modal and resets its values and errors
+ var closeGithubModal = function() {
+ modal.classList.remove("active");
+ githubInput.value = "";
+ errorMsg.textContent = "";
+ };
- closeModalBtn.addEventListener('click', closeGithubModal);
+ closeModalBtn.addEventListener("click", closeGithubModal);
- // Close on clicking outside the card
- modal.addEventListener('click', (e) => {
+ // Closes the modal if the user clicks the background overlay outside the card
+ modal.addEventListener("click", function(e) {
if (e.target === modal) closeGithubModal();
- });
+ });
+
+ // Fetches repositories for a GitHub user and adds unique languages to the skills list
+ fetchBtn.addEventListener("click", function() {
+ var username = githubInput.value.trim();
- // 3. Fetch Skills Logic
- fetchBtn.addEventListener('click', async () => {
- const username = githubInput.value.trim();
- if (!username) return;
+ // Clear any previous error before validating / retrying
+ errorMsg.textContent = "";
+
+ if (!username) {
+ errorMsg.textContent = "Please enter a GitHub username";
+ githubInput.focus();
+ return;
+ }
fetchBtn.disabled = true;
- fetchBtn.textContent = 'Syncing...';
+ fetchBtn.textContent = "Syncing...";
- try {
- const response = await fetch(`https://api.github.com/users/${username}/repos`);
- if (!response.ok) throw new Error();
-
- const repos = await response.json();
- const langs = [...new Set(repos.map(r => r.language).filter(Boolean))];
+ fetch("https://api.github.com/users/" + encodeURIComponent(username) + "/repos")
+ .then(function(response) {
+ if (!response.ok) {
+ if (response.status === 404) {
+ throw new Error("Username not found. Please check and try again.");
+ }
+ if (response.status === 403) {
+ throw new Error("GitHub rate limit reached. Please try again later.");
+ }
+ throw new Error("Failed to fetch skills. Please try again.");
+ }
+ return response.json();
+ })
+ .then(function(repos) {
+ var langs = [];
+ for (var i = 0; i < repos.length; i++) {
+ var lang = repos[i].language;
+ if (lang && langs.indexOf(lang) === -1) {
+ langs.push(lang);
+ }
+ }
if (langs.length > 0) {
- langs.forEach(lang => {
- if (typeof addSkill === 'function') addSkill(lang);
- });
- closeGithubModal();
+ for (var j = 0; j < langs.length; j++) {
+ if (typeof addSkill === "function") addSkill(langs[j]);
+ }
+ closeGithubModal();
} else {
- errorMsg.textContent = "No public languages found.";
+ errorMsg.textContent = "No public languages found.";
}
- } catch (err) {
- errorMsg.textContent = err.message ?? "Failed to fetch skills";
- } finally {
fetchBtn.disabled = false;
- fetchBtn.textContent = 'Fetch Skills';
- }
- });
+ fetchBtn.textContent = "Fetch Skills";
+ })
+ .catch(function(err) {
+ errorMsg.textContent = err.message || "Failed to fetch skills";
+ fetchBtn.disabled = false;
+ fetchBtn.textContent = "Fetch Skills";
+ });
+ });
+ } // end github modal handlers
}
-/* ---- Scroll-to-top button ---- */
-/* Show the button only when the user has scrolled more than 300px */
-var SCROLL_THRESHOLD = 300;
+// ============================================================
+// SCROLL NAVIGATION BUTTON (runs on all pages)
+// ============================================================
+(function () {
+ var SCROLL_THRESHOLD = 200;
+ var scrollTopBtn = document.getElementById('scroll-top-btn');
+ var scrollBtnIcon = document.getElementById('scroll-btn-icon');
+ var atBottom = false;
-/* Get the button element; guard against pages that do not have it */
-var scrollTopBtn = document.getElementById('scroll-top-btn');
+ var ARROW_UP = ' ';
+ var ARROW_DOWN = ' ';
-/* Add or remove the .visible class based on scroll position */
-function handleScroll() {
- if (!scrollTopBtn) return;
- if (window.pageYOffset > SCROLL_THRESHOLD) {
- scrollTopBtn.classList.add('visible');
- } else {
- scrollTopBtn.classList.remove('visible');
+ function isNearBottom() {
+ return (window.innerHeight + window.pageYOffset) >= document.body.scrollHeight - 40;
}
-}
-/* Smooth-scroll to the very top of the page */
-function scrollToTop() {
- window.scrollTo({ top: 0, behavior: 'smooth' });
-}
+ function handleScroll() {
+ if (!scrollTopBtn) return;
+ if (window.pageYOffset > SCROLL_THRESHOLD) {
+ scrollTopBtn.classList.add('visible');
+ } else {
+ scrollTopBtn.classList.remove('visible');
+ }
+ if (isNearBottom()) {
+ atBottom = true;
+ scrollTopBtn.setAttribute('aria-label', 'Scroll to top');
+ scrollTopBtn.title = 'Scroll to top';
+ if (scrollBtnIcon) scrollBtnIcon.innerHTML = ARROW_UP;
+ } else {
+ atBottom = false;
+ scrollTopBtn.setAttribute('aria-label', 'Scroll to bottom');
+ scrollTopBtn.title = 'Scroll to bottom';
+ if (scrollBtnIcon) scrollBtnIcon.innerHTML = ARROW_DOWN;
+ }
+ }
-/* Only wire up listeners if the button exists on this page */
-if (scrollTopBtn) {
- window.addEventListener('scroll', handleScroll);
- scrollTopBtn.addEventListener('click', scrollToTop);
-}
+ if (scrollTopBtn) {
+ window.addEventListener('scroll', handleScroll, { passive: true });
+ scrollTopBtn.addEventListener('click', function () {
+ if (atBottom) {
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ } else {
+ window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
+ }
+ });
+ handleScroll();
+ }
+
+})();
diff --git a/app.py b/app.py
index f5e5a749..de4bca85 100644
--- a/app.py
+++ b/app.py
@@ -16,6 +16,13 @@
app = Flask(__name__)
+@app.template_filter('escapejs')
+def escapejs_filter(val):
+ """Safely escape strings for use inside JavaScript."""
+ if val is None:
+ return ''
+ return str(val).replace('\\', '\\\\').replace("'", "\\'").replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r').replace('', '<\\/')
+
# Register all routes defined in the main Blueprint
app.register_blueprint(main)
diff --git a/data/projects.json b/data/projects.json
index e82b476d..8c96f249 100644
--- a/data/projects.json
+++ b/data/projects.json
@@ -1,1217 +1,999 @@
-[
- {
- "id": 1,
- "title": "Personal Expense Tracker",
- "skills": [
- "Python"
- ],
- "level": "Beginner",
- "interest": "Data",
- "time": "Low",
- "description": "A command-line tool that helps users track daily expenses, categorize spending, and generate simple summary reports. Great for learning file handling, loops, and basic data processing.",
- "features": [
- "Add and delete expense entries",
- "Categorize expenses (food, transport, bills)",
- "View monthly summary",
- "Export data to CSV file"
- ],
- "tech_stack": [
- "Python",
- "CSV module",
- "datetime module"
- ],
- "roadmap": [
- "Step 1: Set up the project folder and create main.py",
- "Step 2: Design the expense data structure as a dictionary",
- "Step 3: Write functions to add and delete expenses",
- "Step 4: Implement category filtering logic",
- "Step 5: Write the summary report generator",
- "Step 6: Add CSV export functionality",
- "Step 7: Test with sample data and fix bugs"
- ],
- "resources": [
- "Python official docs: https://docs.python.org",
- "CSV module guide: https://docs.python.org/3/library/csv.html",
- "Real Python beginner tutorials: https://realpython.com"
- ],
- "starter_code": "starter_code/expense_tracker.py"
- },
- {
- "id": 2,
- "title": "Weather Dashboard",
- "skills": [
- "JavaScript",
- "HTML",
- "CSS"
- ],
- "level": "Beginner",
- "interest": "Web",
- "time": "Low",
- "description": "A simple web page that fetches weather data from a free API and displays current conditions for any city. Teaches API calls, DOM manipulation, and basic UI design.",
- "features": [
- "Search weather by city name",
- "Display temperature, humidity, and conditions",
- "Show a weather icon based on conditions",
- "Toggle between Celsius and Fahrenheit"
- ],
- "tech_stack": [
- "HTML",
- "CSS",
- "JavaScript",
- "OpenWeatherMap API"
- ],
- "roadmap": [
- "Step 1: Create the HTML structure with a search form",
- "Step 2: Style the page with CSS flexbox",
- "Step 3: Sign up for a free OpenWeatherMap API key",
- "Step 4: Write the fetch() call to get weather data",
- "Step 5: Parse the JSON response and extract key fields",
- "Step 6: Display the data dynamically using DOM methods",
- "Step 7: Add the Celsius/Fahrenheit toggle button"
- ],
- "resources": [
- "MDN Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API",
- "OpenWeatherMap free tier: https://openweathermap.org/api",
- "CSS Flexbox guide: https://css-tricks.com/snippets/css/a-guide-to-flexbox"
- ],
- "starter_code": "starter_code/weather_dashboard.html"
- },
- {
- "id": 3,
- "title": "Student Grade Manager",
- "skills": [
- "Python"
- ],
- "level": "Beginner",
- "interest": "Education",
- "time": "Medium",
- "description": "A Python application to store student names and their grades, compute averages, and display a class report. Ideal for practicing data structures, functions, and file persistence.",
- "features": [
- "Add students and assign grades per subject",
- "Calculate individual and class averages",
- "Assign letter grades automatically",
- "Save and load data from a JSON file"
- ],
- "tech_stack": [
- "Python",
- "json module",
- "os module"
- ],
- "roadmap": [
- "Step 1: Define the student data structure using a dictionary",
- "Step 2: Write add_student() and add_grade() functions",
- "Step 3: Implement average calculation logic",
- "Step 4: Create a letter grade converter function",
- "Step 5: Build the JSON save/load functions",
- "Step 6: Create a simple text menu for user interaction",
- "Step 7: Write a class report printer function"
- ],
- "resources": [
- "Python JSON module: https://docs.python.org/3/library/json.html",
- "Python functions tutorial: https://realpython.com/defining-your-own-python-function",
- "W3Schools Python: https://www.w3schools.com/python"
- ],
- "starter_code": "starter_code/grade_manager.py"
- },
- {
- "id": 4,
- "title": "Task Manager REST API",
- "skills": [
- "Python"
- ],
- "level": "Intermediate",
- "interest": "Web",
- "time": "Medium",
- "description": "A RESTful API built with Flask that allows clients to create, read, update, and delete tasks. Perfect for learning API design, HTTP methods, and JSON responses.",
- "features": [
- "CRUD endpoints for tasks",
- "Filter tasks by status (pending, done)",
- "Assign priority levels to tasks",
- "Persist data to a JSON file"
- ],
- "tech_stack": [
- "Python",
- "Flask",
- "JSON",
- "Postman (for testing)"
- ],
- "roadmap": [
- "Step 1: Install Flask and create the app.py file",
- "Step 2: Define the task data model as a dictionary",
- "Step 3: Create the GET /tasks endpoint to list all tasks",
- "Step 4: Create the POST /tasks endpoint to add a task",
- "Step 5: Create PUT /tasks/ to update a task",
- "Step 6: Create DELETE /tasks/ to remove a task",
- "Step 7: Add JSON file persistence for saving tasks",
- "Step 8: Test all endpoints using Postman or curl"
- ],
- "resources": [
- "Flask quickstart: https://flask.palletsprojects.com/quickstart",
- "REST API design guide: https://restfulapi.net",
- "Postman download: https://www.postman.com/downloads"
- ],
- "starter_code": "starter_code/task_api.py"
- },
- {
- "id": 5,
- "title": "Portfolio Website",
- "skills": [
- "HTML",
- "CSS",
- "JavaScript"
- ],
- "level": "Beginner",
- "interest": "Web",
- "time": "Low",
- "description": "A personal portfolio site with sections for bio, projects, and contact. A great first project that teaches HTML layout, CSS styling, and a bit of JavaScript for interactivity.",
- "features": [
- "Hero section with name and tagline",
- "Projects grid with cards",
- "Skills list with visual indicators",
- "Contact form with basic validation"
- ],
- "tech_stack": [
- "HTML",
- "CSS",
- "JavaScript"
- ],
- "roadmap": [
- "Step 1: Plan the page sections on paper first",
- "Step 2: Write the HTML structure for all sections",
- "Step 3: Add CSS reset and base typography styles",
- "Step 4: Style the navigation and hero section",
- "Step 5: Build the projects grid using CSS Grid",
- "Step 6: Add the contact form with labels and inputs",
- "Step 7: Write JavaScript for form validation",
- "Step 8: Make the site responsive with media queries"
- ],
- "resources": [
- "HTML reference: https://developer.mozilla.org/en-US/docs/Web/HTML",
- "CSS Grid guide: https://css-tricks.com/snippets/css/complete-guide-grid",
- "Responsive design basics: https://web.dev/learn/design"
- ],
- "starter_code": "starter_code/portfolio.html"
- },
- {
- "id": 6,
- "title": "URL Shortener",
- "skills": [
- "Python",
- "JavaScript",
- "HTML",
- "CSS"
- ],
- "level": "Intermediate",
- "interest": "Web",
- "time": "High",
- "description": "A full-stack web app that takes long URLs and generates short codes. Users can paste a link and get a shorter one back. Teaches Flask routing, random code generation, and front-end form handling.",
- "features": [
- "Shorten any valid URL",
- "Redirect short codes to original URL",
- "Track how many times a link was clicked",
- "List all shortened links in a dashboard"
- ],
- "tech_stack": [
- "Python",
- "Flask",
- "HTML",
- "CSS",
- "JavaScript",
- "JSON"
- ],
- "roadmap": [
- "Step 1: Set up Flask app with two routes: home and redirect",
- "Step 2: Write a random 6-character code generator",
- "Step 3: Store URL mappings in a JSON file",
- "Step 4: Build the HTML form for pasting long URLs",
- "Step 5: Display the shortened URL after submission",
- "Step 6: Implement the redirect route using the short code",
- "Step 7: Add a click counter that updates on each visit",
- "Step 8: Build a simple dashboard to list all links"
- ],
- "resources": [
- "Flask routing docs: https://flask.palletsprojects.com/en/stable/quickstart/#routing",
- "Python secrets module: https://docs.python.org/3/library/secrets.html",
- "UUID in Python: https://docs.python.org/3/library/uuid.html"
- ],
- "starter_code": "starter_code/url_shortener.py"
- },
- {
- "id": 7,
- "title": "Data Analysis Report Generator",
- "skills": [
- "Python"
- ],
- "level": "Intermediate",
- "interest": "Data",
- "time": "High",
- "description": "Upload a CSV file and automatically generate a summary report with statistics, missing value counts, and basic charts. A practical project for learning data wrangling and pandas.",
- "features": [
- "Load and inspect CSV files",
- "Show column types and null counts",
- "Calculate mean, median, and mode per column",
- "Generate bar charts for categorical data"
- ],
- "tech_stack": [
- "Python",
- "pandas",
- "matplotlib",
- "os module"
- ],
- "roadmap": [
- "Step 1: Install pandas and matplotlib via pip",
- "Step 2: Write a CSV loader that validates the file path",
- "Step 3: Generate a summary table of column info",
- "Step 4: Compute descriptive statistics for numeric columns",
- "Step 5: Count and display missing values per column",
- "Step 6: Build chart generation functions using matplotlib",
- "Step 7: Export the full report to a text or HTML file"
- ],
- "resources": [
- "pandas docs: https://pandas.pydata.org/docs",
- "matplotlib tutorials: https://matplotlib.org/stable/tutorials",
- "Real Python data analysis: https://realpython.com/pandas-dataframe"
- ],
- "starter_code": "starter_code/data_report.py"
- },
- {
- "id": 8,
- "title": "Library Management System",
- "skills": [
- "Java"
- ],
- "level": "Beginner",
- "interest": "Backend",
- "time": "Medium",
- "description": "A Java application that helps manage books, students, and borrowing records in a library. This project teaches object-oriented programming concepts, file handling, and menu-driven application design.",
- "features": [
- "Add and remove books",
- "Issue and return books",
- "Store student records",
- "Search books by title or author"
- ],
- "tech_stack": [
- "Java",
- "OOP",
- "File Handling"
- ],
- "roadmap": [
- "Step 1: Create Book and Student classes",
- "Step 2: Design the menu-driven interface",
- "Step 3: Implement add and remove book features",
- "Step 4: Add issue and return book functionality",
- "Step 5: Store records using file handling",
- "Step 6: Implement search functionality",
- "Step 7: Test the system with sample records"
- ],
- "resources": [
- "Java official docs: https://docs.oracle.com/javase/tutorial",
- "OOP concepts in Java: https://www.geeksforgeeks.org/object-oriented-programming-oops-concept-in-java",
- "Java file handling: https://www.w3schools.com/java/java_files.asp"
- ],
- "starter_code": "starter_code/library_management.java"
- },
- {
- "id": 9,
- "title": "Real-Time Chat Application",
- "skills": [
- "JavaScript",
- "Node.js"
- ],
- "level": "Intermediate",
- "interest": "Web",
- "time": "High",
- "description": "A real-time chat application that allows multiple users to send and receive instant messages using WebSockets. This project introduces backend communication, event handling, and real-time systems.",
- "features": [
- "Multiple user chat support",
- "Real-time messaging",
- "User join and leave notifications",
- "Simple responsive chat interface"
- ],
- "tech_stack": [
- "Node.js",
- "Express.js",
- "Socket.IO",
- "HTML",
- "CSS"
- ],
- "roadmap": [
- "Step 1: Initialize the Node.js project",
- "Step 2: Install Express and Socket.IO",
- "Step 3: Create the server using Express",
- "Step 4: Build the frontend chat interface",
- "Step 5: Implement real-time messaging with Socket.IO",
- "Step 6: Add user connection notifications",
- "Step 7: Test the application with multiple users"
- ],
- "resources": [
- "Node.js docs: https://nodejs.org/en/docs",
- "Socket.IO guide: https://socket.io/docs/v4",
- "Express.js documentation: https://expressjs.com"
- ],
- "starter_code": "starter_code/realtime_chat_app.js"
- },
- {
- "id": 10,
- "title": "Password Strength Checker",
- "skills": [
- "Python"
- ],
- "level": "Beginner",
- "interest": "Cybersecurity",
- "time": "Low",
- "description": "A tool that checks password strength based on length, symbols, uppercase letters, and numbers. Helps beginners understand input validation and security basics.",
- "features": [
- "Check password complexity",
- "Display strength rating",
- "Suggest stronger password improvements",
- "Prevent weak password patterns"
- ],
- "tech_stack": [
- "Python",
- "Regex"
- ],
- "roadmap": [
- "Step 1: Create the password input system",
- "Step 2: Check password length",
- "Step 3: Detect uppercase and lowercase letters",
- "Step 4: Detect numbers and symbols",
- "Step 5: Create a scoring system",
- "Step 6: Display password strength feedback"
- ],
- "resources": [
- "Python regex docs: https://docs.python.org/3/library/re.html",
- "OWASP password guidelines: https://owasp.org"
- ],
- "starter_code": "starter_code/password_checker.py"
- },
- {
- "id": 11,
- "title": "Feedback Survey Form",
- "skills": [
- "HTML"
- ],
- "level": "Beginner",
- "interest": "Web",
- "time": "Low",
- "description": "A simple student feedback form that collects user names, emails, and ratings. Teaches basic HTML form handling and layout design.",
- "features": [
- "Collect user name, email, and age with validation",
- "Dropdown menu for experience selection",
- "Text area for detailed user suggestions"
- ],
- "tech_stack": [
- "HTML"
- ],
- "roadmap": [
- "Step 1: Create the HTML folder structure inside starter_code",
- "Step 2: Build the input text fields and labels",
- "Step 3: Add select options and textarea elements",
- "Step 4: Align the form to the center for better layout",
- "Step 5: Test the form using Live Server"
- ],
- "resources": [
- "MDN HTML Forms: https://developer.mozilla.org/en-US/docs/Learn/Forms"
- ],
- "starter_code": "starter_code/survey_form/index.html"
- },
- {
- "id": 12,
- "title": "API ETL Pipeline",
- "skills": [
- "Python",
- "pandas",
- "requests"
- ],
- "level": "Intermediate",
- "interest": "Data",
- "time": "Medium",
- "description": "Enter a public API URL to fetch data and automatically transform it into a structured CSV dataset.",
- "features": [
- "Fetch data from public APIs",
- "handle missing values",
- "Normalize nested JSON",
- "Generate summary statistics",
- "Export the processed CSV for any other Analytics projects"
- ],
- "tech_stack": [
- "Python",
- "pandas",
- "requests",
- "JSON"
- ],
- "roadmap": [
- "Step 1: Install required modules via pip",
- "Step 2: Find a public API key for this project",
- "Step 3: Fetch the data from the API using requests",
- "Step 4: Validate the response you just fetched From the API",
- "Step 5: Normalize the nested JSON data by flattening it",
- "Step 6: Use the fetched data to build a pandas dataframe",
- "Step 7: Handle missing values or duplicate values",
- "Step 8: Export the cleaned dataset to CSV format",
- "Step 9: Generate a summary for the newly created CSV dataset",
- "Step 10: Test the file with at least two different public APIs"
- ],
- "resources": [
- "pandas docs: https://pandas.pydata.org/docs",
- "requests docs: https://requests.readthedocs.io/en/latest/",
- "JSON handling in Python: https://docs.python.org/3/library/json.html",
- "REST API tutorial: https://restfulapi.net/",
- "Real Python API guide: https://realpython.com/api-integration-in-python/"
- ],
- "starter_code": "starter_code/api_data_pipeline.py"
- },
- {
- "id": 13,
- "title": "AI Resume Analyzer",
- "skills": [
- "Python",
- "Flask",
- "HTML",
- "CSS",
- "JavaScript"
- ],
- "level": "Intermediate",
- "interest": "Data",
- "time": "High",
- "description": "A Flask web app that compares a resume against a job description using TF-IDF similarity and keyword extraction. Users upload a PDF or paste text, and the app returns a match score, a list of missing keywords, and actionable feedback — with no external AI API required.",
- "features": [
- "Upload a resume as PDF or paste plain text",
- "Paste any job description for comparison",
- "TF-IDF cosine similarity match score (0–100%)",
- "Missing skills and keyword gap analysis",
- "Actionable written feedback based on score",
- "Single-page interface with interactive feedback display"
- ],
- "tech_stack": [
- "Python",
- "Flask",
- "PyPDF2",
- "scikit-learn",
- "HTML",
- "CSS",
- "JavaScript"
- ],
- "roadmap": [
- "Step 1: Run the server and verify the upload form renders",
- "Step 2: Complete extract_text_from_pdf() using PyPDF2",
- "Step 3: Complete clean_text() to normalise punctuation and whitespace",
- "Step 4: Complete extract_keywords() to remove stopwords and count frequency",
- "Step 5: Complete calculate_similarity() with TF-IDF and cosine distance",
- "Step 6: Complete find_missing_skills() by comparing two keyword sets",
- "Step 7: Complete generate_feedback() to produce written suggestions",
- "Step 8: Wire everything together inside the /analyze Flask route",
- "Step 9: Test with a real resume PDF and a real job posting"
- ],
- "resources": [
- "PyPDF2 documentation: https://pypdf2.readthedocs.io/",
- "scikit-learn TF-IDF guide: https://scikit-learn.org/stable/modules/feature_extraction.html#tfidf-term-weighting",
- "Cosine similarity explained: https://www.machinelearningplus.com/nlp/cosine-similarity",
- "Flask quickstart: https://flask.palletsprojects.com/quickstart"
- ],
- "starter_code": "starter_code/ai_resume_analyzer.py"
- },
- {
- "id": 14,
- "title": "Number Guessing Game",
- "skills": [
- "Python"
- ],
- "level": "Beginner",
- "interest": "Games",
- "time": "Low",
- "description": "A fun command-line game where the computer picks a random number and the user tries to guess it. Great for learning loops, conditionals, and user input handling.",
- "features": [
- "Generate random number between 1 and 100",
- "Give hints: too high or too low",
- "Count number of attempts",
- "Show final score at the end"
- ],
- "tech_stack": [
- "Python",
- "random module"
- ],
- "roadmap": [
- "Step 1: Set up the Python file and import random module",
- "Step 2: Generate a random number using random.randint()",
- "Step 3: Write a loop to take user input",
- "Step 4: Compare guess with the number",
- "Step 5: Give hints if guess is too high or too low",
- "Step 6: Count the number of attempts",
- "Step 7: Display win message with attempt count"
- ],
- "resources": [
- "Python random module: https://docs.python.org/3/library/random.html",
- "W3Schools Python: https://www.w3schools.com/python",
- "Real Python: https://realpython.com"
- ],
- "starter_code": "starter_code/number_guessing.py"
- },
- {
- "id": 15,
- "title": "Simple Email Automation",
- "skills": [
- "Python"
- ],
- "level": "Beginner",
- "interest": "Automation",
- "time": "Low",
- "description": "A Python script that sends automated emails using the smtplib library. Learn how to automate repetitive tasks and work with Python standard libraries.",
- "features": [
- "Compose and send emails via Python",
- "Send to multiple recipients",
- "Add subject and body text",
- "Read recipient list from a text file"
- ],
- "tech_stack": [
- "Python",
- "smtplib",
- "email module"
- ],
- "roadmap": [
- "Step 1: Set up Python file and import smtplib",
- "Step 2: Configure sender email and password",
- "Step 3: Write the email composition function",
- "Step 4: Connect to Gmail SMTP server",
- "Step 5: Send email to one recipient and test",
- "Step 6: Read recipient list from a text file",
- "Step 7: Loop through recipients and send to all"
- ],
- "resources": [
- "Python smtplib docs: https://docs.python.org/3/library/smtplib.html",
- "Real Python email guide: https://realpython.com/python-send-email",
- "Gmail SMTP settings: https://support.google.com/mail"
- ],
- "starter_code": "starter_code/email_automation.py"
- },
- {
- "id": 16,
- "title": "Quiz App",
- "skills": [
- "HTML",
- "CSS",
- "JavaScript"
- ],
- "level": "Beginner",
- "interest": "Games",
- "time": "Low",
- "description": "A browser-based quiz app with multiple choice questions, a score counter, and a results screen. Perfect for practising DOM manipulation and event handling in JavaScript.",
- "features": [
- "Display one question at a time",
- "Four multiple choice options per question",
- "Show correct or incorrect feedback instantly",
- "Display final score on results screen"
- ],
- "tech_stack": [
- "HTML",
- "CSS",
- "JavaScript"
- ],
- "roadmap": [
- "Step 1: Create HTML structure for question and options",
- "Step 2: Style the quiz card with CSS",
- "Step 3: Store questions as a JavaScript array of objects",
- "Step 4: Write a function to display each question",
- "Step 5: Add click event listeners to option buttons",
- "Step 6: Check the selected answer and update score",
- "Step 7: Move to the next question automatically",
- "Step 8: Show the results screen with final score"
- ],
- "resources": [
- "MDN DOM guide: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model",
- "JavaScript events: https://javascript.info/events",
- "W3Schools JavaScript: https://www.w3schools.com/js"
- ],
- "starter_code": "starter_code/quiz_app.html"
- },
- {
- "id": 17,
- "title": "Movie Recommendation System",
- "skills": [
- "Python"
- ],
- "level": "Intermediate",
- "interest": "Data",
- "time": "High",
- "description": "A recommendation system that suggests movies to users based on ratings and similarity scores. An intermediate project for learning similarity scoring, pandas data handling, and basic recommendation logic.",
- "features": [
- "Recommend movies based on user preferences",
- "Find similar movies using similarity metrics",
- "Load and process movie datasets",
- "Display top recommended movies"
- ],
- "tech_stack": [
- "Python",
- "pandas",
- "scikit-learn",
- "NumPy"
- ],
- "roadmap": [
- "Step 1: Download and inspect a movie ratings dataset",
- "Step 2: Load the dataset using pandas",
- "Step 3: Clean and preprocess missing data",
- "Step 4: Organize movie features into a pandas DataFrame",
- "Step 5: Compare movies using cosine similarity",
- "Step 6: Build the recommendation function",
- "Step 7: Display top movie recommendations to the user",
- "Step 8: Test the recommendation system with sample inputs"
- ],
- "resources": [
- "pandas documentation: https://pandas.pydata.org/docs",
- "scikit-learn user guide: https://scikit-learn.org/stable/user_guide.html",
- "MovieLens dataset: https://grouplens.org/datasets/movielens",
- "Cosine Similarity: https://naomy-gomes.medium.com/the-cosine-similarity-and-its-use-in-recommendation-systems-cb2ebd811ce1"
- ],
- "starter_code": "starter_code/movie_recommender.py"
- },
- {
- "id": 18,
- "title": "Sentiment Analysis Web App",
- "skills": [
- "Python",
- "HTML",
- "CSS"
- ],
- "level": "Intermediate",
- "interest": "Data",
- "time": "High",
- "description": "A web application that predicts whether user entered text has positive, negative, or neutral sentiment. Great for learning text preprocessing and basic sentiment prediction.",
- "features": [
- "Analyze sentiment from user input text",
- "Display sentiment prediction results",
- "Preprocess text using NLP techniques",
- "Deploy the model with a simple web interface"
- ],
- "tech_stack": [
- "Python",
- "Flask",
- "scikit-learn",
- "HTML",
- "CSS"
- ],
- "roadmap": [
- "Step 1: Collect or download a sentiment dataset",
- "Step 2: Preprocess text data by cleaning and tokenizing",
- "Step 3: Convert text into numerical features",
- "Step 4: Build a simple sentiment prediction model",
- "Step 5: Test predictions with sample text inputs",
- "Step 6: Build a Flask app for user interaction",
- "Step 7: Connect the trained model to the web app",
- "Step 8: Test the application with sample text inputs"
- ],
- "resources": [
- "Flask documentation: https://flask.palletsprojects.com",
- "scikit-learn text tutorial: https://scikit-learn.org/stable/",
- "NLTK documentation: https://www.nltk.org",
- "Tokenization: https://www.geeksforgeeks.org/nlp/nlp-how-tokenizing-text-sentence-words-works/"
- ],
- "starter_code": "starter_code/sentiment_app.py"
- },
- {
- "id": 19,
- "title": "Sunrise Grand Hotel Management System",
- "skills": [
- "Python"
- ],
- "level": "Intermediate",
- "interest": "Business Logic",
- "time": "Medium",
- "description": "A robust CLI application to manage a 40-room inventory with tiered pricing, guest booking logic, and automatic billing calculation.",
- "features": [
- "Dynamic 40-room inventory across 4 luxury categories",
- "Real-time availability filtering and room status tracking",
- "Booking engine with 10-digit contact validation",
- "Automatic stay duration and total price calculation",
- "Cancellation logic and detailed guest lookup"
- ],
- "tech_stack": [
- "Python",
- "Dictionaries",
- "Input Validation"
- ],
- "roadmap": [
- "Step 1: Initialize the 40-room inventory with tiered pricing",
- "Step 2: Implement availability display and category filtering",
- "Step 3: Build the booking engine with contact validation",
- "Step 4: Create the cancellation and guest detail lookup logic",
- "Step 5: Wrap all logic in a persistent main menu loop"
- ],
- "resources": [
- "Python Dictionaries: https://docs.python.org/3/tutorial/datastructures.html#dictionaries",
- "Python Input Validation: https://realpython.com/python-input-output/",
- "Menu-driven CLI logic: https://www.geeksforgeeks.org/how-to-create-a-menu-driven-program-in-python/"
- ],
- "starter_code": "starter_code/hotel_management.py"
- },
- {
- "id": 20,
- "title": "CLI URL Shortener",
- "skills": [
- "Go"
- ],
- "level": "Beginner",
- "interest": "Tools",
- "time": "Low",
- "description": "A command-line tool that generates short hash codes for long URLs and stores the mappings in a local JSON file. Great for learning Go's standard library, file I/O, and CLI argument handling.",
- "features": [
- "Accept a URL as a CLI argument",
- "Generate a short hash code using MD5",
- "Store URL mappings in a local JSON file",
- "Resolve a short code back to the original URL"
- ],
- "tech_stack": [
- "Go",
- "encoding/json",
- "crypto/md5",
- "os",
- "fmt"
- ],
- "roadmap": [
- "Step 1: Initialise a Go module with go mod init",
- "Step 2: Accept a URL as a CLI argument using os.Args",
- "Step 3: Generate a short key by hashing the URL with MD5",
- "Step 4: Read and write the mappings map to a JSON file",
- "Step 5: Add a resolve subcommand to look up a short code",
- "Step 6: Handle errors gracefully for missing or invalid input",
- "Step 7: Test shorten and resolve with several sample URLs"
- ],
- "resources": [
- "Go Tour: https://tour.golang.org",
- "Go by Example: https://gobyexample.com",
- "Go standard library docs: https://pkg.go.dev/std"
- ],
- "starter_code": "starter_code/generated/cli_url_shortener.py"
- },
- {
- "id": 21,
- "title": "Go REST API Server",
- "skills": [
- "Go"
- ],
- "level": "Intermediate",
- "interest": "Web",
- "time": "Medium",
- "description": "A RESTful API server using Go's net/http standard library with full CRUD endpoints for a notes resource. Teaches HTTP routing, JSON encoding, and in-memory data management in Go.",
- "features": [
- "GET /notes — list all notes",
- "POST /notes — create a new note",
- "PUT /notes/{id} — update an existing note",
- "DELETE /notes/{id} — remove a note",
- "JSON request and response handling throughout"
- ],
- "tech_stack": [
- "Go",
- "net/http",
- "encoding/json",
- "strconv"
- ],
- "roadmap": [
- "Step 1: Create main.go and start an HTTP server with http.ListenAndServe",
- "Step 2: Define a Note struct with ID, Title, and Body fields",
- "Step 3: Use a map as an in-memory store with a counter for IDs",
- "Step 4: Write handler functions for each HTTP method",
- "Step 5: Register routes using http.HandleFunc",
- "Step 6: Parse request bodies with json.NewDecoder",
- "Step 7: Write JSON responses with json.NewEncoder",
- "Step 8: Test all endpoints with curl or Postman"
- ],
- "resources": [
- "net/http docs: https://pkg.go.dev/net/http",
- "Go by Example — HTTP servers: https://gobyexample.com/http-servers",
- "Go by Example — JSON: https://gobyexample.com/json"
- ],
- "starter_code": "starter_code/generated/go_rest_api_server.py"
- },
- {
- "id": 22,
- "title": "File Duplicate Finder",
- "skills": [
- "Rust"
- ],
- "level": "Beginner",
- "interest": "Tools",
- "time": "Low",
- "description": "A CLI tool that recursively scans a directory, hashes every file using SHA-256, and reports groups of duplicates. A practical first Rust project for learning ownership, iterators, and the standard library.",
- "features": [
- "Recursively walk a directory with std::fs",
- "Hash each file using the sha2 crate",
- "Group files by hash and print duplicate sets",
- "Accept target directory as a CLI argument"
- ],
- "tech_stack": [
- "Rust",
- "std::fs",
- "std::collections::HashMap",
- "sha2 crate"
- ],
- "roadmap": [
- "Step 1: Create a new project with cargo new and add sha2 to Cargo.toml",
- "Step 2: Accept a directory path from std::env::args",
- "Step 3: Walk the directory recursively using std::fs::read_dir",
- "Step 4: Read each file and compute its SHA-256 hash",
- "Step 5: Store results in a HashMap keyed by hash",
- "Step 6: Print any hash bucket that contains more than one path",
- "Step 7: Test on a folder with known duplicate files"
- ],
- "resources": [
- "The Rust Book: https://doc.rust-lang.org/book/",
- "sha2 crate: https://crates.io/crates/sha2",
- "Rust std::fs docs: https://doc.rust-lang.org/std/fs/"
- ],
- "starter_code": "starter_code/generated/file_duplicate_finder.py"
- },
- {
- "id": 23,
- "title": "Markdown to HTML Converter",
- "skills": [
- "Rust"
- ],
- "level": "Intermediate",
- "interest": "Tools",
- "time": "Medium",
- "description": "A CLI tool that reads a Markdown file and produces a styled HTML file using the pulldown-cmark parser. Teaches Rust's crate ecosystem, file handling, and string processing.",
- "features": [
- "Parse headings, bold, italic, lists, and links",
- "Generate a complete and valid HTML file",
- "Accept input and output file paths as CLI arguments",
- "Wrap output in a minimal HTML template with inline CSS"
- ],
- "tech_stack": [
- "Rust",
- "pulldown-cmark",
- "std::fs",
- "std::env"
- ],
- "roadmap": [
- "Step 1: Create a Cargo project and add pulldown-cmark to Cargo.toml",
- "Step 2: Read the input Markdown file path from CLI arguments",
- "Step 3: Load the file content into a String with std::fs::read_to_string",
- "Step 4: Use pulldown-cmark Parser and push_html to convert to HTML",
- "Step 5: Wrap the generated HTML in a full page template",
- "Step 6: Write the output to the specified file path",
- "Step 7: Test with a Markdown file containing all common elements"
- ],
- "resources": [
- "pulldown-cmark docs: https://docs.rs/pulldown-cmark",
- "Rust CLI book: https://rust-cli.github.io/book/",
- "The Rust Book: https://doc.rust-lang.org/book/"
- ],
- "starter_code": "starter_code/generated/markdown_to_html_converter.py"
- },
- {
- "id": 24,
- "title": "Android Tip Calculator",
- "skills": [
- "Kotlin"
- ],
- "level": "Beginner",
- "interest": "Mobile",
- "time": "Low",
- "description": "An Android app that calculates the tip amount and total bill in real time as the user types a bill amount and adjusts a tip percentage slider. A solid first Kotlin project for learning Android UI basics.",
- "features": [
- "Input field for the bill amount",
- "SeekBar to select tip percentage from 0 to 30",
- "Live display of tip amount and total bill",
- "Handles empty or invalid input gracefully"
- ],
- "tech_stack": [
- "Kotlin",
- "Android SDK",
- "View Binding",
- "SeekBar"
- ],
- "roadmap": [
- "Step 1: Create a new Android project in Android Studio with Kotlin",
- "Step 2: Build the layout with EditText, SeekBar, and TextViews",
- "Step 3: Enable View Binding in build.gradle",
- "Step 4: Add a TextWatcher to the EditText to react to input changes",
- "Step 5: Add a SeekBar.OnSeekBarChangeListener for the slider",
- "Step 6: Calculate tip and total on every change event",
- "Step 7: Display results and handle the empty-input edge case"
- ],
- "resources": [
- "Android Kotlin Fundamentals: https://developer.android.com/courses/kotlin-android-fundamentals/overview",
- "Kotlin docs: https://kotlinlang.org/docs/",
- "Android View Binding: https://developer.android.com/topic/libraries/view-binding"
- ],
- "starter_code": "starter_code/generated/android_tip_calculator.py"
- },
- {
- "id": 25,
- "title": "Kotlin Weather App",
- "skills": [
- "Kotlin"
- ],
- "level": "Intermediate",
- "interest": "Mobile",
- "time": "Medium",
- "description": "An Android weather app that fetches current conditions from the OpenWeatherMap API and displays temperature, humidity, and a weather description. Teaches Retrofit, Kotlin Coroutines, and API integration.",
- "features": [
- "Search weather by city name",
- "Display temperature, humidity, and conditions",
- "Show a loading indicator while fetching",
- "Display an error message if the city is not found"
- ],
- "tech_stack": [
- "Kotlin",
- "Retrofit",
- "Kotlin Coroutines",
- "OpenWeatherMap API",
- "Gson"
- ],
- "roadmap": [
- "Step 1: Create an Android project and add Retrofit and Gson dependencies",
- "Step 2: Sign up for a free OpenWeatherMap API key",
- "Step 3: Define a data class matching the API JSON response",
- "Step 4: Build a Retrofit interface with a suspend GET function",
- "Step 5: Launch a coroutine in the button click listener to fetch data",
- "Step 6: Show a ProgressBar while the request is in flight",
- "Step 7: Update the UI with the result or display an error message"
- ],
- "resources": [
- "Retrofit docs: https://square.github.io/retrofit/",
- "Kotlin Coroutines guide: https://kotlinlang.org/docs/coroutines-overview.html",
- "OpenWeatherMap API: https://openweathermap.org/api"
- ],
- "starter_code": "starter_code/generated/kotlin_weather_app.py"
- },
- {
- "id": 26,
- "title": "Java Student Grade Tracker",
- "skills": [
- "Java"
- ],
- "level": "Beginner",
- "interest": "Education",
- "time": "Low",
- "description": "A console application that stores student names and grades, calculates individual and class averages, and prints a formatted summary report. Great for practising Java OOP basics, ArrayLists, and Scanner input.",
- "features": [
- "Add students and record multiple grades per student",
- "Calculate individual student averages",
- "Calculate overall class average",
- "Print a formatted report for all students"
- ],
- "tech_stack": [
- "Java",
- "ArrayList",
- "HashMap",
- "Scanner"
- ],
- "roadmap": [
- "Step 1: Set up a Java project in IntelliJ IDEA or VS Code",
- "Step 2: Create a Student class with a name field and a grades ArrayList",
- "Step 3: Write an addGrade() method and an getAverage() method",
- "Step 4: Use a Scanner to accept student names and grades from the user",
- "Step 5: Store Student objects in an ArrayList",
- "Step 6: Implement a printReport() method that formats the output",
- "Step 7: Calculate and display the overall class average at the end"
- ],
- "resources": [
- "Java SE docs: https://docs.oracle.com/en/java/",
- "MOOC Java programming: https://java-programming.mooc.fi/",
- "W3Schools Java: https://www.w3schools.com/java"
- ],
- "starter_code": "starter_code/generated/java_student_grade_tracker.py"
- },
- {
- "id": 27,
- "title": "Java Bank Account System",
- "skills": [
- "Java"
- ],
- "level": "Intermediate",
- "interest": "Web",
- "time": "Medium",
- "description": "An OOP-based console banking application with deposit, withdrawal, balance inquiry, and transaction history. Demonstrates inheritance, encapsulation, and polymorphism through a BankAccount class hierarchy.",
- "features": [
- "BankAccount base class with deposit and withdrawal",
- "SavingsAccount subclass with minimum balance enforcement",
- "CheckingAccount subclass with overdraft protection",
- "Transaction history stored as an ArrayList of strings",
- "Menu-driven console interface"
- ],
- "tech_stack": [
- "Java",
- "OOP",
- "ArrayList",
- "Scanner",
- "Inheritance"
- ],
- "roadmap": [
- "Step 1: Design the class hierarchy on paper before writing any code",
- "Step 2: Implement the BankAccount base class with balance and transaction list",
- "Step 3: Write deposit() and withdraw() with input validation",
- "Step 4: Create SavingsAccount subclass with minimum balance check",
- "Step 5: Create CheckingAccount subclass with overdraft logic",
- "Step 6: Build a menu-driven console interface using Scanner",
- "Step 7: Add a printHistory() method to display all transactions",
- "Step 8: Test each account type with edge case inputs"
- ],
- "resources": [
- "Java OOP tutorial: https://docs.oracle.com/javase/tutorial/java/concepts/",
- "MOOC Java programming: https://java-programming.mooc.fi/",
- "Java inheritance guide: https://www.w3schools.com/java/java_inheritance.asp"
- ],
- "starter_code": "starter_code/generated/java_bank_account_system.py"
- },
- {
- "id": 28,
- "title": "TypeScript Todo App",
- "skills": [
- "TypeScript",
- "HTML",
- "CSS"
- ],
- "level": "Beginner",
- "interest": "Productivity",
- "time": "Low",
- "description": "A browser-based todo list app written entirely in TypeScript with strongly typed interfaces, DOM manipulation, and in-page state management. A great first TypeScript project that reinforces type safety concepts.",
- "features": [
- "Add and delete tasks",
- "Mark tasks as complete with a strikethrough",
- "Filter view by All, Active, or Completed",
- "TypeScript interfaces used for all data structures"
- ],
- "tech_stack": [
- "TypeScript",
- "HTML",
- "CSS",
- "tsc compiler"
- ],
- "roadmap": [
- "Step 1: Initialise the project with npm init and install TypeScript",
- "Step 2: Create tsconfig.json targeting ES6 with strict mode on",
- "Step 3: Define a Task interface with id, title, and completed fields",
- "Step 4: Write addTask(), deleteTask(), and toggleTask() functions with full types",
- "Step 5: Build the HTML structure for the task list",
- "Step 6: Write a render() function that re-draws the list from state",
- "Step 7: Compile with tsc and wire up the HTML file",
- "Step 8: Test add, delete, toggle, and filter interactions"
- ],
- "resources": [
- "TypeScript Handbook: https://www.typescriptlang.org/docs/handbook/intro.html",
- "TypeScript in 5 minutes: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html",
- "MDN DOM reference: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model"
- ],
- "starter_code": "starter_code/generated/typescript_todo_app.py"
- },
- {
- "id": 29,
- "title": "TypeScript REST API Client",
- "skills": [
- "TypeScript"
- ],
- "level": "Intermediate",
- "interest": "Web",
- "time": "Medium",
- "description": "A fully typed API client that queries a public REST API using the Fetch API, maps the response to TypeScript interfaces, and renders the data to the DOM. Teaches type-safe async programming and error handling.",
- "features": [
- "Typed interfaces matching the API response shape",
- "Async fetch wrapper with a typed return value",
- "Loading and error state handling",
- "Render fetched data to the DOM dynamically"
- ],
- "tech_stack": [
- "TypeScript",
- "Fetch API",
- "HTML",
- "CSS",
- "tsconfig"
- ],
- "roadmap": [
- "Step 1: Set up a TypeScript project with tsconfig.json",
- "Step 2: Choose a public API (e.g. JSONPlaceholder or Open Library)",
- "Step 3: Define interfaces that match the API response structure",
- "Step 4: Write an async fetchData() generic function",
- "Step 5: Call the function and handle the Promise with try/catch",
- "Step 6: Show a loading message while the request is in flight",
- "Step 7: Render the typed response data into the DOM",
- "Step 8: Display a user-friendly error message on failure"
- ],
- "resources": [
- "TypeScript Handbook: https://www.typescriptlang.org/docs/handbook/",
- "Using Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch",
- "JSONPlaceholder (free test API): https://jsonplaceholder.typicode.com/"
- ],
- "starter_code": "starter_code/generated/typescript_rest_api_client.py"
- },
- {
- "id": 30,
- "title": "C++ Student Records Manager",
- "skills": [
- "C++"
- ],
- "level": "Beginner",
- "interest": "Education",
- "time": "Low",
- "description": "A console application to manage student records using structs, vectors, and file I/O in C++. Ideal for learning C++ fundamentals including structs, loops, and the fstream library.",
- "features": [
- "Add and display student records",
- "Save all records to a text file with ofstream",
- "Load existing records from file on startup",
- "Search for a student record by name"
- ],
- "tech_stack": [
- "C++",
- "struct",
- "vector",
- "fstream",
- "iostream"
- ],
- "roadmap": [
- "Step 1: Define a Student struct with name, roll number, and grade fields",
- "Step 2: Use a std::vector to store Student objects in memory",
- "Step 3: Write an addStudent() function that reads from cin",
- "Step 4: Write a displayAll() function that prints the vector",
- "Step 5: Implement saveToFile() using std::ofstream",
- "Step 6: Implement loadFromFile() using std::ifstream on startup",
- "Step 7: Add a searchByName() function using a linear scan",
- "Step 8: Wrap all functionality in a menu-driven main loop"
- ],
- "resources": [
- "LearnCpp: https://www.learncpp.com/",
- "cppreference std::fstream: https://en.cppreference.com/w/cpp/io/basic_fstream",
- "cppreference std::vector: https://en.cppreference.com/w/cpp/container/vector"
- ],
- "starter_code": "starter_code/generated/c_student_records_manager.py"
- },
- {
- "id": 31,
- "title": "C++ Task Scheduler",
- "skills": [
- "C++"
- ],
- "level": "Intermediate",
- "interest": "Automation",
- "time": "Medium",
- "description": "A CLI task scheduler that queues tasks with priority levels and processes them in order using a priority queue from the C++ STL. Teaches STL containers, custom comparators, and structured program design.",
- "features": [
- "Add tasks with a name and integer priority",
- "Process tasks in priority order using std::priority_queue",
- "Log each completed task to the console",
- "Display the pending queue at any time"
- ],
- "tech_stack": [
- "C++",
- "std::priority_queue",
- "struct",
- "iostream",
- "vector"
- ],
- "roadmap": [
- "Step 1: Define a Task struct with name and priority fields",
- "Step 2: Write a custom comparator struct for the priority queue",
- "Step 3: Implement addTask() to push onto the priority queue",
- "Step 4: Implement runNext() to pop and log the top task",
- "Step 5: Add a displayQueue() function that copies and drains a temp queue",
- "Step 6: Build a menu-driven console interface",
- "Step 7: Test with tasks of varying priorities to verify ordering"
- ],
- "resources": [
- "cppreference std::priority_queue: https://en.cppreference.com/w/cpp/container/priority_queue",
- "LearnCpp: https://www.learncpp.com/",
- "C++ STL tutorial: https://www.geeksforgeeks.org/the-c-standard-template-library-stl/"
- ],
- "starter_code": "starter_code/generated/c_task_scheduler.py"
- }
-]
+[
+ {
+ "id": 1,
+ "title": "Personal Expense Tracker",
+ "skills": ["Python"],
+ "level": "Beginner",
+ "interest": "Data",
+ "time": "Low",
+ "description": "A command-line tool that helps users track daily expenses, categorize spending, and generate simple summary reports. Great for learning file handling, loops, and basic data processing.",
+ "features": [
+ "Add and delete expense entries",
+ "Categorize expenses (food, transport, bills)",
+ "View monthly summary",
+ "Export data to CSV file"
+ ],
+ "tech_stack": ["Python", "CSV module", "datetime module"],
+ "roadmap": [
+ "Step 1: Set up the project folder and create main.py",
+ "Step 2: Design the expense data structure as a dictionary",
+ "Step 3: Write functions to add and delete expenses",
+ "Step 4: Implement category filtering logic",
+ "Step 5: Write the summary report generator",
+ "Step 6: Add CSV export functionality",
+ "Step 7: Test with sample data and fix bugs"
+ ],
+ "resources": [
+ "Python official docs: https://docs.python.org",
+ "CSV module guide: https://docs.python.org/3/library/csv.html",
+ "Real Python beginner tutorials: https://realpython.com"
+ ],
+ "starter_code": "starter_code/expense_tracker.py"
+ },
+ {
+ "id": 2,
+ "title": "Weather Dashboard",
+ "skills": ["JavaScript", "HTML", "CSS"],
+ "level": "Beginner",
+ "interest": "Web",
+ "time": "Low",
+ "description": "A simple web page that fetches weather data from a free API and displays current conditions for any city. Teaches API calls, DOM manipulation, and basic UI design.",
+ "features": [
+ "Search weather by city name",
+ "Display temperature, humidity, and conditions",
+ "Show a weather icon based on conditions",
+ "Toggle between Celsius and Fahrenheit"
+ ],
+ "tech_stack": ["HTML", "CSS", "JavaScript", "OpenWeatherMap API"],
+ "roadmap": [
+ "Step 1: Create the HTML structure with a search form",
+ "Step 2: Style the page with CSS flexbox",
+ "Step 3: Sign up for a free OpenWeatherMap API key",
+ "Step 4: Write the fetch() call to get weather data",
+ "Step 5: Parse the JSON response and extract key fields",
+ "Step 6: Display the data dynamically using DOM methods",
+ "Step 7: Add the Celsius/Fahrenheit toggle button"
+ ],
+ "resources": [
+ "MDN Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API",
+ "OpenWeatherMap free tier: https://openweathermap.org/api",
+ "CSS Flexbox guide: https://css-tricks.com/snippets/css/a-guide-to-flexbox"
+ ],
+ "starter_code": "starter_code/weather_dashboard.html"
+ },
+ {
+ "id": 3,
+ "title": "Student Grade Manager",
+ "skills": ["Python"],
+ "level": "Beginner",
+ "interest": "Education",
+ "time": "Medium",
+ "description": "A Python application to store student names and their grades, compute averages, and display a class report. Ideal for practicing data structures, functions, and file persistence.",
+ "features": [
+ "Add students and assign grades per subject",
+ "Calculate individual and class averages",
+ "Assign letter grades automatically",
+ "Save and load data from a JSON file"
+ ],
+ "tech_stack": ["Python", "json module", "os module"],
+ "roadmap": [
+ "Step 1: Define the student data structure using a dictionary",
+ "Step 2: Write add_student() and add_grade() functions",
+ "Step 3: Implement average calculation logic",
+ "Step 4: Create a letter grade converter function",
+ "Step 5: Build the JSON save/load functions",
+ "Step 6: Create a simple text menu for user interaction",
+ "Step 7: Write a class report printer function"
+ ],
+ "resources": [
+ "Python JSON module: https://docs.python.org/3/library/json.html",
+ "Python functions tutorial: https://realpython.com/defining-your-own-python-function",
+ "W3Schools Python: https://www.w3schools.com/python"
+ ],
+ "starter_code": "starter_code/grade_manager.py"
+ },
+ {
+ "id": 4,
+ "title": "Task Manager REST API",
+ "skills": ["Python"],
+ "level": "Intermediate",
+ "interest": "Web",
+ "time": "Medium",
+ "description": "A RESTful API built with Flask that allows clients to create, read, update, and delete tasks. Perfect for learning API design, HTTP methods, and JSON responses.",
+ "features": [
+ "CRUD endpoints for tasks",
+ "Filter tasks by status (pending, done)",
+ "Assign priority levels to tasks",
+ "Persist data to a JSON file"
+ ],
+ "tech_stack": ["Python", "Flask", "JSON", "Postman (for testing)"],
+ "roadmap": [
+ "Step 1: Install Flask and create the app.py file",
+ "Step 2: Define the task data model as a dictionary",
+ "Step 3: Create the GET /tasks endpoint to list all tasks",
+ "Step 4: Create the POST /tasks endpoint to add a task",
+ "Step 5: Create PUT /tasks/ to update a task",
+ "Step 6: Create DELETE /tasks/ to remove a task",
+ "Step 7: Add JSON file persistence for saving tasks",
+ "Step 8: Test all endpoints using Postman or curl"
+ ],
+ "resources": [
+ "Flask quickstart: https://flask.palletsprojects.com/quickstart",
+ "REST API design guide: https://restfulapi.net",
+ "Postman download: https://www.postman.com/downloads"
+ ],
+ "starter_code": "starter_code/task_api.py"
+ },
+ {
+ "id": 5,
+ "title": "Portfolio Website",
+ "skills": ["HTML", "CSS", "JavaScript"],
+ "level": "Beginner",
+ "interest": "Web",
+ "time": "Low",
+ "description": "A personal portfolio site with sections for bio, projects, and contact. A great first project that teaches HTML layout, CSS styling, and a bit of JavaScript for interactivity.",
+ "features": [
+ "Hero section with name and tagline",
+ "Projects grid with cards",
+ "Skills list with visual indicators",
+ "Contact form with basic validation"
+ ],
+ "tech_stack": ["HTML", "CSS", "JavaScript"],
+ "roadmap": [
+ "Step 1: Plan the page sections on paper first",
+ "Step 2: Write the HTML structure for all sections",
+ "Step 3: Add CSS reset and base typography styles",
+ "Step 4: Style the navigation and hero section",
+ "Step 5: Build the projects grid using CSS Grid",
+ "Step 6: Add the contact form with labels and inputs",
+ "Step 7: Write JavaScript for form validation",
+ "Step 8: Make the site responsive with media queries"
+ ],
+ "resources": [
+ "HTML reference: https://developer.mozilla.org/en-US/docs/Web/HTML",
+ "CSS Grid guide: https://css-tricks.com/snippets/css/complete-guide-grid",
+ "Responsive design basics: https://web.dev/learn/design"
+ ],
+ "starter_code": "starter_code/portfolio.html"
+ },
+ {
+ "id": 6,
+ "title": "URL Shortener",
+ "skills": ["Python", "JavaScript", "HTML", "CSS"],
+ "level": "Intermediate",
+ "interest": "Web",
+ "time": "High",
+ "description": "A full-stack web app that takes long URLs and generates short codes. Users can paste a link and get a shorter one back. Teaches Flask routing, random code generation, and front-end form handling.",
+ "features": [
+ "Shorten any valid URL",
+ "Redirect short codes to original URL",
+ "Track how many times a link was clicked",
+ "List all shortened links in a dashboard"
+ ],
+ "tech_stack": ["Python", "Flask", "HTML", "CSS", "JavaScript", "JSON"],
+ "roadmap": [
+ "Step 1: Set up Flask app with two routes: home and redirect",
+ "Step 2: Write a random 6-character code generator",
+ "Step 3: Store URL mappings in a JSON file",
+ "Step 4: Build the HTML form for pasting long URLs",
+ "Step 5: Display the shortened URL after submission",
+ "Step 6: Implement the redirect route using the short code",
+ "Step 7: Add a click counter that updates on each visit",
+ "Step 8: Build a simple dashboard to list all links"
+ ],
+ "resources": [
+ "Flask routing docs: https://flask.palletsprojects.com/en/stable/quickstart/#routing",
+ "Python secrets module: https://docs.python.org/3/library/secrets.html",
+ "UUID in Python: https://docs.python.org/3/library/uuid.html"
+ ],
+ "starter_code": "starter_code/url_shortener.py"
+ },
+ {
+ "id": 7,
+ "title": "Data Analysis Report Generator",
+ "skills": ["Python"],
+ "level": "Intermediate",
+ "interest": "Data",
+ "time": "High",
+ "description": "Upload a CSV file and automatically generate a summary report with statistics, missing value counts, and basic charts. A practical project for learning data wrangling and pandas.",
+ "features": [
+ "Load and inspect CSV files",
+ "Show column types and null counts",
+ "Calculate mean, median, and mode per column",
+ "Generate bar charts for categorical data"
+ ],
+ "tech_stack": ["Python", "pandas", "matplotlib", "os module"],
+ "roadmap": [
+ "Step 1: Install pandas and matplotlib via pip",
+ "Step 2: Write a CSV loader that validates the file path",
+ "Step 3: Generate a summary table of column info",
+ "Step 4: Compute descriptive statistics for numeric columns",
+ "Step 5: Count and display missing values per column",
+ "Step 6: Build chart generation functions using matplotlib",
+ "Step 7: Export the full report to a text or HTML file"
+ ],
+ "resources": [
+ "pandas docs: https://pandas.pydata.org/docs",
+ "matplotlib tutorials: https://matplotlib.org/stable/tutorials",
+ "Real Python data analysis: https://realpython.com/pandas-dataframe"
+ ],
+ "starter_code": "starter_code/data_report.py"
+ },
+ {
+ "id": 8,
+
+ "title": "Library Management System",
+ "skills": ["Java"],
+ "level": "Beginner",
+ "interest": "Backend",
+ "time": "Medium",
+ "description": "A Java application that helps manage books, students, and borrowing records in a library. This project teaches object-oriented programming concepts, file handling, and menu-driven application design.",
+ "features": [
+ "Add and remove books",
+ "Issue and return books",
+ "Store student records",
+ "Search books by title or author"
+ ],
+ "tech_stack": ["Java", "OOP", "File Handling"],
+ "roadmap": [
+ "Step 1: Create Book and Student classes",
+ "Step 2: Design the menu-driven interface",
+ "Step 3: Implement add and remove book features",
+ "Step 4: Add issue and return book functionality",
+ "Step 5: Store records using file handling",
+ "Step 6: Implement search functionality",
+ "Step 7: Test the system with sample records"
+ ],
+ "resources": [
+ "Java official docs: https://docs.oracle.com/javase/tutorial",
+ "OOP concepts in Java: https://www.geeksforgeeks.org/object-oriented-programming-oops-concept-in-java",
+ "Java file handling: https://www.w3schools.com/java/java_files.asp"
+ ],
+ "starter_code": "starter_code/library_management.java"
+ },
+ {
+ "id": 9,
+ "title": "Real-Time Chat Application",
+ "skills": ["JavaScript", "Node.js"],
+ "level": "Intermediate",
+ "interest": "Web",
+ "time": "High",
+ "description": "A real-time chat application that allows multiple users to send and receive instant messages using WebSockets. This project introduces backend communication, event handling, and real-time systems.",
+ "features": [
+ "Multiple user chat support",
+ "Real-time messaging",
+ "User join and leave notifications",
+ "Simple responsive chat interface"
+ ],
+ "tech_stack": ["Node.js", "Express.js", "Socket.IO", "HTML", "CSS"],
+ "roadmap": [
+ "Step 1: Initialize the Node.js project",
+ "Step 2: Install Express and Socket.IO",
+ "Step 3: Create the server using Express",
+ "Step 4: Build the frontend chat interface",
+ "Step 5: Implement real-time messaging with Socket.IO",
+ "Step 6: Add user connection notifications",
+ "Step 7: Test the application with multiple users"
+ ],
+ "resources": [
+ "Node.js docs: https://nodejs.org/en/docs",
+ "Socket.IO guide: https://socket.io/docs/v4",
+ "Express.js documentation: https://expressjs.com"
+ ],
+ "starter_code": "starter_code/realtime_chat_app.js"
+ },
+ {
+ "id": 99,
+ "title": "Password Strength Checker",
+ "skills": ["Python"],
+ "level": "Beginner",
+ "interest": "Cybersecurity",
+ "time": "Low",
+ "description": "A tool that checks password strength based on length, symbols, uppercase letters, and numbers. Helps beginners understand input validation and security basics.",
+ "features": [
+ "Check password complexity",
+ "Display strength rating",
+ "Suggest stronger password improvements",
+ "Prevent weak password patterns"
+ ],
+ "tech_stack": ["Python", "Regex"],
+ "roadmap": [
+ "Step 1: Create the password input system",
+ "Step 2: Check password length",
+ "Step 3: Detect uppercase and lowercase letters",
+ "Step 4: Detect numbers and symbols",
+ "Step 5: Create a scoring system",
+ "Step 6: Display password strength feedback"
+ ],
+ "resources": [
+ "Python regex docs: https://docs.python.org/3/library/re.html",
+ "OWASP password guidelines: https://owasp.org"
+ ],
+ "starter_code": "starter_code/password_checker.py"
+ },
+ {
+ "id": 10,
+ "title": "Feedback Survey Form",
+ "skills": ["HTML"],
+ "level": "Beginner",
+ "interest": "Web",
+ "time": "Low",
+ "description": "A simple student feedback form that collects user names, emails, and ratings. Teaches basic HTML form handling and layout design.",
+ "features": [
+ "Collect user name, email, and age with validation",
+ "Dropdown menu for experience selection",
+ "Text area for detailed user suggestions"
+ ],
+ "tech_stack": ["HTML"],
+ "roadmap": [
+ "Step 1: Create the HTML folder structure inside starter_code",
+ "Step 2: Build the input text fields and labels",
+ "Step 3: Add select options and textarea elements",
+ "Step 4: Align the form to the center for better layout",
+ "Step 5: Test the form using Live Server"
+ ],
+ "resources": [
+ "MDN HTML Forms: https://developer.mozilla.org/en-US/docs/Learn/Forms"
+ ],
+ "starter_code": "starter_code/survey_form/index.html"
+ },
+ {
+ "id": 98,
+ "title": "API ETL Pipeline",
+ "skills": ["Python", "pandas", "requests"],
+ "level": "Intermediate",
+ "interest": "Data",
+ "time": "Medium",
+ "description": "Enter a public API URL to fetch data and automatically transform it into a structured CSV dataset.",
+ "features": [
+ "Fetch data from public APIs",
+ "handle missing values",
+ "Normalize nested JSON",
+ "Generate summary statistics",
+ "Export the processed CSV for any other Analytics projects"
+ ],
+ "tech_stack": ["Python", "pandas", "requests", "JSON"],
+ "roadmap": [
+ "Step 1: Install required modules via pip",
+ "Step 2: Find a public API key for this project",
+ "Step 3: Fetch the data from the API using requests",
+ "Step 4: Validate the response you just fetched From the API",
+ "Step 5: Normalize the nested JSON data by flattening it",
+ "Step 6: Use the fetched data to build a pandas dataframe",
+ "Step 7: Handle missing values or duplicate values",
+ "Step 8: Export the cleaned dataset to CSV format",
+ "Step 9: Generate a summary for the newly created CSV dataset",
+ "Step 10: Test the file with at least two different public APIs"
+ ],
+ "resources": [
+ "pandas docs: https://pandas.pydata.org/docs",
+ "requests docs: https://requests.readthedocs.io/en/latest/",
+ "JSON handling in Python: https://docs.python.org/3/library/json.html",
+ "REST API tutorial: https://restfulapi.net/",
+ "Real Python API guide: https://realpython.com/api-integration-in-python/"
+ ],
+ "starter_code": "starter_code/api_data_pipeline.py"
+ },
+ {
+ "id": 11,
+ "title": "AI Resume Analyzer",
+ "skills": [
+ "Python",
+ "Flask",
+ "HTML",
+ "CSS",
+ "JavaScript"
+ ],
+ "level": "Intermediate",
+ "interest": "Data",
+ "time": "High",
+ "description": "A Flask web app that compares a resume against a job description using TF-IDF similarity and keyword extraction. Users upload a PDF or paste text, and the app returns a match score, a list of missing keywords, and actionable feedback — with no external AI API required.",
+ "features": [
+ "Upload a resume as PDF or paste plain text",
+ "Paste any job description for comparison",
+ "TF-IDF cosine similarity match score (0–100%)",
+ "Missing skills and keyword gap analysis",
+ "Actionable written feedback based on score",
+ "Single-page interface with interactive feedback display"
+ ],
+ "tech_stack": [
+ "Python",
+ "Flask",
+ "PyPDF2",
+ "scikit-learn",
+ "HTML",
+ "CSS",
+ "JavaScript"
+ ],
+ "roadmap": [
+ "Step 1: Run the server and verify the upload form renders",
+ "Step 2: Complete extract_text_from_pdf() using PyPDF2",
+ "Step 3: Complete clean_text() to normalise punctuation and whitespace",
+ "Step 4: Complete extract_keywords() to remove stopwords and count frequency",
+ "Step 5: Complete calculate_similarity() with TF-IDF and cosine distance",
+ "Step 6: Complete find_missing_skills() by comparing two keyword sets",
+ "Step 7: Complete generate_feedback() to produce written suggestions",
+ "Step 8: Wire everything together inside the /analyze Flask route",
+ "Step 9: Test with a real resume PDF and a real job posting"
+ ],
+ "resources": [
+ "PyPDF2 documentation: https://pypdf2.readthedocs.io/",
+ "scikit-learn TF-IDF guide: https://scikit-learn.org/stable/modules/feature_extraction.html#tfidf-term-weighting",
+ "Cosine similarity explained: https://www.machinelearningplus.com/nlp/cosine-similarity",
+ "Flask quickstart: https://flask.palletsprojects.com/quickstart"
+ ],
+ "starter_code": "starter_code/ai_resume_analyzer.py"
+ },
+ {
+ "id": 12,
+ "title": "Number Guessing Game",
+ "skills": ["Python"],
+ "level": "Beginner",
+ "interest": "Games",
+ "time": "Low",
+ "description": "A fun command-line game where the computer picks a random number and the user tries to guess it. Great for learning loops, conditionals, and user input handling.",
+ "features": [
+ "Generate random number between 1 and 100",
+ "Give hints: too high or too low",
+ "Count number of attempts",
+ "Show final score at the end"
+ ],
+ "tech_stack": ["Python", "random module"],
+ "roadmap": [
+ "Step 1: Set up the Python file and import random module",
+ "Step 2: Generate a random number using random.randint()",
+ "Step 3: Write a loop to take user input",
+ "Step 4: Compare guess with the number",
+ "Step 5: Give hints if guess is too high or too low",
+ "Step 6: Count the number of attempts",
+ "Step 7: Display win message with attempt count"
+ ],
+ "resources": [
+ "Python random module: https://docs.python.org/3/library/random.html",
+ "W3Schools Python: https://www.w3schools.com/python",
+ "Real Python: https://realpython.com"
+ ],
+ "starter_code": "starter_code/number_guessing.py"
+ },
+ {
+ "id": 13,
+ "title": "Simple Email Automation",
+ "skills": ["Python"],
+ "level": "Beginner",
+ "interest": "Automation",
+ "time": "Low",
+ "description": "A Python script that sends automated emails using the smtplib library. Learn how to automate repetitive tasks and work with Python standard libraries.",
+ "features": [
+ "Compose and send emails via Python",
+ "Send to multiple recipients",
+ "Add subject and body text",
+ "Read recipient list from a text file"
+ ],
+ "tech_stack": ["Python", "smtplib", "email module"],
+ "roadmap": [
+ "Step 1: Set up Python file and import smtplib",
+ "Step 2: Configure sender email and password",
+ "Step 3: Write the email composition function",
+ "Step 4: Connect to Gmail SMTP server",
+ "Step 5: Send email to one recipient and test",
+ "Step 6: Read recipient list from a text file",
+ "Step 7: Loop through recipients and send to all"
+ ],
+ "resources": [
+ "Python smtplib docs: https://docs.python.org/3/library/smtplib.html",
+ "Real Python email guide: https://realpython.com/python-send-email",
+ "Gmail SMTP settings: https://support.google.com/mail"
+ ],
+ "starter_code": "starter_code/email_automation.py"
+ },
+ {
+ "id": 14,
+ "title": "Quiz App",
+ "skills": ["HTML", "CSS", "JavaScript"],
+ "level": "Beginner",
+ "interest": "Games",
+ "time": "Low",
+ "description": "A browser-based quiz app with multiple choice questions, a score counter, and a results screen. Perfect for practising DOM manipulation and event handling in JavaScript.",
+ "features": [
+ "Display one question at a time",
+ "Four multiple choice options per question",
+ "Show correct or incorrect feedback instantly",
+ "Display final score on results screen"
+ ],
+ "tech_stack": ["HTML", "CSS", "JavaScript"],
+ "roadmap": [
+ "Step 1: Create HTML structure for question and options",
+ "Step 2: Style the quiz card with CSS",
+ "Step 3: Store questions as a JavaScript array of objects",
+ "Step 4: Write a function to display each question",
+ "Step 5: Add click event listeners to option buttons",
+ "Step 6: Check the selected answer and update score",
+ "Step 7: Move to the next question automatically",
+ "Step 8: Show the results screen with final score"
+ ],
+ "resources": [
+ "MDN DOM guide: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model",
+ "JavaScript events: https://javascript.info/events",
+ "W3Schools JavaScript: https://www.w3schools.com/js"
+ ],
+ "starter_code": "starter_code/quiz_app.html"
+ },
+ {
+ "id": 15,
+ "title": "Movie Recommendation System",
+ "skills": ["Python"],
+ "level": "Intermediate",
+ "interest": "Data",
+ "time": "High",
+ "description": "A recommendation system that suggests movies to users based on ratings and similarity scores. An intermediate project for learning similarity scoring, pandas data handling, and basic recommendation logic.",
+ "features": [
+ "Recommend movies based on user preferences",
+ "Find similar movies using similarity metrics",
+ "Load and process movie datasets",
+ "Display top recommended movies"
+ ],
+ "tech_stack": ["Python", "pandas", "scikit-learn", "NumPy"],
+ "roadmap": [
+ "Step 1: Download and inspect a movie ratings dataset",
+ "Step 2: Load the dataset using pandas",
+ "Step 3: Clean and preprocess missing data",
+ "Step 4: Organize movie features into a pandas DataFrame",
+ "Step 5: Compare movies using cosine similarity",
+ "Step 6: Build the recommendation function",
+ "Step 7: Display top movie recommendations to the user",
+ "Step 8: Test the recommendation system with sample inputs"
+ ],
+ "resources": [
+ "pandas documentation: https://pandas.pydata.org/docs",
+ "scikit-learn user guide: https://scikit-learn.org/stable/user_guide.html",
+ "MovieLens dataset: https://grouplens.org/datasets/movielens",
+ "Cosine Similarity: https://naomy-gomes.medium.com/the-cosine-similarity-and-its-use-in-recommendation-systems-cb2ebd811ce1"
+ ],
+ "starter_code": "starter_code/movie_recommender.py"
+ },
+ {
+ "id": 16,
+ "title": "Sentiment Analysis Web App",
+ "skills": ["Python", "HTML", "CSS"],
+ "level": "Intermediate",
+ "interest": "Data",
+ "time": "High",
+ "description": "A web application that predicts whether user entered text has positive, negative, or neutral sentiment. Great for learning text preprocessing and basic sentiment prediction.",
+ "features": [
+ "Analyze sentiment from user input text",
+ "Display sentiment prediction results",
+ "Preprocess text using NLP techniques",
+ "Deploy the model with a simple web interface"
+ ],
+ "tech_stack": ["Python", "Flask", "scikit-learn", "HTML", "CSS"],
+ "roadmap": [
+ "Step 1: Collect or download a sentiment dataset",
+ "Step 2: Preprocess text data by cleaning and tokenizing",
+ "Step 3: Convert text into numerical features",
+ "Step 4: Build a simple sentiment prediction model",
+ "Step 5: Test predictions with sample text inputs",
+ "Step 6: Build a Flask app for user interaction",
+ "Step 7: Connect the trained model to the web app",
+ "Step 8: Test the application with sample text inputs"
+ ],
+ "resources": [
+ "Flask documentation: https://flask.palletsprojects.com",
+ "scikit-learn text tutorial: https://scikit-learn.org/stable/",
+ "NLTK documentation: https://www.nltk.org",
+ "Tokenization: https://www.geeksforgeeks.org/nlp/nlp-how-tokenizing-text-sentence-words-works/"
+ ],
+ "starter_code": "starter_code/sentiment_app.py"
+ }
+ ,
+ {
+ "id": 17,
+ "title": "Sunrise Grand Hotel Management System",
+ "skills": ["Python"],
+ "level": "Medium",
+ "interest": "Business Logic",
+ "time": "Medium",
+ "description": "A robust CLI application to manage a 40-room inventory with tiered pricing, guest booking logic, and automatic billing calculation.",
+ "features": [
+ "Dynamic 40-room inventory across 4 luxury categories",
+ "Real-time availability filtering and room status tracking",
+ "Booking engine with 10-digit contact validation",
+ "Automatic stay duration and total price calculation",
+ "Cancellation logic and detailed guest lookup"
+ ],
+ "tech_stack": ["Python", "Dictionaries", "Input Validation"],
+ "roadmap": [
+ "Step 1: Initialize the 40-room inventory with tiered pricing",
+ "Step 2: Implement availability display and category filtering",
+ "Step 3: Build the booking engine with contact validation",
+ "Step 4: Create the cancellation and guest detail lookup logic",
+ "Step 5: Wrap all logic in a persistent main menu loop"
+ ],
+ "resources": [
+ "Python Dictionaries: https://docs.python.org/3/tutorial/datastructures.html#dictionaries",
+ "Python Input Validation: https://realpython.com/python-input-output/",
+ "Menu-driven CLI logic: https://www.geeksforgeeks.org/how-to-create-a-menu-driven-program-in-python/"
+ ],
+ "starter_code": "starter_code/hotel_management.py"
+ },
+ {
+ "id": 18,
+ "title": "CLI URL Shortener",
+ "skills": ["Go"],
+ "level": "Beginner",
+ "interest": "Tools",
+ "time": "Low",
+ "description": "A command-line tool that generates short hash codes for long URLs and stores the mappings in a local JSON file. Great for learning Go's standard library, file I/O, and CLI argument handling.",
+ "features": [
+ "Accept a URL as a CLI argument",
+ "Generate a short hash code using MD5",
+ "Store URL mappings in a local JSON file",
+ "Resolve a short code back to the original URL"
+ ],
+ "tech_stack": ["Go", "encoding/json", "crypto/md5", "os", "fmt"],
+ "roadmap": [
+ "Step 1: Initialise a Go module with go mod init",
+ "Step 2: Accept a URL as a CLI argument using os.Args",
+ "Step 3: Generate a short key by hashing the URL with MD5",
+ "Step 4: Read and write the mappings map to a JSON file",
+ "Step 5: Add a resolve subcommand to look up a short code",
+ "Step 6: Handle errors gracefully for missing or invalid input",
+ "Step 7: Test shorten and resolve with several sample URLs"
+ ],
+ "resources": [
+ "Go Tour: https://tour.golang.org",
+ "Go by Example: https://gobyexample.com",
+ "Go standard library docs: https://pkg.go.dev/std"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 19,
+ "title": "Go REST API Server",
+ "skills": ["Go"],
+ "level": "Intermediate",
+ "interest": "Web",
+ "time": "Medium",
+ "description": "A RESTful API server using Go's net/http standard library with full CRUD endpoints for a notes resource. Teaches HTTP routing, JSON encoding, and in-memory data management in Go.",
+ "features": [
+ "GET /notes — list all notes",
+ "POST /notes — create a new note",
+ "PUT /notes/{id} — update an existing note",
+ "DELETE /notes/{id} — remove a note",
+ "JSON request and response handling throughout"
+ ],
+ "tech_stack": ["Go", "net/http", "encoding/json", "strconv"],
+ "roadmap": [
+ "Step 1: Create main.go and start an HTTP server with http.ListenAndServe",
+ "Step 2: Define a Note struct with ID, Title, and Body fields",
+ "Step 3: Use a map as an in-memory store with a counter for IDs",
+ "Step 4: Write handler functions for each HTTP method",
+ "Step 5: Register routes using http.HandleFunc",
+ "Step 6: Parse request bodies with json.NewDecoder",
+ "Step 7: Write JSON responses with json.NewEncoder",
+ "Step 8: Test all endpoints with curl or Postman"
+ ],
+ "resources": [
+ "net/http docs: https://pkg.go.dev/net/http",
+ "Go by Example — HTTP servers: https://gobyexample.com/http-servers",
+ "Go by Example — JSON: https://gobyexample.com/json"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 20,
+ "title": "File Duplicate Finder",
+ "skills": ["Rust"],
+ "level": "Beginner",
+ "interest": "Tools",
+ "time": "Low",
+ "description": "A CLI tool that recursively scans a directory, hashes every file using SHA-256, and reports groups of duplicates. A practical first Rust project for learning ownership, iterators, and the standard library.",
+ "features": [
+ "Recursively walk a directory with std::fs",
+ "Hash each file using the sha2 crate",
+ "Group files by hash and print duplicate sets",
+ "Accept target directory as a CLI argument"
+ ],
+ "tech_stack": ["Rust", "std::fs", "std::collections::HashMap", "sha2 crate"],
+ "roadmap": [
+ "Step 1: Create a new project with cargo new and add sha2 to Cargo.toml",
+ "Step 2: Accept a directory path from std::env::args",
+ "Step 3: Walk the directory recursively using std::fs::read_dir",
+ "Step 4: Read each file and compute its SHA-256 hash",
+ "Step 5: Store results in a HashMap keyed by hash",
+ "Step 6: Print any hash bucket that contains more than one path",
+ "Step 7: Test on a folder with known duplicate files"
+ ],
+ "resources": [
+ "The Rust Book: https://doc.rust-lang.org/book/",
+ "sha2 crate: https://crates.io/crates/sha2",
+ "Rust std::fs docs: https://doc.rust-lang.org/std/fs/"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 21,
+ "title": "Markdown to HTML Converter",
+ "skills": ["Rust"],
+ "level": "Intermediate",
+ "interest": "Tools",
+ "time": "Medium",
+ "description": "A CLI tool that reads a Markdown file and produces a styled HTML file using the pulldown-cmark parser. Teaches Rust's crate ecosystem, file handling, and string processing.",
+ "features": [
+ "Parse headings, bold, italic, lists, and links",
+ "Generate a complete and valid HTML file",
+ "Accept input and output file paths as CLI arguments",
+ "Wrap output in a minimal HTML template with inline CSS"
+ ],
+ "tech_stack": ["Rust", "pulldown-cmark", "std::fs", "std::env"],
+ "roadmap": [
+ "Step 1: Create a Cargo project and add pulldown-cmark to Cargo.toml",
+ "Step 2: Read the input Markdown file path from CLI arguments",
+ "Step 3: Load the file content into a String with std::fs::read_to_string",
+ "Step 4: Use pulldown-cmark Parser and push_html to convert to HTML",
+ "Step 5: Wrap the generated HTML in a full page template",
+ "Step 6: Write the output to the specified file path",
+ "Step 7: Test with a Markdown file containing all common elements"
+ ],
+ "resources": [
+ "pulldown-cmark docs: https://docs.rs/pulldown-cmark",
+ "Rust CLI book: https://rust-cli.github.io/book/",
+ "The Rust Book: https://doc.rust-lang.org/book/"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 22,
+ "title": "Android Tip Calculator",
+ "skills": ["Kotlin"],
+ "level": "Beginner",
+ "interest": "Mobile",
+ "time": "Low",
+ "description": "An Android app that calculates the tip amount and total bill in real time as the user types a bill amount and adjusts a tip percentage slider. A solid first Kotlin project for learning Android UI basics.",
+ "features": [
+ "Input field for the bill amount",
+ "SeekBar to select tip percentage from 0 to 30",
+ "Live display of tip amount and total bill",
+ "Handles empty or invalid input gracefully"
+ ],
+ "tech_stack": ["Kotlin", "Android SDK", "View Binding", "SeekBar"],
+ "roadmap": [
+ "Step 1: Create a new Android project in Android Studio with Kotlin",
+ "Step 2: Build the layout with EditText, SeekBar, and TextViews",
+ "Step 3: Enable View Binding in build.gradle",
+ "Step 4: Add a TextWatcher to the EditText to react to input changes",
+ "Step 5: Add a SeekBar.OnSeekBarChangeListener for the slider",
+ "Step 6: Calculate tip and total on every change event",
+ "Step 7: Display results and handle the empty-input edge case"
+ ],
+ "resources": [
+ "Android Kotlin Fundamentals: https://developer.android.com/courses/kotlin-android-fundamentals/overview",
+ "Kotlin docs: https://kotlinlang.org/docs/",
+ "Android View Binding: https://developer.android.com/topic/libraries/view-binding"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 23,
+ "title": "Kotlin Weather App",
+ "skills": ["Kotlin"],
+ "level": "Intermediate",
+ "interest": "Mobile",
+ "time": "Medium",
+ "description": "An Android weather app that fetches current conditions from the OpenWeatherMap API and displays temperature, humidity, and a weather description. Teaches Retrofit, Kotlin Coroutines, and API integration.",
+ "features": [
+ "Search weather by city name",
+ "Display temperature, humidity, and conditions",
+ "Show a loading indicator while fetching",
+ "Display an error message if the city is not found"
+ ],
+ "tech_stack": ["Kotlin", "Retrofit", "Kotlin Coroutines", "OpenWeatherMap API", "Gson"],
+ "roadmap": [
+ "Step 1: Create an Android project and add Retrofit and Gson dependencies",
+ "Step 2: Sign up for a free OpenWeatherMap API key",
+ "Step 3: Define a data class matching the API JSON response",
+ "Step 4: Build a Retrofit interface with a suspend GET function",
+ "Step 5: Launch a coroutine in the button click listener to fetch data",
+ "Step 6: Show a ProgressBar while the request is in flight",
+ "Step 7: Update the UI with the result or display an error message"
+ ],
+ "resources": [
+ "Retrofit docs: https://square.github.io/retrofit/",
+ "Kotlin Coroutines guide: https://kotlinlang.org/docs/coroutines-overview.html",
+ "OpenWeatherMap API: https://openweathermap.org/api"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 24,
+ "title": "Java Student Grade Tracker",
+ "skills": ["Java"],
+ "level": "Beginner",
+ "interest": "Education",
+ "time": "Low",
+ "description": "A console application that stores student names and grades, calculates individual and class averages, and prints a formatted summary report. Great for practising Java OOP basics, ArrayLists, and Scanner input.",
+ "features": [
+ "Add students and record multiple grades per student",
+ "Calculate individual student averages",
+ "Calculate overall class average",
+ "Print a formatted report for all students"
+ ],
+ "tech_stack": ["Java", "ArrayList", "HashMap", "Scanner"],
+ "roadmap": [
+ "Step 1: Set up a Java project in IntelliJ IDEA or VS Code",
+ "Step 2: Create a Student class with a name field and a grades ArrayList",
+ "Step 3: Write an addGrade() method and an getAverage() method",
+ "Step 4: Use a Scanner to accept student names and grades from the user",
+ "Step 5: Store Student objects in an ArrayList",
+ "Step 6: Implement a printReport() method that formats the output",
+ "Step 7: Calculate and display the overall class average at the end"
+ ],
+ "resources": [
+ "Java SE docs: https://docs.oracle.com/en/java/",
+ "MOOC Java programming: https://java-programming.mooc.fi/",
+ "W3Schools Java: https://www.w3schools.com/java"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 25,
+ "title": "Java Bank Account System",
+ "skills": ["Java"],
+ "level": "Intermediate",
+ "interest": "Web",
+ "time": "Medium",
+ "description": "An OOP-based console banking application with deposit, withdrawal, balance inquiry, and transaction history. Demonstrates inheritance, encapsulation, and polymorphism through a BankAccount class hierarchy.",
+ "features": [
+ "BankAccount base class with deposit and withdrawal",
+ "SavingsAccount subclass with minimum balance enforcement",
+ "CheckingAccount subclass with overdraft protection",
+ "Transaction history stored as an ArrayList of strings",
+ "Menu-driven console interface"
+ ],
+ "tech_stack": ["Java", "OOP", "ArrayList", "Scanner", "Inheritance"],
+ "roadmap": [
+ "Step 1: Design the class hierarchy on paper before writing any code",
+ "Step 2: Implement the BankAccount base class with balance and transaction list",
+ "Step 3: Write deposit() and withdraw() with input validation",
+ "Step 4: Create SavingsAccount subclass with minimum balance check",
+ "Step 5: Create CheckingAccount subclass with overdraft logic",
+ "Step 6: Build a menu-driven console interface using Scanner",
+ "Step 7: Add a printHistory() method to display all transactions",
+ "Step 8: Test each account type with edge case inputs"
+ ],
+ "resources": [
+ "Java OOP tutorial: https://docs.oracle.com/javase/tutorial/java/concepts/",
+ "MOOC Java programming: https://java-programming.mooc.fi/",
+ "Java inheritance guide: https://www.w3schools.com/java/java_inheritance.asp"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 26,
+ "title": "TypeScript Todo App",
+ "skills": ["TypeScript", "HTML", "CSS"],
+ "level": "Beginner",
+ "interest": "Productivity",
+ "time": "Low",
+ "description": "A browser-based todo list app written entirely in TypeScript with strongly typed interfaces, DOM manipulation, and in-page state management. A great first TypeScript project that reinforces type safety concepts.",
+ "features": [
+ "Add and delete tasks",
+ "Mark tasks as complete with a strikethrough",
+ "Filter view by All, Active, or Completed",
+ "TypeScript interfaces used for all data structures"
+ ],
+ "tech_stack": ["TypeScript", "HTML", "CSS", "tsc compiler"],
+ "roadmap": [
+ "Step 1: Initialise the project with npm init and install TypeScript",
+ "Step 2: Create tsconfig.json targeting ES6 with strict mode on",
+ "Step 3: Define a Task interface with id, title, and completed fields",
+ "Step 4: Write addTask(), deleteTask(), and toggleTask() functions with full types",
+ "Step 5: Build the HTML structure for the task list",
+ "Step 6: Write a render() function that re-draws the list from state",
+ "Step 7: Compile with tsc and wire up the HTML file",
+ "Step 8: Test add, delete, toggle, and filter interactions"
+ ],
+ "resources": [
+ "TypeScript Handbook: https://www.typescriptlang.org/docs/handbook/intro.html",
+ "TypeScript in 5 minutes: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html",
+ "MDN DOM reference: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 27,
+ "title": "TypeScript REST API Client",
+ "skills": ["TypeScript"],
+ "level": "Intermediate",
+ "interest": "Web",
+ "time": "Medium",
+ "description": "A fully typed API client that queries a public REST API using the Fetch API, maps the response to TypeScript interfaces, and renders the data to the DOM. Teaches type-safe async programming and error handling.",
+ "features": [
+ "Typed interfaces matching the API response shape",
+ "Async fetch wrapper with a typed return value",
+ "Loading and error state handling",
+ "Render fetched data to the DOM dynamically"
+ ],
+ "tech_stack": ["TypeScript", "Fetch API", "HTML", "CSS", "tsconfig"],
+ "roadmap": [
+ "Step 1: Set up a TypeScript project with tsconfig.json",
+ "Step 2: Choose a public API (e.g. JSONPlaceholder or Open Library)",
+ "Step 3: Define interfaces that match the API response structure",
+ "Step 4: Write an async fetchData() generic function",
+ "Step 5: Call the function and handle the Promise with try/catch",
+ "Step 6: Show a loading message while the request is in flight",
+ "Step 7: Render the typed response data into the DOM",
+ "Step 8: Display a user-friendly error message on failure"
+ ],
+ "resources": [
+ "TypeScript Handbook: https://www.typescriptlang.org/docs/handbook/",
+ "Using Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch",
+ "JSONPlaceholder (free test API): https://jsonplaceholder.typicode.com/"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 28,
+ "title": "C++ Student Records Manager",
+ "skills": ["C++"],
+ "level": "Beginner",
+ "interest": "Education",
+ "time": "Low",
+ "description": "A console application to manage student records using structs, vectors, and file I/O in C++. Ideal for learning C++ fundamentals including structs, loops, and the fstream library.",
+ "features": [
+ "Add and display student records",
+ "Save all records to a text file with ofstream",
+ "Load existing records from file on startup",
+ "Search for a student record by name"
+ ],
+ "tech_stack": ["C++", "struct", "vector", "fstream", "iostream"],
+ "roadmap": [
+ "Step 1: Define a Student struct with name, roll number, and grade fields",
+ "Step 2: Use a std::vector to store Student objects in memory",
+ "Step 3: Write an addStudent() function that reads from cin",
+ "Step 4: Write a displayAll() function that prints the vector",
+ "Step 5: Implement saveToFile() using std::ofstream",
+ "Step 6: Implement loadFromFile() using std::ifstream on startup",
+ "Step 7: Add a searchByName() function using a linear scan",
+ "Step 8: Wrap all functionality in a menu-driven main loop"
+ ],
+ "resources": [
+ "LearnCpp: https://www.learncpp.com/",
+ "cppreference std::fstream: https://en.cppreference.com/w/cpp/io/basic_fstream",
+ "cppreference std::vector: https://en.cppreference.com/w/cpp/container/vector"
+ ],
+ "starter_code": null
+ },
+ {
+ "id": 29,
+ "title": "C++ Task Scheduler",
+ "skills": ["C++"],
+ "level": "Intermediate",
+ "interest": "Automation",
+ "time": "Medium",
+ "description": "A CLI task scheduler that queues tasks with priority levels and processes them in order using a priority queue from the C++ STL. Teaches STL containers, custom comparators, and structured program design.",
+ "features": [
+ "Add tasks with a name and integer priority",
+ "Process tasks in priority order using std::priority_queue",
+ "Log each completed task to the console",
+ "Display the pending queue at any time"
+ ],
+ "tech_stack": ["C++", "std::priority_queue", "struct", "iostream", "vector"],
+ "roadmap": [
+ "Step 1: Define a Task struct with name and priority fields",
+ "Step 2: Write a custom comparator struct for the priority queue",
+ "Step 3: Implement addTask() to push onto the priority queue",
+ "Step 4: Implement runNext() to pop and log the top task",
+ "Step 5: Add a displayQueue() function that copies and drains a temp queue",
+ "Step 6: Build a menu-driven console interface",
+ "Step 7: Test with tasks of varying priorities to verify ordering"
+ ],
+ "resources": [
+ "cppreference std::priority_queue: https://en.cppreference.com/w/cpp/container/priority_queue",
+ "LearnCpp: https://www.learncpp.com/",
+ "C++ STL tutorial: https://www.geeksforgeeks.org/the-c-standard-template-library-stl/"
+ ],
+ "starter_code": null
+ }
+]
+
+
diff --git a/utils/data_loader.py b/utils/data_loader.py
index bc529a3b..17da28ef 100644
--- a/utils/data_loader.py
+++ b/utils/data_loader.py
@@ -6,6 +6,9 @@
_projects_cache = None
_cache_lock = threading.Lock()
+_projects_cache = None
+_cache_lock = threading.Lock()
+
def validate_projects(projects):
"""
Validate project dataset integrity.