From f88c48e6cd17dabddea56bafe2256f364c01ce35 Mon Sep 17 00:00:00 2001 From: KAUSTUBH SRIVASTAVA Date: Sun, 7 Jun 2026 12:59:09 +0530 Subject: [PATCH 1/2] Comparision feature added - User can compare between 2 features --- data/career_roadmaps.json | 158 +++++++++ routes/main_routes.py | 36 +- static/compare.js | 223 ++++++++++++ static/script.js | 45 +++ static/style.css | 699 ++++++++++++++++++++++++++++++++++++-- templates/compare.html | 238 +++++++++++++ templates/index.html | 137 ++++---- tests/test_basic.py | 86 +++++ utils/roadmap_comparer.py | 169 +++++++++ 9 files changed, 1686 insertions(+), 105 deletions(-) create mode 100644 data/career_roadmaps.json create mode 100644 static/compare.js create mode 100644 templates/compare.html create mode 100644 utils/roadmap_comparer.py diff --git a/data/career_roadmaps.json b/data/career_roadmaps.json new file mode 100644 index 00000000..6d1e5f65 --- /dev/null +++ b/data/career_roadmaps.json @@ -0,0 +1,158 @@ +[ + { + "id": "frontend", + "title": "Frontend Development", + "description": "Build modern user interfaces with HTML, CSS, JavaScript, and popular frontend frameworks.", + "topics": [ + "HTML & Semantic Markup", + "CSS Layout & Responsive Design", + "JavaScript Fundamentals", + "DOM Manipulation", + "React Basics", + "State Management", + "Accessibility (a11y)", + "Browser DevTools" + ], + "skills": ["HTML", "CSS", "JavaScript", "React", "Git", "Responsive Design", "REST APIs"], + "duration": "3–6 months", + "duration_weeks": 20, + "difficulty": "Beginner", + "difficulty_score": 2, + "career_opportunities": [ + "Frontend Developer", + "UI Developer", + "Web Designer", + "React Developer" + ] + }, + { + "id": "fullstack", + "title": "Full Stack Development", + "description": "End-to-end web development covering both client-side UI and server-side APIs, databases, and deployment.", + "topics": [ + "Frontend Fundamentals", + "Backend APIs (REST)", + "Database Design (SQL)", + "Authentication & Sessions", + "Server Deployment", + "Testing Full Applications", + "DevOps Basics", + "Project Architecture" + ], + "skills": ["HTML", "CSS", "JavaScript", "Python", "Flask", "SQL", "Git", "REST APIs", "Deployment"], + "duration": "6–12 months", + "duration_weeks": 36, + "difficulty": "Intermediate", + "difficulty_score": 4, + "career_opportunities": [ + "Full Stack Developer", + "Software Engineer", + "Web Application Developer", + "Startup Engineer" + ] + }, + { + "id": "react", + "title": "React Development", + "description": "Specialize in building component-based UIs with React, hooks, routing, and modern frontend tooling.", + "topics": [ + "JSX & Components", + "Props & State", + "React Hooks (useState, useEffect)", + "React Router", + "Context API", + "API Integration with fetch", + "Component Testing", + "Build Tools (Vite/CRA)" + ], + "skills": ["JavaScript", "React", "HTML", "CSS", "Git", "REST APIs", "npm"], + "duration": "2–4 months", + "duration_weeks": 14, + "difficulty": "Beginner", + "difficulty_score": 2, + "career_opportunities": [ + "React Developer", + "Frontend Engineer", + "UI Engineer", + "JavaScript Developer" + ] + }, + { + "id": "angular", + "title": "Angular Development", + "description": "Build enterprise-grade single-page applications with Angular, TypeScript, and RxJS.", + "topics": [ + "TypeScript Fundamentals", + "Angular Components & Modules", + "Services & Dependency Injection", + "RxJS & Observables", + "Angular Routing", + "Forms (Template & Reactive)", + "HTTP Client", + "Unit Testing with Jasmine" + ], + "skills": ["TypeScript", "Angular", "RxJS", "HTML", "CSS", "Git", "REST APIs"], + "duration": "3–5 months", + "duration_weeks": 18, + "difficulty": "Intermediate", + "difficulty_score": 3, + "career_opportunities": [ + "Angular Developer", + "Frontend Engineer", + "Enterprise Web Developer", + "TypeScript Developer" + ] + }, + { + "id": "devops", + "title": "DevOps Engineering", + "description": "Automate software delivery with CI/CD pipelines, containerization, infrastructure as code, and monitoring.", + "topics": [ + "Linux & Shell Scripting", + "Git & Version Control Workflows", + "CI/CD Pipelines", + "Docker & Containers", + "Kubernetes Basics", + "Infrastructure as Code (Terraform)", + "Monitoring & Logging", + "Cloud Deployment" + ], + "skills": ["Linux", "Docker", "Kubernetes", "Git", "CI/CD", "Terraform", "AWS", "Python", "Bash"], + "duration": "4–8 months", + "duration_weeks": 28, + "difficulty": "Intermediate", + "difficulty_score": 4, + "career_opportunities": [ + "DevOps Engineer", + "Site Reliability Engineer (SRE)", + "Platform Engineer", + "Release Engineer" + ] + }, + { + "id": "cloud", + "title": "Cloud Engineering", + "description": "Design, deploy, and manage scalable cloud-native applications on AWS, Azure, or GCP.", + "topics": [ + "Cloud Computing Fundamentals", + "Virtual Machines & Networking", + "Serverless Architecture", + "Cloud Storage & Databases", + "Identity & Access Management (IAM)", + "Auto-scaling & Load Balancing", + "Cloud Security Best Practices", + "Cost Optimization" + ], + "skills": ["AWS", "Azure", "Linux", "Docker", "Terraform", "Python", "Networking", "SQL"], + "duration": "4–8 months", + "duration_weeks": 28, + "difficulty": "Intermediate", + "difficulty_score": 4, + "career_opportunities": [ + "Cloud Engineer", + "Cloud Architect", + "Solutions Architect", + "Cloud Security Engineer" + ] + } +] diff --git a/routes/main_routes.py b/routes/main_routes.py index db68640b..f023c328 100644 --- a/routes/main_routes.py +++ b/routes/main_routes.py @@ -7,6 +7,7 @@ from utils.recommender import get_recommendations, validate_recommendation_inputs from utils.data_loader import find_project_by_id, load_all_projects, get_available_levels, get_project_stats +from utils.roadmap_comparer import load_all_career_roadmaps, compare_roadmaps from utils.file_server import read_starter_code, resolve_starter_file, get_starter_code_dir from config import Config import os @@ -38,6 +39,39 @@ def index(): def contact(): return render_template("contact.html") + +@main.route("/compare") +def compare_page(): + """Render the career roadmap comparison page.""" + roadmaps = load_all_career_roadmaps() + return render_template("compare.html", roadmaps=roadmaps, config=Config) + + +@main.route("/api/roadmaps") +def list_roadmaps(): + """Return all career roadmaps as JSON.""" + return jsonify(load_all_career_roadmaps()), 200 + + +@main.route("/api/compare") +def compare_roadmaps_api(): + """Return a side-by-side comparison of two career roadmaps.""" + roadmap_a = (request.args.get("a") or "").strip() + roadmap_b = (request.args.get("b") or "").strip() + + if not roadmap_a or not roadmap_b: + return jsonify({"error": "Both 'a' and 'b' query parameters are required."}), 400 + + result = compare_roadmaps(roadmap_a, roadmap_b) + + if result is None: + return jsonify({"error": "One or both roadmap IDs were not found."}), 404 + + if result.get("error"): + return jsonify(result), 400 + + return jsonify(result), 200 + @main.route("/health") def health_check(): """ @@ -151,7 +185,7 @@ def sitemap(): base = request.host_url.rstrip("/") projects = load_all_projects() - urls = [f"{base}/"] + urls = [f"{base}/", f"{base}/compare"] for p in projects: urls.append(f"{base}/project/{p['id']}") diff --git a/static/compare.js b/static/compare.js new file mode 100644 index 00000000..ee875c89 --- /dev/null +++ b/static/compare.js @@ -0,0 +1,223 @@ +// compare.js — Career roadmap comparison page logic + +(function () { + var selectA = document.getElementById("roadmap-select-a"); + var selectB = document.getElementById("roadmap-select-b"); + var compareBtn = document.getElementById("compare-btn"); + var errorEl = document.getElementById("compare-error"); + var emptyEl = document.getElementById("compare-empty"); + var resultsEl = document.getElementById("compare-results"); + var quickBtns = document.querySelectorAll(".compare-quick-btn"); + + if (!selectA || !selectB || !compareBtn) return; + + function showError(message) { + if (!errorEl) return; + errorEl.textContent = message; + errorEl.hidden = !message; + } + + function setLoading(isLoading) { + compareBtn.disabled = isLoading; + compareBtn.textContent = isLoading ? "Comparing…" : "Compare Roadmaps"; + } + + function renderBarChart(container, labelA, labelB, valueA, valueB, maxValue, unit) { + if (!container) return; + container.innerHTML = ""; + + var rows = [ + { label: labelA, value: valueA, cls: "compare-bar--a" }, + { label: labelB, value: valueB, cls: "compare-bar--b" }, + ]; + + rows.forEach(function (row) { + var pct = maxValue > 0 ? Math.round((row.value / maxValue) * 100) : 0; + var wrap = document.createElement("div"); + wrap.className = "compare-bar-row"; + + var name = document.createElement("span"); + name.className = "compare-bar-label"; + name.textContent = row.label; + + var track = document.createElement("div"); + track.className = "compare-bar-track"; + track.setAttribute("role", "meter"); + track.setAttribute("aria-valuemin", "0"); + track.setAttribute("aria-valuemax", String(maxValue)); + track.setAttribute("aria-valuenow", String(row.value)); + + var fill = document.createElement("div"); + fill.className = "compare-bar-fill " + row.cls; + fill.style.width = pct + "%"; + + var val = document.createElement("span"); + val.className = "compare-bar-value"; + val.textContent = row.value + (unit || ""); + + track.appendChild(fill); + wrap.appendChild(name); + wrap.appendChild(track); + wrap.appendChild(val); + container.appendChild(wrap); + }); + } + + function renderTagList(el, items, sharedSet, side) { + if (!el) return; + el.innerHTML = ""; + items.forEach(function (item) { + var li = document.createElement("li"); + li.textContent = item; + var norm = item.toLowerCase(); + if (sharedSet && sharedSet.has(norm)) { + li.className = "compare-tag compare-tag--shared"; + } else { + li.className = "compare-tag compare-tag--" + side; + } + el.appendChild(li); + }); + } + + function renderSkillChips(el, items, chipClass) { + if (!el) return; + el.innerHTML = ""; + if (!items.length) { + var empty = document.createElement("li"); + empty.className = "compare-skill-empty"; + empty.textContent = "None"; + el.appendChild(empty); + return; + } + items.forEach(function (skill) { + var li = document.createElement("li"); + li.className = "compare-skill-chip " + (chipClass || ""); + li.textContent = skill; + el.appendChild(li); + }); + } + + function renderComparison(data) { + var a = data.roadmap_a; + var b = data.roadmap_b; + var metrics = data.metrics; + + document.getElementById("summary-title-a").textContent = a.title; + document.getElementById("summary-desc-a").textContent = a.description; + document.getElementById("summary-title-b").textContent = b.title; + document.getElementById("summary-desc-b").textContent = b.description; + + document.getElementById("shared-skills-count").textContent = data.summary.shared_skills_count; + document.getElementById("shared-topics-count").textContent = data.summary.shared_topics_count; + + document.getElementById("detail-title-a").textContent = a.title; + document.getElementById("detail-title-b").textContent = b.title; + document.getElementById("meta-duration-a").textContent = a.duration; + document.getElementById("meta-duration-b").textContent = b.duration; + document.getElementById("meta-difficulty-a").textContent = a.difficulty; + document.getElementById("meta-difficulty-b").textContent = b.difficulty; + + var sharedTopicSet = new Set( + data.overlapping_topics.map(function (t) { return t.toLowerCase(); }) + ); + + renderTagList(document.getElementById("topics-list-a"), a.topics, sharedTopicSet, "a"); + renderTagList(document.getElementById("topics-list-b"), b.topics, sharedTopicSet, "b"); + + renderTagList(document.getElementById("careers-list-a"), a.career_opportunities, null, "a"); + renderTagList(document.getElementById("careers-list-b"), b.career_opportunities, null, "b"); + + renderSkillChips(document.getElementById("unique-skills-a"), data.unique_skills_a, "compare-skill-chip--a"); + renderSkillChips(document.getElementById("shared-skills"), data.overlapping_skills, "compare-skill-chip--shared"); + renderSkillChips(document.getElementById("unique-skills-b"), data.unique_skills_b, "compare-skill-chip--b"); + + var maxTopics = Math.max(metrics.topics_count.a, metrics.topics_count.b, 1); + var maxSkills = Math.max(metrics.skills_count.a, metrics.skills_count.b, 1); + + renderBarChart( + document.getElementById("chart-duration"), + a.title, b.title, + metrics.duration_weeks.a, metrics.duration_weeks.b, + metrics.duration_weeks.max, " wks" + ); + renderBarChart( + document.getElementById("chart-difficulty"), + a.title, b.title, + metrics.difficulty_score.a, metrics.difficulty_score.b, + metrics.difficulty_score.max, "/5" + ); + renderBarChart( + document.getElementById("chart-topics"), + a.title, b.title, + metrics.topics_count.a, metrics.topics_count.b, + maxTopics, "" + ); + renderBarChart( + document.getElementById("chart-skills"), + a.title, b.title, + metrics.skills_count.a, metrics.skills_count.b, + maxSkills, "" + ); + + emptyEl.hidden = true; + resultsEl.hidden = false; + resultsEl.scrollIntoView({ behavior: "smooth", block: "start" }); + } + + function runComparison() { + var idA = selectA.value; + var idB = selectB.value; + + showError(""); + + if (!idA || !idB) { + showError("Please select both roadmaps before comparing."); + return; + } + + if (idA === idB) { + showError("Please select two different roadmaps to compare."); + return; + } + + setLoading(true); + + fetch("/api/compare?a=" + encodeURIComponent(idA) + "&b=" + encodeURIComponent(idB)) + .then(function (res) { + return res.json().then(function (body) { + return { ok: res.ok, status: res.status, body: body }; + }); + }) + .then(function (result) { + if (!result.ok) { + showError(result.body.error || "Comparison failed. Please try again."); + return; + } + renderComparison(result.body); + }) + .catch(function () { + showError("Network error. Please check your connection and try again."); + }) + .finally(function () { + setLoading(false); + }); + } + + compareBtn.addEventListener("click", runComparison); + + quickBtns.forEach(function (btn) { + btn.addEventListener("click", function () { + selectA.value = btn.getAttribute("data-a") || ""; + selectB.value = btn.getAttribute("data-b") || ""; + runComparison(); + }); + }); + + // Pre-select from URL query params: /compare?a=frontend&b=fullstack + var params = new URLSearchParams(window.location.search); + var paramA = params.get("a"); + var paramB = params.get("b"); + if (paramA) selectA.value = paramA; + if (paramB) selectB.value = paramB; + if (paramA && paramB) runComparison(); +})(); diff --git a/static/script.js b/static/script.js index 28dbbeb8..fbf6341f 100644 --- a/static/script.js +++ b/static/script.js @@ -550,6 +550,51 @@ updateProfileWidgets(); resultsSection.scrollIntoView({ behavior: "smooth" }); } + function runProjectSearch(query) { + if (!query) return; + setLoadingState(true); + fetch("/api/search?q=" + encodeURIComponent(query)) + .then(function (response) { + return response.json().then(function (data) { + if (!response.ok) throw new Error("Search failed. Please try again."); + return data; + }); + }) + .then(function (projects) { + setLoadingState(false); + recordSearch(); + var message = projects.length + ? null + : "No projects matched \"" + query + "\". Try a different keyword."; + renderResults(projects, message); + var mobileMenu = document.getElementById("nav-mobile-menu"); + var mobileToggle = document.getElementById("nav-mobile-toggle"); + if (mobileMenu && mobileMenu.classList.contains("open")) { + mobileMenu.classList.remove("open"); + if (mobileToggle) { + mobileToggle.classList.remove("open"); + mobileToggle.setAttribute("aria-expanded", "false"); + } + } + }) + .catch(function (err) { + setLoadingState(false); + var general = document.getElementById("form-error-general"); + if (general) general.textContent = err.message || "Search failed. Please try again."; + }); + } + + function bindSearchForm(form, input) { + if (!form || !input) return; + form.addEventListener("submit", function (event) { + event.preventDefault(); + runProjectSearch(input.value.trim()); + }); + } + + bindSearchForm(document.getElementById("topic-search-form"), document.getElementById("topic-search")); + bindSearchForm(document.getElementById("topic-search-form-mobile"), document.getElementById("topic-search-mobile")); + skillsInput.setAttribute("role", "combobox"); skillsInput.setAttribute("aria-expanded", "false"); suggestions.setAttribute("role", "listbox"); diff --git a/static/style.css b/static/style.css index ae3e9a03..e742e531 100644 --- a/static/style.css +++ b/static/style.css @@ -502,21 +502,22 @@ html[data-entry-anim="true"] .form-card form > .btn-submit { animat } .nav-inner { - max-width: 1140px; + max-width: 1200px; margin: 0 auto; - padding: 0 32px; + padding: 0 24px; height: 64px; display: flex; align-items: center; - justify-content: space-between; + gap: 16px; } .nav-logo { font-family: var(--font-display); - font-size: 1.3rem; + font-size: 1.25rem; font-weight: 800; - color: #ffffff; /* always white; navbar bg is always dark */ + color: #ffffff; letter-spacing: -0.04em; + flex-shrink: 0; } .nav-logo:hover { @@ -527,21 +528,118 @@ html[data-entry-anim="true"] .form-card form > .btn-submit { animat color: var(--yellow-400); } +/* Navbar search — matches dark glass navbar theme */ +.navbar-search { + display: flex; + align-items: center; + flex: 1; + min-width: 0; + max-width: 240px; + margin-left: 8px; + background: rgba(255, 255, 255, 0.08); + border: 1.5px solid rgba(255, 255, 255, 0.16); + border-radius: var(--r-full); + overflow: hidden; + transition: border-color var(--t), background var(--t), box-shadow var(--t); +} + +.navbar-search:focus-within { + border-color: rgba(255, 255, 255, 0.32); + background: rgba(255, 255, 255, 0.11); + box-shadow: 0 0 0 3px rgba(79, 110, 247, 0.25); +} + +#topic-search, +#topic-search-mobile { + flex: 1; + min-width: 0; + border: none; + background: transparent; + padding: 8px 14px; + font-size: 0.84rem; + font-family: var(--font-body); + color: #ffffff; +} + +#topic-search::placeholder, +#topic-search-mobile::placeholder { + color: rgba(255, 255, 255, 0.42); +} + +#topic-search:focus, +#topic-search-mobile:focus { + outline: none; +} + +.navbar-search-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + margin: 3px; + border: none; + border-radius: var(--r-full); + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.85); + cursor: pointer; + flex-shrink: 0; + transition: background var(--t), color var(--t), transform var(--t); +} + +.navbar-search-btn:hover { + background: var(--indigo-500); + color: #ffffff; +} + +.navbar-search-btn:focus-visible { + outline: var(--focus-ring-width) solid var(--focus-ring-color); + outline-offset: 1px; +} + +.navbar-search-btn svg { + width: 16px; + height: 16px; +} + +.navbar-search--mobile { + max-width: none; + margin: 0 0 20px; + width: 100%; +} + +.nav-right { + display: flex; + align-items: center; + gap: 20px; + margin-left: auto; + flex-shrink: 0; +} + .nav-links { display: flex; align-items: center; - gap: 32px; + gap: 20px; } .nav-link { position: relative; - font-size: 0.88rem; + font-size: 0.84rem; font-weight: 500; - color: rgba(255, 255, 255, 0.7); + color: rgba(255, 255, 255, 0.72); transition: color var(--t); - text-decoration: none; display: inline-block; + white-space: nowrap; +} + +.nav-actions { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; + padding-left: 4px; + border-left: 1px solid rgba(255, 255, 255, 0.1); } /* Animated underline on nav links */ @@ -581,12 +679,13 @@ html[data-entry-anim="true"] .form-card form > .btn-submit { animat } .nav-btn-outline { - font-size: 0.86rem; + font-size: 0.82rem; font-weight: 600; color: rgba(255, 255, 255, 0.85); border: 1.5px solid rgba(255, 255, 255, 0.28); border-radius: var(--r-full); - padding: 7px 18px; + padding: 6px 14px; + white-space: nowrap; transition: background var(--t), border-color var(--t); } @@ -709,14 +808,60 @@ html[data-entry-anim="true"] .form-card form > .btn-submit { animat border-bottom: none; } -/* Mobile nav breakpoint (upstream) */ -@media (max-width: 768px) { +/* Responsive navbar — tighten before switching to mobile menu */ +@media (max-width: 1100px) { .nav-links { + gap: 14px; + } + + .nav-link { + font-size: 0.8rem; + } + + .navbar-search:not(.navbar-search--mobile) { + max-width: 200px; + } + + .nav-right { + gap: 14px; + } +} + +@media (max-width: 992px) { + .navbar-search:not(.navbar-search--mobile) { + display: none; + } +} + +@media (max-width: 768px) { + .nav-right { display: none; } .nav-mobile-toggle { display: flex; + margin-left: auto; + } + + .nav-mobile-menu .theme-toggle { + display: flex; + align-items: center; + gap: 10px; + background: none; + border: none; + text-align: left; + cursor: pointer; + width: 100%; + padding: 10px 0; + font-size: 0.95rem; + font-weight: 500; + color: rgba(255, 255, 255, 0.8); + } +} + +@media (min-width: 769px) { + .nav-mobile-toggle { + display: none; } } @@ -3458,16 +3603,13 @@ input[type="text"]:not(.skill-input-wrap input):focus { } @media (max-width: 640px) { - .nav-links { - display: none; - } - .nav-mobile-toggle { display: flex; } .nav-inner { - padding: 0 20px; + padding: 0 16px; + gap: 12px; } .hero { @@ -3767,22 +3909,517 @@ html[data-theme="dark"] .btn-view-code-sm { } } -.theme-toggle { - width: 42px; - height: 42px; - border-radius: 12px; - border: 1px solid rgba(255,255,255,0.15); - background: rgba(255,255,255,0.08); - color: white; +/* ============================================================ + WORKING DARK MODE + ============================================================ */ + +/* ============================================================= + CAREER ROADMAP COMPARISON PAGE + ============================================================= */ + +.compare-page-body { + background: var(--bg-secondary); + min-height: 100vh; +} + +.nav-link--active { + color: var(--indigo-600); + font-weight: 600; +} + +.compare-hero { + background: linear-gradient(135deg, var(--indigo-900) 0%, var(--indigo-700) 60%, var(--purple-700) 100%); + padding: 120px 24px 64px; + text-align: center; +} + +.compare-hero-inner { + max-width: 720px; + margin: 0 auto; +} + +.compare-badge { + display: inline-block; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--yellow-300); + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + padding: 6px 14px; + border-radius: var(--r-full); + margin-bottom: 16px; +} + +.compare-title { + font-family: var(--font-display); + font-size: clamp(1.8rem, 4vw, 2.6rem); + font-weight: 800; + color: var(--white); + margin-bottom: 12px; +} + +.compare-subtitle { + font-size: 1rem; + color: rgba(255, 255, 255, 0.82); + line-height: 1.6; + max-width: 580px; + margin: 0 auto; +} + +.compare-main { + max-width: 1100px; + margin: -32px auto 64px; + padding: 0 20px; +} + +.compare-selectors { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 16px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--r-lg); + padding: 24px; + box-shadow: var(--shadow-md); + margin-bottom: 24px; +} + +.compare-select-card { + flex: 1; + min-width: 200px; +} + +.compare-select-label { + display: block; + font-size: 0.82rem; + font-weight: 600; + color: var(--text-body); + margin-bottom: 8px; +} + +.compare-select { + width: 100%; + padding: 12px 14px; + font-size: 0.92rem; + font-family: var(--font-body); + border: 1px solid var(--border); + border-radius: var(--r-md); + background: var(--surface); + color: var(--text-heading); + cursor: pointer; +} + +.compare-select:focus { + outline: 2px solid var(--indigo-500); + outline-offset: 2px; +} + +.compare-vs-badge { + font-family: var(--font-display); + font-weight: 800; + font-size: 0.9rem; + color: var(--indigo-600); + background: var(--indigo-100); + padding: 10px 14px; + border-radius: var(--r-full); + align-self: center; + margin-bottom: 4px; +} + +.compare-btn { + white-space: nowrap; + padding: 12px 24px; +} + +.compare-error { + background: #fef2f2; + color: var(--red-500); + border: 1px solid #fecaca; + border-radius: var(--r-md); + padding: 12px 16px; + font-size: 0.88rem; + font-weight: 500; + margin-bottom: 20px; +} + +.compare-empty { + text-align: center; + padding: 64px 24px; + background: var(--surface); + border: 1px dashed var(--border); + border-radius: var(--r-lg); +} + +.compare-empty-icon { + color: var(--gray-400); + margin-bottom: 16px; +} + +.compare-empty h2 { + font-size: 1.2rem; + color: var(--text-heading); + margin-bottom: 8px; +} + +.compare-empty p { + color: var(--text-body); + font-size: 0.92rem; + margin-bottom: 24px; +} + +.compare-quick-picks { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; +} + +.compare-quick-btn { + font-size: 0.82rem; + font-weight: 600; + padding: 8px 16px; + border-radius: var(--r-full); + border: 1px solid var(--indigo-100); + background: var(--indigo-50); + color: var(--indigo-600); cursor: pointer; + transition: background 0.2s, transform 0.15s; +} + +.compare-quick-btn:hover { + background: var(--indigo-100); + transform: translateY(-1px); +} + +.compare-results { + display: flex; + flex-direction: column; + gap: 32px; +} + +.compare-summary-grid { + display: grid; + grid-template-columns: 1fr auto 1fr; + gap: 16px; + align-items: stretch; +} + +.compare-summary-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--r-lg); + padding: 24px; + box-shadow: var(--shadow-sm); +} + +.compare-summary-card--a { + border-top: 3px solid var(--indigo-600); +} + +.compare-summary-card--b { + border-top: 3px solid var(--purple-600); +} + +.compare-summary-card--shared { + border-top: 3px solid var(--green-500); + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + min-width: 160px; +} + +.compare-summary-label { + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-light); +} + +.compare-summary-card h2 { + font-size: 1.1rem; + color: var(--text-heading); + margin: 8px 0; +} + +.compare-summary-card p { + font-size: 0.85rem; + color: var(--text-body); + line-height: 1.5; +} + +.compare-shared-stat { + margin-top: 12px; +} + +.compare-stat-num { + display: block; + font-family: var(--font-display); + font-size: 1.8rem; + font-weight: 800; + color: var(--green-500); +} + +.compare-stat-text { + font-size: 0.78rem; + color: var(--text-body); +} + +.compare-section-title { + font-family: var(--font-display); + font-size: 1.2rem; + font-weight: 700; + color: var(--text-heading); + margin-bottom: 16px; +} + +.compare-charts-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 16px; +} + +.compare-chart-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--r-lg); + padding: 20px; +} + +.compare-chart-card h3 { + font-size: 0.88rem; + font-weight: 600; + color: var(--text-body); + margin-bottom: 16px; +} + +.compare-bar-row { + display: grid; + grid-template-columns: 1fr 2fr auto; + gap: 10px; + align-items: center; + margin-bottom: 12px; +} + +.compare-bar-label { + font-size: 0.75rem; + font-weight: 600; + color: var(--text-heading); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.compare-bar-track { + height: 10px; + background: var(--gray-100); + border-radius: var(--r-full); + overflow: hidden; +} + +.compare-bar-fill { + height: 100%; + border-radius: var(--r-full); + transition: width 0.6s ease; +} + +.compare-bar-fill.compare-bar--a { + background: var(--indigo-600); +} + +.compare-bar-fill.compare-bar--b { + background: var(--purple-600); +} + +.compare-bar-value { + font-size: 0.75rem; + font-weight: 700; + color: var(--text-body); + min-width: 48px; + text-align: right; +} + +.compare-details-grid { + display: grid; + grid-template-columns: 1fr 1.2fr 1fr; + gap: 20px; +} + +.compare-detail-col { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--r-lg); + padding: 24px; +} + +.compare-detail-col--a { + border-top: 3px solid var(--indigo-600); +} + +.compare-detail-col--b { + border-top: 3px solid var(--purple-600); +} + +.compare-detail-col--center { + border-top: 3px solid var(--green-500); +} + +.compare-detail-col h3 { font-size: 1rem; - transition: all 0.25s ease; + font-weight: 700; + color: var(--text-heading); + margin-bottom: 12px; } -.theme-toggle:hover { - background: rgba(255,255,255,0.18); - transform: translateY(-2px); +.compare-detail-col h4 { + font-size: 0.82rem; + font-weight: 600; + color: var(--text-body); + margin: 16px 0 8px; } -/* ============================================================ - WORKING DARK MODE +.compare-meta-list { + list-style: none; + padding: 0; + margin: 0 0 8px; +} + +.compare-meta-list li { + display: flex; + justify-content: space-between; + font-size: 0.85rem; + padding: 6px 0; + border-bottom: 1px solid var(--border); +} + +.compare-meta-list span { + color: var(--text-light); +} + +.compare-tag-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.compare-tag { + font-size: 0.78rem; + font-weight: 500; + padding: 4px 10px; + border-radius: var(--r-full); + background: var(--gray-100); + color: var(--text-body); +} + +.compare-tag--a { + background: var(--indigo-100); + color: var(--indigo-700); +} + +.compare-tag--b { + background: var(--purple-100); + color: var(--purple-700); +} + +.compare-tag--shared { + background: var(--green-100); + color: #047857; + font-weight: 600; +} + +.compare-skills-venn { + display: flex; + flex-direction: column; + gap: 16px; +} + +.compare-skills-block h4 { + font-size: 0.78rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-light); + margin-bottom: 8px; +} + +.compare-skill-chips { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.compare-skill-chip { + font-size: 0.78rem; + font-weight: 600; + padding: 5px 12px; + border-radius: var(--r-full); +} + +.compare-skill-chip--a { + background: var(--indigo-100); + color: var(--indigo-700); +} + +.compare-skill-chip--b { + background: var(--purple-100); + color: var(--purple-700); +} + +.compare-skill-chip--shared { + background: var(--green-100); + color: #047857; +} + +.compare-skill-empty { + font-size: 0.82rem; + color: var(--text-light); + font-style: italic; +} + +@media (max-width: 900px) { + .compare-summary-grid { + grid-template-columns: 1fr; + } + + .compare-summary-card--shared { + order: -1; + } + + .compare-details-grid { + grid-template-columns: 1fr; + } + + .compare-selectors { + flex-direction: column; + align-items: stretch; + } + + .compare-vs-badge { + align-self: center; + } + + .compare-btn { + width: 100%; + } +} + +[data-theme="dark"] .compare-error { + background: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.3); +} + +[data-theme="dark"] .compare-tag--shared, +[data-theme="dark"] .compare-skill-chip--shared { + background: rgba(16, 185, 129, 0.15); + color: #6ee7b7; +} diff --git a/templates/compare.html b/templates/compare.html new file mode 100644 index 00000000..8c79dc3e --- /dev/null +++ b/templates/compare.html @@ -0,0 +1,238 @@ + + + + + + + + Compare Roadmaps — DevPath + + {% include 'partials/theme_head.html' %} + + + + + + + + +
+
+ Career Decision Tool +

Compare Learning Roadmaps

+

+ Select two career paths to compare topics, duration, difficulty, overlapping skills, and job opportunities — + side by side. +

+
+
+ +
+ +
+
+ + +
+ + + +
+ + +
+ + +
+ + + + +
+ +

Pick two roadmaps to get started

+

Try comparing Frontend vs Full Stack, React vs Angular, or + DevOps vs Cloud Engineering. +

+
+ + + +
+
+ + + +
+ + + + + + + + diff --git a/templates/index.html b/templates/index.html index 5c149c1f..454b1c36 100644 --- a/templates/index.html +++ b/templates/index.html @@ -41,94 +41,85 @@ - +