diff --git a/static/script.js b/static/script.js index a381478b..1480ca34 100644 --- a/static/script.js +++ b/static/script.js @@ -12,7 +12,7 @@ // Detect which page we are on // ============================================================ // !! trick turns the DOM result into a simple true/false -var isIndexPage = !!document.getElementById("recommend-form"); +var isIndexPage = !!document.getElementById("recommend-form"); // PROJECT_ID is set by the server only on detail pages, so if it's missing we're elsewhere var isDetailPage = typeof PROJECT_ID !== "undefined"; var modal = document.getElementById('github-modal-overlay'); @@ -92,7 +92,7 @@ if (isIndexPage) { "C#", "Ruby", "PHP", "Go", "Swift", "TypeScript", "Angular", "Vue.js", "Spring", "Flutter", "TensorFlow", "PyTorch", "Data Science", "Machine Learning", "Artificial Intelligence", "DevOps", "Cybersecurity", - "Blockchain", "UI/UX Design", "Game Development", "CI/CD", "REST API", "GraphQL", + "Blockchain", "UI/UX Design", "Game Development", "CI/CD", "REST API", "GraphQL", "Rust", "Kotlin" ]; } @@ -498,10 +498,10 @@ if (isIndexPage) { //combine form values into an object to send to server/api var payload = { // Prefer the hidden input value; fall back to raw text box if hidden input is empty - skills: skillsHidden.value.trim() || skillsTextInput.value.trim(), - level: document.getElementById("level").value, + skills: skillsHidden.value.trim() || skillsTextInput.value.trim(), + level: document.getElementById("level").value, interest: document.getElementById("interest").value, - time: document.getElementById("time").value + time: document.getElementById("time").value }; //post the data to backend api as JSON, then handle the response @@ -725,7 +725,12 @@ if (isDetailPage) { return; } if (codePanelFilename) codePanelFilename.textContent = data.filename; - if (codeContentEl) codeContentEl.textContent = data.code; + 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; }) @@ -764,11 +769,11 @@ if (isDetailPage) { // 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"); + var btnLabel = btnCopyCode.querySelector(".copy-btn-label"); - if (copyIcon) copyIcon.style.display = "none"; + if (copyIcon) copyIcon.style.display = "none"; if (checkIcon) checkIcon.style.display = "inline"; - if (btnLabel) btnLabel.textContent = "Copied!"; + 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; @@ -782,9 +787,9 @@ if (isDetailPage) { // Clear any previous timeout first so timers don't stack up clearTimeout(toastTimeout); toastTimeout = setTimeout(function () { - if (copyIcon) copyIcon.style.display = "inline"; + if (copyIcon) copyIcon.style.display = "inline"; if (checkIcon) checkIcon.style.display = "none"; - if (btnLabel) btnLabel.textContent = "Copy Code"; + if (btnLabel) btnLabel.textContent = "Copy Code"; btnCopyCode.classList.remove("copied"); btnCopyCode.disabled = false; if (copyToast) copyToast.classList.remove("show"); @@ -793,7 +798,11 @@ if (isDetailPage) { if (btnCopyCode) { btnCopyCode.addEventListener("click", function () { - var code = codeContentEl ? codeContentEl.textContent : ""; + 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; @@ -895,17 +904,17 @@ var scrollTopBtn = document.getElementById('scroll-top-btn'); /* 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'); - } + if (!scrollTopBtn) return; + if (window.pageYOffset > SCROLL_THRESHOLD) { + scrollTopBtn.classList.add('visible'); + } else { + scrollTopBtn.classList.remove('visible'); + } } /* Smooth-scroll to the very top of the page */ function scrollToTop() { - window.scrollTo({ top: 0, behavior: 'smooth' }); + window.scrollTo({ top: 0, behavior: 'smooth' }); } /* Only wire up listeners if the button exists on this page */ diff --git a/static/style.css b/static/style.css index 0849005d..13cd1c94 100644 --- a/static/style.css +++ b/static/style.css @@ -14,7 +14,7 @@ --indigo-600: #3347e0; --indigo-500: #4f6ef7; --indigo-100: #eef1ff; - --indigo-50: #f5f7ff; + --indigo-50: #f5f7ff; --purple-700: #5b21b6; --purple-600: #7c3aed; @@ -24,32 +24,32 @@ --yellow-300: #fcd34d; --yellow-100: #fef9e7; - --pink-100: #fce7f3; - --pink-500: #ec4899; + --pink-100: #fce7f3; + --pink-500: #ec4899; - --green-500: #10b981; - --green-100: #d1fae5; + --green-500: #10b981; + --green-100: #d1fae5; --orange-500: #f59e0b; - --red-500: #ef4444; + --red-500: #ef4444; /* Neutrals */ - --white: #ffffff; - --gray-50: #f9fafb; - --gray-100: #f3f4f6; - --gray-200: #e5e7eb; - --gray-300: #d1d5db; - --gray-400: #9ca3af; - --gray-500: #6b7280; - --gray-600: #4b5563; - --gray-700: #374151; - --gray-900: #111827; + --white: #ffffff; + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-900: #111827; /* Semantic */ --text-heading: var(--gray-900); - --text-body: var(--gray-600); - --text-light: var(--gray-400); - --border: var(--gray-200); + --text-body: var(--gray-600); + --text-light: var(--gray-400); + --border: var(--gray-200); /* Shadows */ --shadow-xs: 0 1px 2px rgba(15, 21, 96, 0.05); @@ -59,25 +59,35 @@ --shadow-xl: 0 16px 60px rgba(15, 21, 96, 0.22); /* Radii */ - --r-xs: 6px; - --r-sm: 10px; - --r-md: 16px; - --r-lg: 24px; - --r-xl: 32px; + --r-xs: 6px; + --r-sm: 10px; + --r-md: 16px; + --r-lg: 24px; + --r-xl: 32px; --r-full: 9999px; /* Fonts */ --font-display: "Sora", sans-serif; - --font-body: "Inter", sans-serif; - --font-mono: "JetBrains Mono", "Courier New", monospace; + --font-body: "Inter", sans-serif; + --font-mono: "JetBrains Mono", "Courier New", monospace; /* Transition */ --t: 0.2s ease; } /* ---- Reset ------------------------------------------------- */ -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } -html { scroll-behavior: smooth; } +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; +} + body { font-family: var(--font-body); color: var(--gray-700); @@ -85,10 +95,25 @@ body { line-height: 1.65; -webkit-font-smoothing: antialiased; } -img { display: block; max-width: 100%; } -a { color: var(--indigo-500); text-decoration: none; } -a:hover { text-decoration: underline; } -ul, ol { list-style: none; } + +img { + display: block; + max-width: 100%; +} + +a { + color: var(--indigo-500); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +ul, +ol { + list-style: none; +} /* ---- Shared utilities -------------------------------------- */ .container { @@ -173,12 +198,14 @@ ul, ol { list-style: none; } /* ---- Navigation ------------------------------------------- */ .navbar { position: fixed; - top: 0; left: 0; right: 0; + top: 0; + left: 0; + right: 0; z-index: 200; background: rgba(10, 15, 100, 0.88); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); - border-bottom: 1px solid rgba(255,255,255,0.06); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); transition: background var(--t); } @@ -199,16 +226,26 @@ ul, ol { list-style: none; } color: var(--white); letter-spacing: -0.04em; } -.nav-logo:hover { text-decoration: none; } -.nav-logo-accent { color: var(--yellow-400); } -.nav-links { display: flex; align-items: center; gap: 32px; } +.nav-logo:hover { + text-decoration: none; +} + +.nav-logo-accent { + color: var(--yellow-400); +} + +.nav-links { + display: flex; + align-items: center; + gap: 32px; +} .nav-link { position: relative; font-size: 0.88rem; font-weight: 500; - color: rgba(255,255,255,0.7); + color: rgba(255, 255, 255, 0.7); transition: color var(--t); } @@ -223,8 +260,8 @@ ul, ol { list-style: none; } background: linear-gradient(90deg, var(--indigo-400, #60a5fa), var(--yellow-400)); transform: scaleX(0); transform-origin: left; - transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), - opacity 0.35s ease; + transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), + opacity 0.35s ease; opacity: 0; } @@ -241,15 +278,16 @@ ul, ol { list-style: none; } .nav-btn-outline { font-size: 0.86rem; font-weight: 600; - color: rgba(255,255,255,0.85); - border: 1.5px solid rgba(255,255,255,0.28); + 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; transition: background var(--t), border-color var(--t); } + .nav-btn-outline:hover { - background: rgba(255,255,255,0.1); - border-color: rgba(255,255,255,0.45); + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.45); text-decoration: none; } @@ -263,6 +301,7 @@ ul, ol { list-style: none; } cursor: pointer; padding: 4px; } + .nav-mobile-toggle span { display: block; width: 22px; @@ -271,26 +310,38 @@ ul, ol { list-style: none; } border-radius: 2px; transition: transform var(--t), opacity var(--t); } -.nav-mobile-toggle.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); } -.nav-mobile-toggle.open span:nth-child(2) { opacity: 0; } -.nav-mobile-toggle.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); } + +.nav-mobile-toggle.open span:nth-child(1) { + transform: translateY(7px) rotate(45deg); +} + +.nav-mobile-toggle.open span:nth-child(2) { + opacity: 0; +} + +.nav-mobile-toggle.open span:nth-child(3) { + transform: translateY(-7px) rotate(-45deg); +} .nav-mobile-menu { display: none; flex-direction: column; padding: 16px 32px 20px; background: var(--indigo-900); - border-top: 1px solid rgba(255,255,255,0.06); + border-top: 1px solid rgba(255, 255, 255, 0.06); +} + +.nav-mobile-menu.open { + display: flex; } -.nav-mobile-menu.open { display: flex; } .nav-mobile-link { position: relative; font-size: 0.95rem; font-weight: 500; - color: rgba(255,255,255,0.8); + color: rgba(255, 255, 255, 0.8); padding: 10px 0; - border-bottom: 1px solid rgba(255,255,255,0.06); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); transition: color var(--t); } @@ -305,8 +356,8 @@ ul, ol { list-style: none; } background: linear-gradient(90deg, var(--indigo-400, #60a5fa), var(--yellow-400)); transform: scaleX(0); transform-origin: left; - transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), - opacity 0.35s ease; + transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), + opacity 0.35s ease; opacity: 0; } @@ -320,7 +371,9 @@ ul, ol { list-style: none; } opacity: 1; } -.nav-mobile-link:last-child { border-bottom: none; } +.nav-mobile-link:last-child { + border-bottom: none; +} /* ---- Hero Section ----------------------------------------- */ .hero { @@ -337,7 +390,7 @@ ul, ol { list-style: none; } .hero-bg-grid { position: absolute; inset: 0; - background-image: radial-gradient(rgba(255,255,255,0.06) 1px, transparent 1px); + background-image: radial-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px); background-size: 28px 28px; pointer-events: none; } @@ -355,7 +408,10 @@ ul, ol { list-style: none; } } /* Copy column (order keeps layout when decorative blocks follow in DOM) */ -.hero-copy { max-width: 580px; order: 1; } +.hero-copy { + max-width: 580px; + order: 1; +} .hero-badge { display: inline-flex; @@ -365,15 +421,17 @@ ul, ol { list-style: none; } font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; - color: rgba(255,255,255,0.75); - border: 1px solid rgba(255,255,255,0.2); - background: rgba(255,255,255,0.07); + color: rgba(255, 255, 255, 0.75); + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.07); border-radius: var(--r-full); padding: 6px 16px; margin-bottom: 24px; } + .hero-badge-dot { - width: 7px; height: 7px; + width: 7px; + height: 7px; border-radius: 50%; background: var(--green-500); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.25); @@ -388,11 +446,14 @@ ul, ol { list-style: none; } letter-spacing: -0.04em; margin-bottom: 22px; } -.hero-heading-accent { color: var(--yellow-300); } + +.hero-heading-accent { + color: var(--yellow-300); +} .hero-subtext { font-size: 1.08rem; - color: rgba(255,255,255,0.72); + color: rgba(255, 255, 255, 0.72); line-height: 1.78; margin-bottom: 32px; max-width: 500px; @@ -401,7 +462,12 @@ ul, ol { list-style: none; } /* Hero CTA buttons */ -.hero-cta-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; } +.hero-cta-row { + display: flex; + align-items: center; + gap: 14px; + flex-wrap: wrap; +} .btn-hero-primary { display: inline-flex; @@ -417,6 +483,7 @@ ul, ol { list-style: none; } box-shadow: 0 4px 20px rgba(251, 191, 36, 0.4); transition: background var(--t), transform var(--t), box-shadow var(--t); } + .btn-hero-primary:hover { background: var(--yellow-300); transform: translateY(-2px); @@ -429,8 +496,8 @@ ul, ol { list-style: none; } align-items: center; gap: 8px; padding: 14px 28px; - background: rgba(255,255,255,0.1); - border: 1.5px solid rgba(255,255,255,0.25); + background: rgba(255, 255, 255, 0.1); + border: 1.5px solid rgba(255, 255, 255, 0.25); color: var(--white); font-family: var(--font-display); font-size: 0.95rem; @@ -438,7 +505,11 @@ ul, ol { list-style: none; } border-radius: var(--r-full); transition: background var(--t); } -.btn-hero-ghost:hover { background: rgba(255,255,255,0.18); text-decoration: none; } + +.btn-hero-ghost:hover { + background: rgba(255, 255, 255, 0.18); + text-decoration: none; +} /* Visual column (code preview cards) */ .hero-visual { @@ -454,22 +525,49 @@ ul, ol { list-style: none; } display: flex; align-items: center; gap: 14px; - background: rgba(255,255,255,0.95); + background: rgba(255, 255, 255, 0.95); border-radius: var(--r-md); padding: 14px 18px; box-shadow: var(--shadow-lg); } + .hvc-icon { - width: 40px; height: 40px; + width: 40px; + height: 40px; border-radius: var(--r-sm); - display: flex; align-items: center; justify-content: center; + display: flex; + align-items: center; + justify-content: center; flex-shrink: 0; } -.hvc-icon--blue { background: var(--indigo-100); color: var(--indigo-600); } -.hvc-icon--green { background: var(--green-100); color: var(--green-500); } -.hvc-text { display: flex; flex-direction: column; gap: 2px; } -.hvc-title { font-family: var(--font-display); font-size: 0.88rem; font-weight: 700; color: var(--gray-900); } -.hvc-sub { font-size: 0.77rem; color: var(--gray-500); } + +.hvc-icon--blue { + background: var(--indigo-100); + color: var(--indigo-600); +} + +.hvc-icon--green { + background: var(--green-100); + color: var(--green-500); +} + +.hvc-text { + display: flex; + flex-direction: column; + gap: 2px; +} + +.hvc-title { + font-family: var(--font-display); + font-size: 0.88rem; + font-weight: 700; + color: var(--gray-900); +} + +.hvc-sub { + font-size: 0.77rem; + color: var(--gray-500); +} /* Code preview card */ .hero-visual-main { @@ -478,19 +576,41 @@ ul, ol { list-style: none; } overflow: hidden; box-shadow: var(--shadow-xl); } + .hvm-header { display: flex; align-items: center; gap: 6px; padding: 10px 16px; background: #161b22; - border-bottom: 1px solid rgba(255,255,255,0.05); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.hvm-dot { + width: 11px; + height: 11px; + border-radius: 50%; + flex-shrink: 0; +} + +.hvm-dot--red { + background: #ff5f56; +} + +.hvm-dot--yellow { + background: #ffbd2e; +} + +.hvm-dot--green { + background: #27c93f; +} + +.hvm-filename { + font-family: var(--font-mono); + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.35); + margin-left: 6px; } -.hvm-dot { width: 11px; height: 11px; border-radius: 50%; flex-shrink: 0; } -.hvm-dot--red { background: #ff5f56; } -.hvm-dot--yellow { background: #ffbd2e; } -.hvm-dot--green { background: #27c93f; } -.hvm-filename { font-family: var(--font-mono); font-size: 0.75rem; color: rgba(255,255,255,0.35); margin-left: 6px; } .hvm-code { padding: 18px 20px; @@ -501,13 +621,35 @@ ul, ol { list-style: none; } font-size: 0.8rem; line-height: 1.85; } -.hvm-line { display: block; color: #e6edf3; } -.hvm-indent { padding-left: 20px; } -.hvm-indent2 { padding-left: 40px; } -.hvm-kw { color: #ff7b72; } -.hvm-fn { color: #d2a8ff; } -.hvm-cm { color: #8b949e; } -.hvm-op { color: #79c0ff; } + +.hvm-line { + display: block; + color: #e6edf3; +} + +.hvm-indent { + padding-left: 20px; +} + +.hvm-indent2 { + padding-left: 40px; +} + +.hvm-kw { + color: #ff7b72; +} + +.hvm-fn { + color: #d2a8ff; +} + +.hvm-cm { + color: #8b949e; +} + +.hvm-op { + color: #79c0ff; +} /* Hero decorative blobs */ .hero-blob { @@ -517,21 +659,28 @@ ul, ol { list-style: none; } filter: blur(90px); opacity: 0.25; } + .hero-blob-1 { - width: 560px; height: 560px; + width: 560px; + height: 560px; background: var(--purple-600); - top: -180px; right: -180px; + top: -180px; + right: -180px; } + .hero-blob-2 { - width: 360px; height: 360px; + width: 360px; + height: 360px; background: var(--indigo-500); - bottom: -80px; left: -100px; + bottom: -80px; + left: -100px; } /* ---- Stats Section (Dynamic) ------------------------------ */ .stats-section { padding: 40px 0; - margin-top: -60px; /* Overlap slightly with hero */ + margin-top: -60px; + /* Overlap slightly with hero */ position: relative; z-index: 10; } @@ -569,11 +718,25 @@ ul, ol { list-style: none; } flex-shrink: 0; } -.stat-icon--indigo { background: var(--indigo-100); color: var(--indigo-600); } -.stat-icon--green { background: var(--green-100); color: var(--green-500); } -.stat-icon--yellow { background: var(--yellow-100); color: var(--orange-500); } +.stat-icon--indigo { + background: var(--indigo-100); + color: var(--indigo-600); +} + +.stat-icon--green { + background: var(--green-100); + color: var(--green-500); +} + +.stat-icon--yellow { + background: var(--yellow-100); + color: var(--orange-500); +} -.stat-info { display: flex; flex-direction: column; } +.stat-info { + display: flex; + flex-direction: column; +} .stat-value { font-family: var(--font-display); @@ -591,24 +754,34 @@ ul, ol { list-style: none; } } @media (max-width: 992px) { - .stats-grid { - grid-template-columns: repeat(2, 1fr); + .stats-grid { + grid-template-columns: repeat(2, 1fr); gap: 20px; } - .stats-section { margin-top: -40px; } - .stat-card { padding: 24px; } + + .stats-section { + margin-top: -40px; + } + + .stat-card { + padding: 24px; + } } @media (max-width: 768px) { - .stats-grid { - grid-template-columns: 1fr; + .stats-grid { + grid-template-columns: 1fr; gap: 16px; } - .stats-section { - margin-top: 0; - padding: 48px 0 24px; + + .stats-section { + margin-top: 0; + padding: 48px 0 24px; + } + + .stat-card { + padding: 20px; } - .stat-card { padding: 20px; } } @media (max-width: 480px) { @@ -617,10 +790,12 @@ ul, ol { list-style: none; } text-align: center; gap: 12px; } + .stat-icon { width: 48px; height: 48px; } + .stat-value { font-size: 1.5rem; } @@ -638,28 +813,7 @@ ul, ol { list-style: none; } border-bottom: 1px solid rgba(35, 53, 194, 0.08); padding: 18px 32px; } -.skill-strip::before, -.skill-strip::after { - content: ""; - position: absolute; - inset: auto; - width: 180px; - height: 180px; - border-radius: 50%; - pointer-events: none; - filter: blur(24px); - opacity: 0.18; -} -.skill-strip::before { - top: -90px; - left: -40px; - background: rgba(79, 110, 247, 0.4); -} -.skill-strip::after { - bottom: -110px; - right: -30px; - background: rgba(236, 72, 153, 0.28); -} + .skill-strip-inner { max-width: 1140px; margin: 0 auto; @@ -674,6 +828,7 @@ ul, ol { list-style: none; } backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); } + .skill-strip-label { flex-shrink: 0; font-size: 0.74rem; @@ -683,24 +838,7 @@ ul, ol { list-style: none; } text-transform: uppercase; white-space: nowrap; } -.skill-strip-marquee { - flex: 1; - min-width: 0; - overflow: hidden; - mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); - -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); -} -.skill-strip-marquee:hover .skill-strip-track { - animation-play-state: paused; -} -.skill-strip-track { - display: flex; - align-items: center; - width: max-content; - gap: 18px; - animation: skill-strip-marquee 30s linear infinite; - will-change: transform; -} + .skill-strip-items { display: flex; align-items: center; @@ -708,6 +846,7 @@ ul, ol { list-style: none; } gap: 0; white-space: nowrap; } + .ss-item { display: inline-flex; align-items: center; @@ -734,6 +873,7 @@ ul, ol { list-style: none; } border-color: rgba(79, 110, 247, 0.28); box-shadow: 0 8px 20px rgba(15, 21, 96, 0.08); } + .ss-sep { width: 6px; height: 6px; @@ -830,7 +970,11 @@ ul, ol { list-style: none; } box-shadow: var(--shadow-sm); transition: box-shadow var(--t), transform var(--t); } -.step-card:hover { box-shadow: var(--shadow-md); transform: translateY(-3px); } + +.step-card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-3px); +} .step-num { font-family: var(--font-display); @@ -852,7 +996,12 @@ ul, ol { list-style: none; } color: var(--text-heading); margin-bottom: 10px; } -.step-card p { font-size: 0.9rem; color: var(--text-body); line-height: 1.65; } + +.step-card p { + font-size: 0.9rem; + color: var(--text-body); + line-height: 1.65; +} .step-connector { color: var(--gray-300); @@ -884,23 +1033,47 @@ ul, ol { list-style: none; } overflow: hidden; transition: transform var(--t), box-shadow var(--t); } -.feature-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); } -.feature-card--pink { background: var(--pink-100); } -.feature-card--yellow { background: var(--yellow-100); } -.feature-card--purple { background: var(--purple-100); } +.feature-card:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-md); +} + +.feature-card--pink { + background: var(--pink-100); +} + +.feature-card--yellow { + background: var(--yellow-100); +} + +.feature-card--purple { + background: var(--purple-100); +} .feature-card-icon { - width: 52px; height: 52px; - background: rgba(255,255,255,0.7); + width: 52px; + height: 52px; + background: rgba(255, 255, 255, 0.7); border-radius: var(--r-sm); - display: flex; align-items: center; justify-content: center; + display: flex; + align-items: center; + justify-content: center; margin-bottom: 22px; box-shadow: var(--shadow-xs); } -.feature-card--pink .feature-card-icon { color: var(--pink-500); } -.feature-card--yellow .feature-card-icon { color: var(--orange-500); } -.feature-card--purple .feature-card-icon { color: var(--purple-600); } + +.feature-card--pink .feature-card-icon { + color: var(--pink-500); +} + +.feature-card--yellow .feature-card-icon { + color: var(--orange-500); +} + +.feature-card--purple .feature-card-icon { + color: var(--purple-600); +} .feature-card h3 { font-family: var(--font-display); @@ -909,6 +1082,7 @@ ul, ol { list-style: none; } color: var(--text-heading); margin-bottom: 12px; } + .feature-card p { font-size: 0.91rem; color: var(--gray-600); @@ -924,11 +1098,12 @@ ul, ol { list-style: none; } font-weight: 700; color: var(--indigo-700); border: 1.5px solid var(--indigo-200, #c7d2fe); - background: rgba(255,255,255,0.6); + background: rgba(255, 255, 255, 0.6); padding: 7px 16px; border-radius: var(--r-full); transition: background var(--t), border-color var(--t); } + .feature-card-link:hover { background: var(--white); border-color: var(--indigo-500); @@ -957,7 +1132,9 @@ ul, ol { list-style: none; } } /* Form groups */ -.form-group { margin-bottom: 32px; } +.form-group { + margin-bottom: 24px; +} .form-row { display: grid; @@ -990,6 +1167,7 @@ label { cursor: text; transition: border-color var(--t), box-shadow var(--t); } + .skill-input-wrap:focus-within { border-color: var(--indigo-500); box-shadow: 0 0 0 3px rgba(79, 110, 247, 0.12); @@ -1016,6 +1194,7 @@ label { border-radius: var(--r-full); padding: 3px 10px 3px 12px; } + .skill-chip-remove { background: none; border: none; @@ -1028,7 +1207,10 @@ label { align-items: center; transition: color var(--t); } -.skill-chip-remove:hover { color: var(--red-500); } + +.skill-chip-remove:hover { + color: var(--red-500); +} /* Text input inside the skill wrap */ .skill-input-wrap input[type="text"] { @@ -1042,6 +1224,7 @@ label { font-size: 0.93rem; color: var(--gray-900); } + .skill-input-wrap input[type="text"]:focus { outline: none; box-shadow: none; @@ -1123,27 +1306,35 @@ label { cursor: pointer; transition: background var(--t), border-color var(--t), color var(--t); } -.skill-chip:hover, .skill-chip.active { + +.skill-chip:hover, +.skill-chip.active { background: var(--indigo-700); border-color: var(--indigo-700); color: var(--white); } /* Select wrapper with custom chevron */ -.select-wrap { position: relative; } +.select-wrap { + position: relative; +} + .select-wrap::after { content: ""; position: absolute; - right: 14px; top: 50%; + right: 14px; + top: 50%; transform: translateY(-50%); - width: 0; height: 0; + width: 0; + height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 6px solid var(--gray-400); pointer-events: none; } -select, input[type="text"]:not(.skill-input-wrap input) { +select, +input[type="text"]:not(.skill-input-wrap input) { width: 100%; padding: 11px 14px; border: 1.5px solid var(--border); @@ -1156,6 +1347,7 @@ select, input[type="text"]:not(.skill-input-wrap input) { -webkit-appearance: none; transition: border-color var(--t), box-shadow var(--t); } + select:focus { outline: none; border-color: var(--indigo-500); @@ -1170,6 +1362,7 @@ select:focus { color: var(--gray-400); margin-top: 5px; } + .form-error-msg { font-size: 0.81rem; color: var(--red-500); @@ -1177,6 +1370,7 @@ select:focus { min-height: 18px; font-weight: 500; } + .form-error-general { font-size: 0.84rem; color: var(--red-500); @@ -1202,12 +1396,19 @@ select:focus { transition: opacity var(--t), transform var(--t), box-shadow var(--t); margin-top: 8px; } + .btn-submit:hover { opacity: 0.93; transform: translateY(-1px); box-shadow: 0 6px 28px rgba(35, 53, 194, 0.42); } -.btn-submit:disabled { opacity: 0.55; cursor: not-allowed; transform: none; box-shadow: none; } + +.btn-submit:disabled { + opacity: 0.55; + cursor: not-allowed; + transform: none; + box-shadow: none; +} /* ---- Results Section -------------------------------------- */ .results-section { @@ -1220,30 +1421,55 @@ select:focus { padding: 56px 0; color: var(--text-body); } + .loading-dots { display: flex; justify-content: center; gap: 8px; margin-bottom: 18px; } + .loading-dots span { - width: 11px; height: 11px; + width: 11px; + height: 11px; background: var(--indigo-500); border-radius: 50%; animation: dotBounce 1.2s ease-in-out infinite; } -.loading-dots span:nth-child(2) { animation-delay: 0.2s; } -.loading-dots span:nth-child(3) { animation-delay: 0.4s; } + +.loading-dots span:nth-child(2) { + animation-delay: 0.2s; +} + +.loading-dots span:nth-child(3) { + animation-delay: 0.4s; +} + @keyframes dotBounce { - 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } - 40% { transform: scale(1.1); opacity: 1; } + + 0%, + 80%, + 100% { + transform: scale(0.6); + opacity: 0.4; + } + + 40% { + transform: scale(1.1); + opacity: 1; + } } /* Empty state */ .empty-state { padding: 64px 0 32px; } -.empty-icon { color: var(--gray-300); margin-bottom: 20px; } + +.empty-icon { + color: var(--gray-300); + margin-bottom: 20px; +} + .empty-state h3 { font-family: var(--font-display); font-size: 1.3rem; @@ -1251,7 +1477,12 @@ select:focus { color: var(--text-heading); margin-bottom: 10px; } -.empty-state p { color: var(--text-body); margin-bottom: 24px; } + +.empty-state p { + color: var(--text-body); + margin-bottom: 24px; +} + .btn-try-again { padding: 11px 24px; background: var(--indigo-100); @@ -1264,7 +1495,10 @@ select:focus { cursor: pointer; transition: background var(--t); } -.btn-try-again:hover { background: var(--indigo-100); } + +.btn-try-again:hover { + background: var(--indigo-100); +} /* Results grid */ .results-grid { @@ -1287,6 +1521,7 @@ select:focus { gap: 16px; transition: box-shadow var(--t), transform var(--t), border-color var(--t); } + .project-card:hover { box-shadow: var(--shadow-lg); transform: translateY(-5px); @@ -1299,6 +1534,7 @@ select:focus { font-weight: 700; color: var(--text-heading); } + .project-card-desc { font-size: 0.88rem; color: var(--text-body); @@ -1311,26 +1547,48 @@ select:focus { flex-wrap: wrap; gap: 7px; } + .project-tag { font-size: 0.74rem; font-weight: 600; padding: 3px 11px; border-radius: var(--r-full); } -.project-tag--skill { background: var(--indigo-100); color: var(--indigo-700); } -.project-tag--time { background: var(--gray-100); color: var(--gray-600); } -.project-tag--beginner { background: var(--green-100); color: #065f46; } -.project-tag--intermediate { background: var(--yellow-100); color: #78350f; } -.project-tag--advanced { background: #fee2e2; color: #7f1d1d; } -.project-card-footer { - padding-top: 8px; - border-top: 1px solid var(--gray-100); +.project-tag--skill { + background: var(--indigo-100); + color: var(--indigo-700); } -.btn-details { - display: block; - width: 100%; - padding: 11px 18px; + +.project-tag--time { + background: var(--gray-100); + color: var(--gray-600); +} + +.project-tag--beginner { + background: var(--green-100); + color: #065f46; +} + +.project-tag--intermediate { + background: var(--yellow-100); + color: #78350f; +} + +.project-tag--advanced { + background: #fee2e2; + color: #7f1d1d; +} + +.project-card-footer { + padding-top: 8px; + border-top: 1px solid var(--gray-100); +} + +.btn-details { + display: block; + width: 100%; + padding: 11px 18px; background: transparent; border: 1.5px solid var(--indigo-500); color: var(--indigo-600); @@ -1343,6 +1601,7 @@ select:focus { transition: background var(--t), color var(--t); text-decoration: none; } + .btn-details:hover { background: var(--indigo-600); color: var(--white); @@ -1355,6 +1614,7 @@ select:focus { background: linear-gradient(135deg, var(--indigo-800) 0%, var(--purple-700) 100%); text-align: center; } + .cta-inner h2 { font-family: var(--font-display); font-size: clamp(2rem, 4vw, 3rem); @@ -1364,12 +1624,17 @@ select:focus { line-height: 1.2; margin-bottom: 14px; } -.cta-accent { color: var(--yellow-300); } + +.cta-accent { + color: var(--yellow-300); +} + .cta-inner p { font-size: 1.05rem; - color: rgba(255,255,255,0.72); + color: rgba(255, 255, 255, 0.72); margin-bottom: 36px; } + .btn-cta { display: inline-flex; align-items: center; @@ -1384,6 +1649,7 @@ select:focus { box-shadow: 0 6px 24px rgba(251, 191, 36, 0.45); transition: background var(--t), transform var(--t); } + .btn-cta:hover { background: var(--yellow-300); transform: translateY(-2px); @@ -1395,6 +1661,7 @@ select:focus { background: var(--indigo-900); padding: 64px 32px 0; } + .footer-inner { max-width: 1140px; margin: 0 auto; @@ -1402,8 +1669,9 @@ select:focus { grid-template-columns: 2fr 1fr 1fr 1fr; gap: 40px; padding-bottom: 56px; - border-bottom: 1px solid rgba(255,255,255,0.08); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); } + .footer-logo { font-family: var(--font-display); font-size: 1.3rem; @@ -1413,24 +1681,43 @@ select:focus { display: block; margin-bottom: 14px; } -.footer-logo-accent { color: var(--yellow-400); } -.footer-tagline { font-size: 0.86rem; color: rgba(255,255,255,0.45); line-height: 1.7; } + +.footer-logo-accent { + color: var(--yellow-400); +} + +.footer-tagline { + font-size: 0.86rem; + color: rgba(255, 255, 255, 0.45); + line-height: 1.7; +} + .footer-col-title { font-family: var(--font-display); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; - color: rgba(255,255,255,0.45); + color: rgba(255, 255, 255, 0.45); margin-bottom: 18px; } -.footer-links-list { display: flex; flex-direction: column; gap: 10px; } + +.footer-links-list { + display: flex; + flex-direction: column; + gap: 10px; +} + .footer-links-list a { font-size: 0.88rem; - color: rgba(255,255,255,0.6); + color: rgba(255, 255, 255, 0.6); transition: color var(--t); } -.footer-links-list a:hover { color: var(--white); text-decoration: none; } + +.footer-links-list a:hover { + color: var(--white); + text-decoration: none; +} .footer-bottom { max-width: 1140px; @@ -1441,13 +1728,32 @@ select:focus { justify-content: space-between; gap: 16px; } -.footer-bottom p { font-size: 0.82rem; color: rgba(255,255,255,0.35); } -.footer-bottom-links { display: flex; gap: 20px; } -.footer-bottom-links a { font-size: 0.82rem; color: rgba(255,255,255,0.35); transition: color var(--t); } -.footer-bottom-links a:hover { color: rgba(255,255,255,0.7); text-decoration: none; } + +.footer-bottom p { + font-size: 0.82rem; + color: rgba(255, 255, 255, 0.35); +} + +.footer-bottom-links { + display: flex; + gap: 20px; +} + +.footer-bottom-links a { + font-size: 0.82rem; + color: rgba(255, 255, 255, 0.35); + transition: color var(--t); +} + +.footer-bottom-links a:hover { + color: rgba(255, 255, 255, 0.7); + text-decoration: none; +} /* ---- Detail Page ------------------------------------------ */ -.detail-page { background: var(--gray-50); } +.detail-page { + background: var(--gray-50); +} .detail-hero { background: linear-gradient(135deg, var(--indigo-900) 0%, var(--indigo-800) 50%, var(--purple-700) 100%); @@ -1455,18 +1761,19 @@ select:focus { position: relative; overflow: hidden; } + .detail-hero::after { content: ""; position: absolute; inset: 0; - background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px); + background-image: radial-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px); background-size: 28px 28px; pointer-events: none; } .breadcrumb { font-size: 0.82rem; - color: rgba(255,255,255,0.5); + color: rgba(255, 255, 255, 0.5); display: flex; align-items: center; gap: 6px; @@ -1474,9 +1781,19 @@ select:focus { position: relative; z-index: 2; } -.breadcrumb a { color: rgba(255,255,255,0.5); } -.breadcrumb a:hover { color: var(--white); } -.breadcrumb-sep { opacity: 0.4; font-size: 1rem; } + +.breadcrumb a { + color: rgba(255, 255, 255, 0.5); +} + +.breadcrumb a:hover { + color: var(--white); +} + +.breadcrumb-sep { + opacity: 0.4; + font-size: 1rem; +} .detail-hero-content { display: flex; @@ -1486,9 +1803,19 @@ select:focus { position: relative; z-index: 2; } -.detail-hero-left { flex: 1; max-width: 680px; } -.detail-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; } +.detail-hero-left { + flex: 1; + max-width: 680px; +} + +.detail-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 18px; +} + .detail-tag { font-size: 0.74rem; font-weight: 700; @@ -1497,9 +1824,21 @@ select:focus { padding: 4px 13px; border-radius: var(--r-full); } -.detail-tag--level { background: rgba(255,255,255,0.12); color: rgba(255,255,255,0.85); } -.detail-tag--interest { background: rgba(6, 182, 212, 0.18); color: #a7f3f9; } -.detail-tag--time { background: rgba(251,191,36,0.18); color: var(--yellow-300); } + +.detail-tag--level { + background: rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.85); +} + +.detail-tag--interest { + background: rgba(6, 182, 212, 0.18); + color: #a7f3f9; +} + +.detail-tag--time { + background: rgba(251, 191, 36, 0.18); + color: var(--yellow-300); +} .detail-title { font-family: var(--font-display); @@ -1510,9 +1849,10 @@ select:focus { line-height: 1.15; margin-bottom: 16px; } + .detail-description { font-size: 1.02rem; - color: rgba(255,255,255,0.72); + color: rgba(255, 255, 255, 0.72); line-height: 1.78; max-width: 600px; } @@ -1524,6 +1864,7 @@ select:focus { flex-shrink: 0; padding-top: 52px; } + .btn-download { display: inline-flex; align-items: center; @@ -1536,18 +1877,23 @@ select:focus { font-weight: 700; border-radius: var(--r-xs); white-space: nowrap; - box-shadow: 0 4px 16px rgba(251,191,36,0.35); + box-shadow: 0 4px 16px rgba(251, 191, 36, 0.35); transition: background var(--t), transform var(--t); } -.btn-download:hover { background: var(--yellow-300); transform: translateY(-1px); text-decoration: none; } + +.btn-download:hover { + background: var(--yellow-300); + transform: translateY(-1px); + text-decoration: none; +} .btn-view-code { display: inline-flex; align-items: center; gap: 8px; padding: 13px 22px; - background: rgba(255,255,255,0.08); - border: 1.5px solid rgba(255,255,255,0.28); + background: rgba(255, 255, 255, 0.08); + border: 1.5px solid rgba(255, 255, 255, 0.28); color: var(--white); font-family: var(--font-display); font-size: 0.9rem; @@ -1557,10 +1903,16 @@ select:focus { white-space: nowrap; transition: background var(--t); } -.btn-view-code:hover { background: rgba(255,255,255,0.16); } + +.btn-view-code:hover { + background: rgba(255, 255, 255, 0.16); +} /* Detail body */ -.detail-body { padding: 56px 0 96px; } +.detail-body { + padding: 56px 0 96px; +} + .detail-grid { display: grid; grid-template-columns: 1fr 310px; @@ -1576,26 +1928,33 @@ select:focus { box-shadow: var(--shadow-sm); margin-bottom: 24px; } + .detail-section-header { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; } + .detail-section-icon { - width: 36px; height: 36px; + width: 36px; + height: 36px; background: var(--indigo-100); border-radius: var(--r-xs); - display: flex; align-items: center; justify-content: center; + display: flex; + align-items: center; + justify-content: center; color: var(--indigo-600); flex-shrink: 0; } + .detail-section-header h2 { font-family: var(--font-display); font-size: 1.1rem; font-weight: 700; color: var(--text-heading); } + .detail-section-sub { font-size: 0.87rem; color: var(--text-body); @@ -1604,7 +1963,12 @@ select:focus { } /* Features list */ -.feature-list { display: flex; flex-direction: column; gap: 12px; } +.feature-list { + display: flex; + flex-direction: column; + gap: 12px; +} + .feature-list-item { display: flex; align-items: flex-start; @@ -1613,19 +1977,31 @@ select:focus { color: var(--text-body); line-height: 1.65; } + .feature-check { - width: 22px; height: 22px; + width: 22px; + height: 22px; background: var(--green-100); border-radius: 50%; - display: flex; align-items: center; justify-content: center; + display: flex; + align-items: center; + justify-content: center; color: var(--green-500); flex-shrink: 0; margin-top: 2px; } /* Roadmap timeline */ -.roadmap-timeline { display: flex; flex-direction: column; } -.roadmap-step { display: flex; gap: 0; } +.roadmap-timeline { + display: flex; + flex-direction: column; +} + +.roadmap-step { + display: flex; + gap: 0; +} + .roadmap-marker { display: flex; flex-direction: column; @@ -1633,6 +2009,7 @@ select:focus { flex-shrink: 0; width: 36px; } + .roadmap-dot { width: 16px; height: 16px; border-radius: 50%; @@ -1652,8 +2029,16 @@ select:focus { border-left:2px dashed var(--border); margin: 4px 0; } -.roadmap-step:last-child .roadmap-line { display: none; } -.roadmap-content { padding: 0 0 28px 16px; flex: 1; } + +.roadmap-step:last-child .roadmap-line { + display: none; +} + +.roadmap-content { + padding: 0 0 28px 16px; + flex: 1; +} + .roadmap-step-num { display: inline-block; font-size: 0.72rem; @@ -1665,11 +2050,24 @@ select:focus { letter-spacing: 0.04em; margin-bottom: 6px; } -.roadmap-step-text { font-size: 0.92rem; color: var(--text-body); line-height: 1.65; } + +.roadmap-step-text { + font-size: 0.92rem; + color: var(--text-body); + line-height: 1.65; +} /* Resources list */ -.resource-list { display: flex; flex-direction: column; gap: 10px; } -.resource-item { display: flex; } +.resource-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.resource-item { + display: flex; +} + .resource-link { display: inline-flex; align-items: center; @@ -1684,12 +2082,32 @@ select:focus { width: 100%; transition: background var(--t), border-color var(--t); } -.resource-link:hover { background: var(--indigo-100); border-color: rgba(79, 110, 247, 0.3); text-decoration: none; } -.resource-link span { flex: 1; } -.resource-plain { font-size: 0.9rem; color: var(--text-body); padding: 8px 0; } + +.resource-link:hover { + background: var(--indigo-100); + border-color: rgba(79, 110, 247, 0.3); + text-decoration: none; +} + +.resource-link span { + flex: 1; +} + +.resource-plain { + font-size: 0.9rem; + color: var(--text-body); + padding: 8px 0; +} /* Sidebar */ -.detail-sidebar { display: flex; flex-direction: column; gap: 20px; position: sticky; top: 84px; } +.detail-sidebar { + display: flex; + flex-direction: column; + gap: 20px; + position: sticky; + top: 84px; +} + .sidebar-card { background: var(--white); border: 1px solid var(--border); @@ -1697,12 +2115,16 @@ select:focus { padding: 24px; box-shadow: var(--shadow-sm); } + .sidebar-card--code { background: linear-gradient(135deg, var(--indigo-50) 0%, var(--purple-100) 100%); border-color: rgba(79, 110, 247, 0.18); } + .sidebar-card-title { - display: flex; align-items: center; gap: 8px; + display: flex; + align-items: center; + gap: 8px; font-family: var(--font-display); font-size: 0.82rem; font-weight: 700; @@ -1711,7 +2133,13 @@ select:focus { text-transform: uppercase; letter-spacing: 0.07em; } -.tech-tags { display: flex; flex-wrap: wrap; gap: 8px; } + +.tech-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + .tech-tag { font-size: 0.8rem; font-weight: 500; @@ -1721,15 +2149,54 @@ select:focus { padding: 4px 12px; border-radius: var(--r-full); } -.stats-list { display: flex; flex-direction: column; gap: 12px; } -.stats-list li { display: flex; align-items: center; justify-content: space-between; font-size: 0.88rem; } -.stats-label { color: var(--text-body); } -.stats-value { font-weight: 600; color: var(--text-heading); font-size: 0.85rem; } -.stats-value--beginner { color: #065f46; } -.stats-value--intermediate { color: #78350f; } -.stats-value--advanced { color: #7f1d1d; } -.sidebar-card-desc { font-size: 0.85rem; color: var(--text-body); line-height: 1.65; margin-bottom: 16px; } -.sidebar-code-actions { display: flex; gap: 10px; } + +.stats-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.stats-list li { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.88rem; +} + +.stats-label { + color: var(--text-body); +} + +.stats-value { + font-weight: 600; + color: var(--text-heading); + font-size: 0.85rem; +} + +.stats-value--beginner { + color: #065f46; +} + +.stats-value--intermediate { + color: #78350f; +} + +.stats-value--advanced { + color: #7f1d1d; +} + +.sidebar-card-desc { + font-size: 0.85rem; + color: var(--text-body); + line-height: 1.65; + margin-bottom: 16px; +} + +.sidebar-code-actions { + display: flex; + gap: 10px; +} + .btn-view-code-sm { flex: 1; padding: 9px 14px; @@ -1744,7 +2211,12 @@ select:focus { text-align: center; transition: background var(--t), color var(--t); } -.btn-view-code-sm:hover { background: var(--indigo-600); color: var(--white); } + +.btn-view-code-sm:hover { + background: var(--indigo-600); + color: var(--white); +} + .btn-download-sm { flex: 1; padding: 9px 14px; @@ -1758,65 +2230,99 @@ select:focus { display: block; transition: background var(--t); } -.btn-download-sm:hover { background: var(--yellow-300); text-decoration: none; } + +.btn-download-sm:hover { + background: var(--yellow-300); + text-decoration: none; +} /* ---- Code Panel (slide-up) -------------------------------- */ .code-panel-overlay { display: none; - position: fixed; inset: 0; + position: fixed; + inset: 0; background: rgba(10, 15, 80, 0.6); z-index: 300; } -.code-panel-overlay.active { display: block; } + +.code-panel-overlay.active { + display: block; +} + .code-panel { position: fixed; - bottom: 0; left: 0; right: 0; + bottom: 0; + left: 0; + right: 0; height: 72vh; background: #0d1117; border-radius: var(--r-lg) var(--r-lg) 0 0; - box-shadow: 0 -8px 48px rgba(0,0,0,0.45); + box-shadow: 0 -8px 48px rgba(0, 0, 0, 0.45); z-index: 301; display: flex; flex-direction: column; transform: translateY(100%); transition: transform 0.3s ease; } -.code-panel.active { transform: translateY(0); } + +.code-panel.active { + transform: translateY(0); +} + .code-panel-header { - display: flex; align-items: center; justify-content: space-between; + display: flex; + align-items: center; + justify-content: space-between; padding: 14px 22px; - border-bottom: 1px solid rgba(255,255,255,0.06); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); flex-shrink: 0; } + .code-panel-title { - display: flex; align-items: center; gap: 8px; + display: flex; + align-items: center; + gap: 8px; font-family: var(--font-mono); font-size: 0.84rem; - color: rgba(255,255,255,0.6); + color: rgba(255, 255, 255, 0.6); } -.code-panel-actions { display: flex; align-items: center; gap: 10px; } + +.code-panel-actions { + display: flex; + align-items: center; + gap: 10px; +} + .code-panel-download { font-size: 0.82rem; font-weight: 600; color: var(--yellow-400); padding: 5px 14px; - border: 1px solid rgba(251,191,36,0.3); + border: 1px solid rgba(251, 191, 36, 0.3); border-radius: var(--r-full); transition: background var(--t); } -.code-panel-download:hover { background: rgba(251,191,36,0.1); text-decoration: none; } + +.code-panel-download:hover { + background: rgba(251, 191, 36, 0.1); + text-decoration: none; +} + .code-panel-close { - background: rgba(255,255,255,0.07); + background: rgba(255, 255, 255, 0.07); border: none; border-radius: var(--r-xs); padding: 6px 16px; font-size: 0.84rem; font-weight: 600; - color: rgba(255,255,255,0.6); + color: rgba(255, 255, 255, 0.6); cursor: pointer; transition: background var(--t); } -.code-panel-close:hover { background: rgba(255,255,255,0.13); } + +.code-panel-close:hover { + background: rgba(255, 255, 255, 0.13); +} /* Copy Code button */ .btn-copy-code { @@ -1833,12 +2339,17 @@ select:focus { cursor: pointer; transition: background var(--t), border-color var(--t), color var(--t), transform 0.1s ease; } + .btn-copy-code:hover { background: rgba(79, 110, 247, 0.28); border-color: var(--indigo-500); color: #a5b4fc; } -.btn-copy-code:active { transform: scale(0.97); } + +.btn-copy-code:active { + transform: scale(0.97); +} + .btn-copy-code.copied { background: rgba(16, 185, 129, 0.18); border-color: rgba(16, 185, 129, 0.4); @@ -1866,6 +2377,7 @@ select:focus { pointer-events: none; transition: opacity 0.25s ease, transform 0.25s ease; } + .copy-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); @@ -1873,23 +2385,28 @@ select:focus { .code-viewer { flex: 1; overflow: auto; - padding: 24px 28px; + padding: 16px 0; font-family: var(--font-mono); font-size: 0.83rem; line-height: 1.75; color: #e6edf3; - white-space: pre; background: transparent; } /* ---- Error Pages ------------------------------------------ */ .error-page { min-height: 100vh; - display: flex; align-items: center; justify-content: center; + display: flex; + align-items: center; + justify-content: center; text-align: center; padding: 40px 24px; } -.error-page-inner { max-width: 440px; } + +.error-page-inner { + max-width: 440px; +} + .error-code { font-family: var(--font-display); font-size: 7rem; @@ -1898,6 +2415,7 @@ select:focus { line-height: 1; margin-bottom: 16px; } + .error-page-inner h1 { font-family: var(--font-display); font-size: 1.5rem; @@ -1905,7 +2423,11 @@ select:focus { color: var(--text-heading); margin-bottom: 12px; } -.error-page-inner p { color: var(--text-body); margin-bottom: 32px; } + +.error-page-inner p { + color: var(--text-body); + margin-bottom: 32px; +} /* Primary button (used in error pages) */ .btn-primary { @@ -1922,50 +2444,124 @@ select:focus { box-shadow: 0 4px 18px rgba(35, 53, 194, 0.3); transition: opacity var(--t), transform var(--t); } -.btn-primary:hover { opacity: 0.92; transform: translateY(-1px); text-decoration: none; } + +.btn-primary:hover { + opacity: 0.92; + transform: translateY(-1px); + text-decoration: none; +} /* ---- Responsive ------------------------------------------- */ @media (max-width: 1024px) { - .hero-inner { grid-template-columns: 1fr; gap: 48px; } - .hero-visual { max-width: 480px; margin: 0 auto; } - .detail-grid { grid-template-columns: 1fr; } - .detail-sidebar { position: static; } - .detail-hero-content { flex-direction: column; gap: 28px; } - .detail-hero-actions { flex-direction: row; padding-top: 0; } - .footer-inner { grid-template-columns: 1fr 1fr; row-gap: 40px; } + .hero-inner { + grid-template-columns: 1fr; + gap: 48px; + } + + .hero-visual { + max-width: 480px; + margin: 0 auto; + } + + .detail-grid { + grid-template-columns: 1fr; + } + + .detail-sidebar { + position: static; + } + + .detail-hero-content { + flex-direction: column; + gap: 28px; + } + + .detail-hero-actions { + flex-direction: row; + padding-top: 0; + } + + .footer-inner { + grid-template-columns: 1fr 1fr; + row-gap: 40px; + } } @media (max-width: 860px) { - .features-grid { grid-template-columns: 1fr; } - .steps-grid { flex-direction: column; align-items: stretch; } - .step-connector { display: none; } - .step-card { max-width: 100%; } + .features-grid { + grid-template-columns: 1fr; + } + + .steps-grid { + flex-direction: column; + align-items: stretch; + } + + .step-connector { + display: none; + } + + .step-card { + max-width: 100%; + } } @media (max-width: 640px) { - .nav-links { display: none; } - .nav-mobile-toggle { display: flex; } - .nav-inner { padding: 0 20px; } + .nav-links { + display: none; + } + + .nav-mobile-toggle { + display: flex; + } + + .nav-inner { + padding: 0 20px; + } - .hero { padding: 110px 20px 72px; } + .hero { + padding: 110px 20px 72px; + } - .form-card { padding: 28px 22px 24px; } + .form-card { + padding: 28px 22px 24px; + } + .form-row { grid-template-columns: 1fr; gap: 16px; - } + } - .results-grid { grid-template-columns: 1fr; } + .results-grid { + grid-template-columns: 1fr; + } - .detail-hero-actions { flex-direction: column; } - .code-panel { height: 82vh; } + .detail-hero-actions { + flex-direction: column; + } + + .code-panel { + height: 82vh; + } + + .footer-inner { + grid-template-columns: 1fr; + } + + .footer-bottom { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } - .footer-inner { grid-template-columns: 1fr; } - .footer-bottom { flex-direction: column; align-items: flex-start; gap: 10px; } + .container { + padding: 0 20px; + } - .container { padding: 0 20px; } - .section-sub { margin-bottom: 40px; } + .section-sub { + margin-bottom: 40px; + } } .github-input-reveal input { @@ -2195,43 +2791,58 @@ select:focus { /* Fixed button that appears after user scrolls 300px down the project detail page */ #scroll-top-btn { - position: fixed; - bottom: 2rem; - right: 2rem; - display: none; - align-items: center; - justify-content: center; - width: 2.75rem; - height: 2.75rem; - border: none; - border-radius: 50%; - background-color: var(--indigo-700); - color: var(--white); - font-size: 1.25rem; - cursor: pointer; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - transition: opacity 0.3s ease, transform 0.2s ease; - z-index: 999; + position: fixed; + bottom: 2rem; + right: 2rem; + display: none; + align-items: center; + justify-content: center; + width: 2.75rem; + height: 2.75rem; + border: none; + border-radius: 50%; + background-color: var(--indigo-700); + color: var(--white); + font-size: 1.25rem; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: opacity 0.3s ease, transform 0.2s ease; + z-index: 999; } #scroll-top-btn:hover { - transform: translateY(-3px); - opacity: 0.9; + transform: translateY(-3px); + opacity: 0.9; } #scroll-top-btn.visible { - display: flex; + display: flex; } -.noscript-warning { - background-color: #fff3cd; - color: #856404; - border: 1px solid #ffc107; - border-bottom: 2px solid #ffc107; - padding: 1rem 1.5rem; - margin-top: 64px; /* pushes below the fixed 64px navbar */ - text-align: center; - font-size: 1rem; - font-weight: 500; - width: 100%; + +/* Line-numbered code rows */ +.code-line { + display: flex; + align-items: flex-start; + min-height: 1.75em; +} + +.line-number { + display: inline-block; + min-width: 44px; + padding-right: 16px; + text-align: right; + color: rgba(255, 255, 255, 0.2); + border-right: 1px solid rgba(255, 255, 255, 0.08); + margin-right: 16px; + user-select: none; + flex-shrink: 0; + font-size: 0.78rem; + line-height: 1.75; +} + +.line-content { + flex: 1; + white-space: pre; + color: #e6edf3; } \ No newline at end of file diff --git a/templates/project.html b/templates/project.html index 2ef43076..85fa9234 100644 --- a/templates/project.html +++ b/templates/project.html @@ -1,5 +1,6 @@ + @@ -17,8 +18,11 @@ - + +
- + + + + + Download Starter Code
@@ -90,7 +103,12 @@

{{ project.title }}

- + + + + +

Features

@@ -98,7 +116,10 @@

Features

{% for feature in project.features %}
  • - + + + {{ feature }}
  • @@ -110,7 +131,10 @@

    Features

    - + + +

    Project Roadmap

    @@ -139,7 +163,12 @@

    Project Roadmap

    - + + + + +

    Learning Resources

    @@ -149,12 +178,17 @@

    Learning Resources

    {% set parts = resource.split(": http") %} {% if parts|length > 1 %} - - {{ parts[0] }} - - + + {{ parts[0] }} + + + + + + {% else %} - {{ resource }} + {{ resource }} {% endif %} {% endfor %} @@ -169,7 +203,12 @@

    Learning Resources