From 221dfbe3a3e690523bc3f3e15227e3c89720627e Mon Sep 17 00:00:00 2001 From: Suhas334 Date: Wed, 20 May 2026 11:08:06 +0530 Subject: [PATCH 1/2] Added reusable Back to Top button --- static/script.js | 34 ++++++++++++++++++++++++ static/style.css | 61 ++++++++++++++++++++++++++++++++++++++++++++ templates/index.html | 3 +++ 3 files changed, 98 insertions(+) diff --git a/static/script.js b/static/script.js index 3a1141aa..8bbda14e 100644 --- a/static/script.js +++ b/static/script.js @@ -858,3 +858,37 @@ if (scrollTopBtn) { window.addEventListener('scroll', handleScroll); scrollTopBtn.addEventListener('click', scrollToTop); } + +/* ========================================= + Back To Top Button +========================================= */ + +const backToTopBtn = + document.getElementById("backToTopBtn"); + +/* Show button after scrolling */ + +window.addEventListener("scroll", () => { + + if (window.scrollY > 300) { + + backToTopBtn.style.display = "block"; + + } else { + + backToTopBtn.style.display = "none"; + + } + +}); + +/* Smooth scroll to top */ + +backToTopBtn.addEventListener("click", () => { + + window.scrollTo({ + top: 0, + behavior: "smooth" + }); + +}); \ No newline at end of file diff --git a/static/style.css b/static/style.css index 0883029d..d665c7e0 100644 --- a/static/style.css +++ b/static/style.css @@ -2148,4 +2148,65 @@ select:focus { #scroll-top-btn.visible { display: flex; +} + +/* ========================================= + Back To Top Button +========================================= */ + +#backToTopBtn { + display: none; + + position: fixed; + + bottom: 24px; + right: 24px; + + z-index: 999; + + width: 50px; + height: 50px; + + border: none; + border-radius: 50%; + + background: #4f46e5; + color: white; + + font-size: 20px; + font-weight: bold; + + cursor: pointer; + + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); + + transition: + opacity 0.3s ease, + transform 0.3s ease, + background 0.3s ease; +} + +#backToTopBtn:hover { + transform: translateY(-4px); + background: #4338ca; +} + +#backToTopBtn:focus { + outline: 3px solid #c7d2fe; + outline-offset: 3px; +} + +/* Mobile Responsive */ +@media (max-width: 768px) { + + #backToTopBtn { + width: 44px; + height: 44px; + + font-size: 18px; + + bottom: 18px; + right: 18px; + } + } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index b8d08125..ed4533f1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -636,6 +636,9 @@ + From 23ec9b24c7d9dd5a078e9e3a1cdaedcac3a75209 Mon Sep 17 00:00:00 2001 From: Suhas334 Date: Tue, 2 Jun 2026 11:54:28 +0530 Subject: [PATCH 2/2] Fix syntax errors in script.js introduced during merge --- static/script.js | 706 +++++++++++++++++++++++------------------------ 1 file changed, 347 insertions(+), 359 deletions(-) diff --git a/static/script.js b/static/script.js index 9ff8600c..4a26ce55 100644 --- a/static/script.js +++ b/static/script.js @@ -36,7 +36,7 @@ // ---- Part B: sync button state once DOM is ready ---------- function initTheme() { - var html = document.documentElement; + var html = document.documentElement; var theme = html.dataset.theme || "light"; // Sync every toggle button on the page (desktop + mobile versions) @@ -60,7 +60,7 @@ // ---- Part C: apply a theme change ------------------------- function applyTheme(theme) { - var html = document.documentElement; + var html = document.documentElement; var isDark = theme === "dark"; // 1. Apply via data attribute — CSS [data-theme="dark"] picks this up @@ -114,7 +114,7 @@ // ============================================================ // Detect which page we are on // ============================================================ -var isIndexPage = !!document.getElementById("recommend-form"); +var isIndexPage = !!document.getElementById("recommend-form"); // !! trick turns the DOM result into a simple true/false 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 @@ -132,7 +132,7 @@ var errorMsg = document.getElementById('github-modal-error'); // ============================================================ (function initMobileNav() { var toggle = document.getElementById("nav-mobile-toggle"); //hamburger button - var menu = document.getElementById("nav-mobile-menu"); //dropdown menu + var menu = document.getElementById("nav-mobile-menu"); //dropdown menu // Nothing to do if the nav isn't on this page, just bail out if (!toggle || !menu) return; @@ -148,9 +148,9 @@ var errorMsg = document.getElementById('github-modal-error'); }); // Close menu when any mobile link is clicked - menu.querySelectorAll(".nav-mobile-link").forEach(function (link) { - link.addEventListener("click", function () { - menu.classList.remove("open"); + menu.querySelectorAll(".nav-mobile-link").forEach(function (link) { + link.addEventListener("click", function () { + menu.classList.remove("open"); toggle.classList.remove("open"); // FIX: reset aria-expanded when menu closes via link click toggle.setAttribute("aria-expanded", "false"); @@ -166,55 +166,55 @@ if (isIndexPage) { // DOM references // grabbing all the elements we'll need so we're not calling getElementById over and over again throughout the code - var form = document.getElementById("recommend-form"); - var submitBtn = document.getElementById("submit-btn"); - var btnLabel = document.getElementById("btn-label"); // "get recommendations" text - var btnLoading = document.getElementById("btn-loading"); // spinner icon inside the button - var resultsSection = document.getElementById("results-section"); - var resultsGrid = document.getElementById("results-grid"); - var resultsLoadingEl = document.getElementById("results-loading"); // "Loading..." text in the results - var resultsEmptyEl = document.getElementById("results-empty"); - var emptyMessageEl = document.getElementById("empty-message"); - var skillsHidden = document.getElementById("skills"); // the hidden input that holds skills list - var skillsTextInput = document.getElementById("skills-input"); //visible text box in which user types skills - var chipsSelectedEl = document.getElementById("skill-chips-selected"); //selected skills tags container - var quickPickChips = document.querySelectorAll(".skill-chip"); // predefined skills user can click + var form = document.getElementById("recommend-form"); + var submitBtn = document.getElementById("submit-btn"); + var btnLabel = document.getElementById("btn-label"); // "get recommendations" text + var btnLoading = document.getElementById("btn-loading"); // spinner icon inside the button + var resultsSection = document.getElementById("results-section"); + var resultsGrid = document.getElementById("results-grid"); + var resultsLoadingEl = document.getElementById("results-loading"); // "Loading..." text in the results + var resultsEmptyEl = document.getElementById("results-empty"); + var emptyMessageEl = document.getElementById("empty-message"); + var skillsHidden = document.getElementById("skills"); // the hidden input that holds skills list + var skillsTextInput = document.getElementById("skills-input"); //visible text box in which user types skills + var chipsSelectedEl = document.getElementById("skill-chips-selected"); //selected skills tags container + var quickPickChips = document.querySelectorAll(".skill-chip"); // predefined skills user can click // Tracks currently selected skills to prevent duplicates var selectedSkills = []; // Clear Filters Button Functionality -var clearFiltersBtn = document.getElementById("clear-filters-btn"); -if (clearFiltersBtn) { - clearFiltersBtn.addEventListener("click", function() { - var recommendForm = document.getElementById("recommend-form"); - if (recommendForm) { - // 1. Reset standard form dropdowns and fields - recommendForm.reset(); - - // 2. Clear out the internal JavaScript array tracker completely - selectedSkills = []; - - // 3. Clear the hidden inputs and visual chips using the file's own variables - if (skillsHidden) skillsHidden.value = ""; - if (chipsSelectedEl) chipsSelectedEl.innerHTML = ""; - if (skillsTextInput) { - skillsTextInput.value = ""; - skillsTextInput.focus(); // Place cursor back on input - } - - // 4. Hide autocomplete suggestions if any are open - var suggestionsBox = document.getElementById("skills-suggestions"); - if (suggestionsBox) suggestionsBox.innerHTML = ""; - - // 5. Reset quick-pick chip visual active states if they have any - if (quickPickChips) { - quickPickChips.forEach(function(chip) { - chip.classList.remove("active", "selected"); - }); - } + var clearFiltersBtn = document.getElementById("clear-filters-btn"); + if (clearFiltersBtn) { + clearFiltersBtn.addEventListener("click", function () { + var recommendForm = document.getElementById("recommend-form"); + if (recommendForm) { + // 1. Reset standard form dropdowns and fields + recommendForm.reset(); + + // 2. Clear out the internal JavaScript array tracker completely + selectedSkills = []; + + // 3. Clear the hidden inputs and visual chips using the file's own variables + if (skillsHidden) skillsHidden.value = ""; + if (chipsSelectedEl) chipsSelectedEl.innerHTML = ""; + if (skillsTextInput) { + skillsTextInput.value = ""; + skillsTextInput.focus(); // Place cursor back on input } + + // 4. Hide autocomplete suggestions if any are open + var suggestionsBox = document.getElementById("skills-suggestions"); + if (suggestionsBox) suggestionsBox.innerHTML = ""; + + // 5. Reset quick-pick chip visual active states if they have any + if (quickPickChips) { + quickPickChips.forEach(function (chip) { + chip.classList.remove("active", "selected"); + }); + } + } }); -} + } // ---------------------------------------------------------- @@ -543,7 +543,7 @@ if (clearFiltersBtn) { removeBtn.type = "button"; removeBtn.className = "skill-chip-remove"; removeBtn.innerHTML = "×"; //'x' symbol - removeBtn.setAttribute("aria-label", "Remove " + skill); + removeBtn.setAttribute("aria-label", "Remove " + skill); removeBtn.addEventListener("click", function (e) { // Stop click from bubbling up to the chip wrap's click listener e.stopPropagation(); @@ -556,7 +556,7 @@ if (clearFiltersBtn) { } function syncSkillsHiddenInput() { - if (!skillsHidden){ + if (!skillsHidden) { var skillsHidden = document.getElementById("skills"); } // Keep the hidden in sync for form serialisation @@ -624,7 +624,7 @@ if (clearFiltersBtn) { form.addEventListener("submit", function (evt) { evt.preventDefault(); //stop the browser from reloading the page on form submit clearAllErrors() - + if (skillsTextInput.value.trim()) { addSkill(skillsTextInput.value); skillsTextInput.value = ""; @@ -670,16 +670,13 @@ if (clearFiltersBtn) { renderResults(data.projects || [], data.message); }) .catch(function () { - setLoadingState(false); - //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, - interest: document.getElementById("interest").value, - time: document.getElementById("time").value - }; + var generalErr = document.getElementById("form-error-general"); + if (generalErr) { + generalErr.textContent = "An error occurred. Please try again."; + } + }); + }); }); // Manages the loading state of the form and results section(whats visible or not) @@ -699,8 +696,8 @@ if (clearFiltersBtn) { // Scroll down so the user can see the spinner without manually scrolling resultsSection.scrollIntoView({ behavior: "smooth" }); } else { - resultsLoadingEl.style.display = "none"; - resultsGrid.style.display = "grid"; //switch back to gird layout + resultsLoadingEl.style.display = "none"; + resultsGrid.style.display = "grid"; //switch back to gird layout } } @@ -717,305 +714,299 @@ if (clearFiltersBtn) { // Clear out any cards from a previous search before showing new ones resultsGrid.innerHTML = ""; - if (!projects || projects.length === 0) { - resultsGrid.style.display = "none"; - resultsEmptyEl.style.display = "block"; - resultsGrid.style.display = "none"; - resultsEmptyEl.style.display = "block"; - if (message && emptyMessageEl) emptyMessageEl.textContent = message; if (!projects || projects.length === 0) { //if no projects returned from api, show the "no results" message and hide the grid - resultsGrid.style.display = "none"; - resultsEmptyEl.style.display = "block"; - - // Show a friendly custom message when the user selected an interest - var selectedInterest = document.getElementById("interest")?.value; - if (selectedInterest) { - emptyMessageEl.textContent = "No projects are currently available for this interest. Please check back later or try a different area."; - } else if (message) { - emptyMessageEl.textContent = message; - } else { - emptyMessageEl.textContent = "Try adjusting your skills or choosing a different interest area."; + resultsGrid.style.display = "none"; + resultsEmptyEl.style.display = "block"; + + // Show a friendly custom message when the user selected an interest + var selectedInterest = document.getElementById("interest")?.value; + if (selectedInterest) { + emptyMessageEl.textContent = "No projects are currently available for this interest. Please check back later or try a different area."; + } else if (message) { + emptyMessageEl.textContent = message; + } else { + emptyMessageEl.textContent = "Try adjusting your skills or choosing a different interest area."; + } + + resultsSection.scrollIntoView({ behavior: "smooth" }); + return; } + resultsEmptyEl.style.display = "none"; + resultsGrid.style.display = "grid"; + + //build a card for each project and add it to the grid + projects.forEach(function (project) { + resultsGrid.appendChild(buildProjectCard(project)); + }); + resultsSection.scrollIntoView({ behavior: "smooth" }); - return; } - resultsEmptyEl.style.display = "none"; - resultsGrid.style.display = "grid"; + // builds one project card as a DOM element and returns it + // the card has title, short description, tags and link + function buildProjectCard(project) { + var card = document.createElement("div"); + card.className = "project-card"; + + // Title + var title = document.createElement("h3"); + title.className = "project-card-title"; + title.textContent = project.title; + + // Description (truncated for visual consistency) + var desc = document.createElement("p"); + desc.className = "project-card-desc"; + // Cut description to 120 chars so all cards stay the same height + desc.textContent = truncate(project.description, 120); + + // Tags row + var tagsRow = document.createElement("div"); + tagsRow.className = "project-card-tags"; + + // Show all project skills as tags so users can see the full match + (project.skills || []).forEach(function (skill) { + tagsRow.appendChild(createTag(skill, "skill")); + }); - //build a card for each project and add it to the grid - projects.forEach(function (project) { - resultsGrid.appendChild(buildProjectCard(project)); - }); + // Level tag (colour-coded via CSS class) + // Lowercase so it matches the CSS class names like "level beginner", "level advanced" + var levelClass = "level " + (project.level || "").toLowerCase(); + tagsRow.appendChild(createTag(project.level, levelClass)); - resultsSection.scrollIntoView({ behavior: "smooth" }); - } + // Time tag + tagsRow.appendChild(createTag("Time: " + project.time, "time")); - // builds one project card as a DOM element and returns it - // the card has title, short description, tags and link - function buildProjectCard(project) { - var card = document.createElement("div"); - card.className = "project-card"; - - // Title - var title = document.createElement("h3"); - title.className = "project-card-title"; - title.textContent = project.title; - - // Description (truncated for visual consistency) - var desc = document.createElement("p"); - desc.className = "project-card-desc"; - // Cut description to 120 chars so all cards stay the same height - desc.textContent = truncate(project.description, 120); - - // Tags row - var tagsRow = document.createElement("div"); - tagsRow.className = "project-card-tags"; - - // Show all project skills as tags so users can see the full match - (project.skills || []).forEach(function (skill) { - tagsRow.appendChild(createTag(skill, "skill")); - }); + // Footer with view-details link + var footer = document.createElement("div"); + footer.className = "project-card-footer"; - // Level tag (colour-coded via CSS class) - // Lowercase so it matches the CSS class names like "level beginner", "level advanced" - var levelClass = "level " + (project.level || "").toLowerCase(); - tagsRow.appendChild(createTag(project.level, levelClass)); + var link = document.createElement("a"); + link.className = "btn-details"; + link.textContent = "View Full Project"; + link.href = "/project/" + project.id; //each project has a unique id - // Time tag - tagsRow.appendChild(createTag("Time: " + project.time, "time")); + footer.appendChild(link); - // Footer with view-details link - var footer = document.createElement("div"); - footer.className = "project-card-footer"; + // Assemble the card in order + card.appendChild(title); + card.appendChild(desc); + card.appendChild(tagsRow); + card.appendChild(footer); - var link = document.createElement("a"); - link.className = "btn-details"; - link.textContent = "View Full Project"; - link.href = "/project/" + project.id; //each project has a unique id + return card; + } - footer.appendChild(link); + // helper to create a coloured tag element (used for skills, level, time tags on the cards) + function createTag(text, type) { + var span = document.createElement("span"); + // The type becomes a BEM modifier so CSS can style each tag differently + span.className = "project-tag project-tag--" + type; + span.textContent = text; + return span; + } - // Assemble the card in order - card.appendChild(title); - card.appendChild(desc); - card.appendChild(tagsRow); - card.appendChild(footer); + function truncate(text, maxLength) { + // Safety check — just return empty string if text is missing + if (!text) return ""; + // Only add "..." if the text is actually longer than the limit + return text.length > maxLength ? text.slice(0, maxLength) + "..." : text; + } - return card; - } + } // end isIndexPage - // helper to create a coloured tag element (used for skills, level, time tags on the cards) - function createTag(text, type) { - var span = document.createElement("span"); - // The type becomes a BEM modifier so CSS can style each tag differently - span.className = "project-tag project-tag--" + type; - span.textContent = text; - return span; - } - function truncate(text, maxLength) { - // Safety check — just return empty string if text is missing - if (!text) return ""; - // Only add "..." if the text is actually longer than the limit - return text.length > maxLength ? text.slice(0, maxLength) + "..." : text; - } + // ============================================================ + // DETAIL PAGE + // ============================================================ + if (isDetailPage) { -} // end isIndexPage + 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;
 
-// ============================================================
-// 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();
-  }
+    //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";
 
-  //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 = "";
-  }
+      // Only fetch the code on the first open, no need to re-fetch every time
+      if (!codeFetched) fetchStarterCode();
+    }
 
-  //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.";
-        }
-      });
-  }
+    //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 = "";
+    }
 
-  // Attach open/close handlers
-  if (btnViewCode) btnViewCode.addEventListener("click", openCodePanel);
-  if (btnViewCodeSm) btnViewCodeSm.addEventListener("click", openCodePanel);
-  if (btnClosePanel) btnClosePanel.addEventListener("click", closeCodePanel);
+    //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...";
 
-  if (codePanelOverlay) {
-    codePanelOverlay.addEventListener("click", closeCodePanel); //clicking on the background overlay to also close the panel
-  }
+      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.";
+          }
+        });
+    }
 
-  // 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
-  });
+    // Attach open/close handlers
+    if (btnViewCode) btnViewCode.addEventListener("click", openCodePanel);
+    if (btnViewCodeSm) btnViewCodeSm.addEventListener("click", openCodePanel);
+    if (btnClosePanel) btnClosePanel.addEventListener("click", closeCodePanel);
 
-  // ----------------------------------------------------------
-  // 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");
+    if (codePanelOverlay) {
+      codePanelOverlay.addEventListener("click", closeCodePanel); //clicking on the background overlay to also close the panel
     }
 
-    // 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);
-  }
+    // 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
+    });
 
-  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
+    // ----------------------------------------------------------
+    // 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");
       }
-    });
-  }
 
-  // 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
+      // 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.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
+        }
+      });
+    }
 
-if (
+    // 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 (
     openModalBtn &&
     closeModalBtn &&
     modal &&
     githubInput &&
     fetchBtn &&
     errorMsg
-) {
-// 1. Open Github Input Modal
-  openModalBtn.addEventListener('click', (e) => {
+  ) {
+    // 1. Open Github Input Modal
+    openModalBtn.addEventListener('click', (e) => {
       e.preventDefault();
       modal.classList.add('active');
       githubInput.focus();
-  });
+    });
 
-  // 2. Close Github Input Modal
-  const closeGithubModal = () => {
+    // 2. Close Github Input Modal
+    const closeGithubModal = () => {
       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) => {
+    // Close on clicking outside the card
+    modal.addEventListener('click', (e) => {
       if (e.target === modal) closeGithubModal();
-  });
+    });
 
-  // 3. Fetch Skills Logic
-  fetchBtn.addEventListener('click', async () => {
+    // 3. Fetch Skills Logic
+    fetchBtn.addEventListener('click', async () => {
       const username = githubInput.value.trim();
       if (!username) return;
 
@@ -1023,57 +1014,54 @@ if (
       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))];
-
-          if (langs.length > 0) {
-              langs.forEach(lang => {
-                  if (typeof addSkill === 'function') addSkill(lang);
-              });
-              closeGithubModal();
-          } else {
-              errorMsg.textContent = "No public languages found.";
-          }
+        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))];
+
+        if (langs.length > 0) {
+          langs.forEach(lang => {
+            if (typeof addSkill === 'function') addSkill(lang);
+          });
+          closeGithubModal();
+        } else {
+          errorMsg.textContent = "No public languages found.";
+        }
       } catch (err) {
-          errorMsg.textContent = err.message ?? "Failed to fetch skills";
+        errorMsg.textContent = err.message ?? "Failed to fetch skills";
       } finally {
-          fetchBtn.disabled = false;
-          fetchBtn.textContent = 'Fetch Skills';
+        fetchBtn.disabled = false;
+        fetchBtn.textContent = 'Fetch Skills';
       }
-  });
-}
+    });
+  } // end github modal handlers
+
+    /* ---- Scroll-to-top button ---- */
 
-} // end github modal handlers
+    /* Show the button only when the user has scrolled more than 300px */
+    var SCROLL_THRESHOLD = 300;
 
-/* ---- Scroll-to-top button ---- */
+    /* Get the button element; guard against pages that do not have it */
+    var scrollTopBtn = document.getElementById('scroll-top-btn');
 
-/* Show the button only when the user has scrolled more than 300px */
-var SCROLL_THRESHOLD = 300;
+    /* 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');
+      }
+    }
 
-/* Get the button element; guard against pages that do not have it */
-var scrollTopBtn = document.getElementById('scroll-top-btn');
+    /* Smooth-scroll to the very top of the page */
+    function scrollToTop() {
+      window.scrollTo({ top: 0, behavior: 'smooth' });
+    }
 
-/* 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');
-  }
-}
-
-/* Smooth-scroll to the very top of the page */
-function scrollToTop() {
-  window.scrollTo({ top: 0, behavior: 'smooth' });
-}
-
-/* Only wire up listeners if the button exists on this page */
-if (scrollTopBtn) {
-    window.addEventListener('scroll', handleScroll);
-    scrollTopBtn.addEventListener('click', scrollToTop);
-}
-}
+    /* Only wire up listeners if the button exists on this page */
+    if (scrollTopBtn) {
+      window.addEventListener('scroll', handleScroll);
+      scrollTopBtn.addEventListener('click', scrollToTop);
+    }