diff --git a/components/header.html b/components/header.html index a112484..3890509 100644 --- a/components/header.html +++ b/components/header.html @@ -20,6 +20,7 @@
  • Experience
  • Offering
  • Live-Work
  • +
  • Infinite-Carousel
  • Latest-Blog
  • diff --git a/css/blogger.css b/css/blogger.css index cf4f9c6..82821ea 100644 --- a/css/blogger.css +++ b/css/blogger.css @@ -4,7 +4,7 @@ .latest-blog-section { width: 100%; - padding: 90px 6%; + padding: 60px 6%; box-sizing: border-box; } @@ -15,7 +15,7 @@ .latest-blog-section .section-header h2 { margin: 0; - font-size: 2.2rem; + /* font-size: 2.2rem; */ font-weight: 700; letter-spacing: 1px; } @@ -26,10 +26,18 @@ .latest-blog-section .section-header p { margin-top: 12px; - font-size: 1rem; + /* font-size: 1rem; */ opacity: 0.75; } +/* ========================================================= + CATEGORY NAVIGATION + Desktop Hidden + ========================================================= */ + +.blog-category-navigation { + display: none; +} /* ========================================================= CATEGORY CONTAINER @@ -42,7 +50,6 @@ width: 100%; } - /* ========================================================= CATEGORY CARD ========================================================= */ @@ -54,17 +61,19 @@ border-radius: 14px; background: rgba(172, 3, 3, 0.04); box-sizing: border-box; - - transition: - transform 0.3s ease, - box-shadow 0.3s ease, - border-color 0.3s ease; + transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; } -.blog-category:hover { - transform: translateY(-5px); - border-color: rgba(128, 128, 128, 0.45); - box-shadow: 0 12px 30px rgba(0, 0, 0, 0.08); +/* ========================================================= + DESKTOP HOVER EFFECT + ========================================================= */ + +@media (min-width: 1025px) and (hover: hover) and (pointer: fine) { + .blog-category:hover { + transform: translateY(-5px); + border-color: rgba(128, 128, 128, 0.45); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.08); + } } /* ========================================================= @@ -96,7 +105,6 @@ font-weight: 700; } - /* ========================================================= VIEW ALL ========================================================= */ @@ -106,13 +114,10 @@ display: inline-flex; align-items: center; gap: 6px; - font-size: 0.8rem; font-weight: 600; - text-decoration: none; white-space: nowrap; - transition: opacity 0.2s ease; } @@ -120,7 +125,6 @@ opacity: 0.65; } - /* ========================================================= POSTS ========================================================= */ @@ -130,7 +134,6 @@ flex-direction: column; } - /* ========================================================= INDIVIDUAL POST ========================================================= */ @@ -152,11 +155,9 @@ .blog-post-title { margin: 0 0 7px; - font-size: 0.98rem; line-height: 1.45; font-weight: 600; - transition: opacity 0.2s ease; } @@ -169,7 +170,6 @@ opacity: 0.55; } - /* ========================================================= LOADING / ERROR ========================================================= */ @@ -181,36 +181,114 @@ opacity: 0.6; } - /* ========================================================= TABLET + 769px - 1024px ========================================================= */ -@media (max-width: 1100px) { +@media (min-width: 769px) and (max-width: 1024px) { + .latest-blog-section { + padding: 75px 5%; + } + + .latest-blog-section .section-header { + margin-bottom: 35px; + } + + /* ------------------------------------------------------- + CATEGORY NAVIGATION + ------------------------------------------------------- */ + + .blog-category-navigation { + display: flex; + align-items: center; + justify-content: center; + gap: 35px; + margin-bottom: 28px; + padding-bottom: 2px; + border-bottom: 1px solid rgba(128, 128, 128, 0.18); + } + + .blog-category-button { + position: relative; + padding: 10px 0; + border: none; + background: transparent; + color: inherit; + font: inherit; + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0.5px; + opacity: 0.5; + white-space: nowrap; + cursor: pointer; + transition: opacity 0.25s ease, color 0.25s ease; + } + + .blog-category-button::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -3px; + height: 2px; + transform: scaleX(0); + transform-origin: center; + background: currentColor; + transition: transform 0.25s ease; + } + + .blog-category-button:hover, + .blog-category-button.active { + opacity: 1; + } + + .blog-category-button.active::after { + transform: scaleX(1); + } + + /* ------------------------------------------------------- + HORIZONTAL SLIDER + ------------------------------------------------------- */ .blog-categories { - grid-template-columns: 1fr; + display: flex; + width: 100%; + gap: 0; + padding: 12px 0 18px; + overflow-x: auto; + overflow-y: auto; + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + scrollbar-width: none; + -ms-overflow-style: none; + } + + .blog-categories::-webkit-scrollbar { + display: none; } .blog-category { + flex: 0 0 100%; width: 100%; + scroll-snap-align: start; + scroll-snap-stop: always; + padding: 28px; } - } - /* ========================================================= MOBILE + 768px and Below ========================================================= */ -@media (max-width: 600px) { - +@media (max-width: 768px) { .latest-blog-section { padding: 65px 5%; } .latest-blog-section .section-header { - margin-bottom: 35px; + margin-bottom: 30px; } .latest-blog-section .section-header h2 { @@ -222,13 +300,92 @@ line-height: 1.6; } + /* ------------------------------------------------------- + CATEGORY NAVIGATION + ------------------------------------------------------- */ + + .blog-category-navigation { + display: flex; + width: 100%; + gap: 24px; + margin-bottom: 22px; + padding-bottom: 2px; + overflow-x: auto; + overflow-y: hidden; + scroll-behavior: smooth; + border-bottom: 1px solid rgba(128, 128, 128, 0.18); + scrollbar-width: none; + -ms-overflow-style: none; + } + + .blog-category-navigation::-webkit-scrollbar { + display: none; + } + + .blog-category-button { + position: relative; + flex-shrink: 0; + padding: 9px 0; + border: none; + background: transparent; + color: inherit; + font: inherit; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.4px; + opacity: 0.5; + white-space: nowrap; + cursor: pointer; + transition: opacity 0.25s ease, color 0.25s ease; + } + + .blog-category-button::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -3px; + height: 2px; + transform: scaleX(0); + transform-origin: center; + background: currentColor; + transition: transform 0.25s ease; + } + + .blog-category-button.active { + opacity: 1; + } + + .blog-category-button.active::after { + transform: scaleX(1); + } + + /* ------------------------------------------------------- + HORIZONTAL SLIDER + ------------------------------------------------------- */ + .blog-categories { display: flex; - flex-direction: column; - gap: 18px; + width: 100%; + gap: 0; + padding: 12px 0 18px; + overflow-x: auto; + overflow-y: auto; + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + scrollbar-width: none; + -ms-overflow-style: none; + } + + .blog-categories::-webkit-scrollbar { + display: none; } .blog-category { + flex: 0 0 100%; + width: 100%; + scroll-snap-align: start; + scroll-snap-stop: always; padding: 22px; border-radius: 12px; } @@ -245,4 +402,17 @@ font-size: 0.72rem; } + .blog-post-title { + font-size: 0.9rem; + } +} + +/* ========================================================= + BLOG CARD INTERACTION + ========================================================= */ + +.blog-category:active { + transform: translateY(-3px); + border-color: rgba(128, 128, 128, 0.45); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.10); } \ No newline at end of file diff --git a/css/global.css b/css/global.css index c41beae..11f9582 100644 --- a/css/global.css +++ b/css/global.css @@ -86,6 +86,11 @@ p { font-style: normal; } +/* Body Dark */ +body.dark h3 { + --color-text: #ffffff; +} + /* =================================================== ===== Common Navbar (Glassmorphic) ===== =================================================== */ diff --git a/index.html b/index.html index 22bf518..0c35efb 100644 --- a/index.html +++ b/index.html @@ -416,7 +416,7 @@

    🏋️ THE PERFORMANCE PLATFORM

    -
    + +
    -

    LATEST BLOG POSTS

    +

    LATEST BLOG POSTS

    +

    Explore the latest insights, ideas, and technical articles from LATEST BLOG POSTS

    + +
    + + + + + +
    +
    @@ -820,9 +849,10 @@

    System Design

    - DATA STRUCTURES & ALGORITHMS + + DATA STRUCTURES & ALGORITHMS + +

    Data Structures & Algorithms

    diff --git a/js/blogger.js b/js/blogger.js index 3e5fe98..6646552 100644 --- a/js/blogger.js +++ b/js/blogger.js @@ -7,134 +7,92 @@ const blogSources = [ { container: "technical-journeys-posts", feed: "https://blog.vimaltech.dev/feeds/posts/default", - limit: 3 + limit: 3, }, { container: "system-design-posts", feed: "https://sd.vimaltech.dev/feeds/posts/default", - limit: 3 + limit: 3, }, { container: "dsa-posts", feed: "https://dsa.vimaltech.dev/feeds/posts/default", - limit: 3 - } + limit: 3, + }, ]; - /* ========================================================= LOAD BLOGGER FEED USING JSONP ========================================================= */ function loadBlogPosts(blog) { - - const callbackName = - `bloggerCallback_${Date.now()}_${Math.random() - .toString(36) - .substring(2, 8)}`; - - const container = - document.getElementById(blog.container); + const callbackName = `bloggerCallback_${Date.now()}_${Math.random() + .toString(36) + .substring(2, 8)}`; + const container = document.getElementById(blog.container); if (!container) { return; } - /* ----------------------------------------- Global JSONP callback ----------------------------------------- */ window[callbackName] = function (data) { - try { - - const entries = - data?.feed?.entry || []; + const entries = data?.feed?.entry || []; if (!entries.length) { - container.innerHTML = `
    No posts available.
    `; - return; } - /* ----------------------------------------- Clear loading message ----------------------------------------- */ container.innerHTML = ""; - /* ----------------------------------------- Render latest posts ----------------------------------------- */ - entries - .slice(0, blog.limit) - .forEach(entry => { - - const title = - entry.title?.$t || "Untitled Post"; - - const url = - getPostUrl(entry); - - const published = - entry.published?.$t || ""; - - const formattedDate = - formatBlogDate(published); - - - const postElement = - document.createElement("article"); - - postElement.className = - "blog-post"; - - - postElement.innerHTML = ` -
    - -

    - ${escapeHtml(title)} -

    - - - -
    - `; - - - container.appendChild(postElement); - - }); - + entries.slice(0, blog.limit).forEach((entry) => { + const title = entry.title?.$t || "Untitled Post"; + const url = getPostUrl(entry); + const published = entry.published?.$t || ""; + const formattedDate = formatBlogDate(published); + const postElement = document.createElement("article"); + + postElement.className = "blog-post"; + + postElement.innerHTML = ` + +

    + ${escapeHtml(title)} +

    + +
    + `; + container.appendChild(postElement); + }); } catch (error) { - - console.error( - `Error processing Blogger feed:`, - error - ); - + console.error("Error processing Blogger feed:", error); showBlogError(container); - } - /* ----------------------------------------- Cleanup callback ----------------------------------------- */ @@ -144,17 +102,13 @@ function loadBlogPosts(blog) { if (scriptElement.parentNode) { scriptElement.parentNode.removeChild(scriptElement); } - }; - /* ----------------------------------------- Create JSONP script ----------------------------------------- */ - const scriptElement = - document.createElement("script"); - + const scriptElement = document.createElement("script"); scriptElement.src = `${blog.feed}` + @@ -162,101 +116,71 @@ function loadBlogPosts(blog) { `&max-results=${blog.limit}` + `&callback=${callbackName}`; - scriptElement.async = true; - /* ----------------------------------------- Handle network/script errors ----------------------------------------- */ scriptElement.onerror = function () { - - console.error( - `Unable to load Blogger feed: ${blog.feed}` - ); - + console.error(`Unable to load Blogger feed: ${blog.feed}`); showBlogError(container); - - delete window[callbackName]; if (scriptElement.parentNode) { scriptElement.parentNode.removeChild(scriptElement); } - }; - document.head.appendChild(scriptElement); } - /* ========================================================= GET BLOG POST URL ========================================================= */ function getPostUrl(entry) { - - const alternateLink = - entry.link?.find( - link => link.rel === "alternate" - ); - + const alternateLink = entry.link?.find((link) => link.rel === "alternate"); return alternateLink?.href || "#"; } - /* ========================================================= FORMAT DATE ========================================================= */ function formatBlogDate(dateString) { - if (!dateString) { return ""; } - const date = - new Date(dateString); + const date = new Date(dateString); if (Number.isNaN(date.getTime())) { return ""; } - return date.toLocaleDateString( - "en-IN", - { - day: "numeric", - month: "short", - year: "numeric" - } - ); + return date.toLocaleDateString("en-IN", { + day: "numeric", + month: "short", + year: "numeric", + }); } - /* ========================================================= HTML ESCAPE ========================================================= */ function escapeHtml(value) { - - const div = - document.createElement("div"); - - div.textContent = - value; - + const div = document.createElement("div"); + div.textContent = value; return div.innerHTML; } - /* ========================================================= HTML ATTRIBUTE ESCAPE ========================================================= */ function escapeHtmlAttribute(value) { - return String(value) .replace(/&/g, "&") .replace(/"/g, """) @@ -265,33 +189,185 @@ function escapeHtmlAttribute(value) { .replace(/>/g, ">"); } - /* ========================================================= ERROR MESSAGE ========================================================= */ function showBlogError(container) { - container.innerHTML = `
    Unable to load posts right now.
    `; - } +/* ========================================================= + INITIALIZE BLOG POSTS + ========================================================= */ + +document.addEventListener("DOMContentLoaded", () => { + blogSources.forEach((blog) => loadBlogPosts(blog)); +}); /* ========================================================= - INITIALIZE + BLOG CATEGORY NAVIGATION + Tablet + Mobile ========================================================= */ -document.addEventListener( - "DOMContentLoaded", - () => { +document.addEventListener("DOMContentLoaded", () => { + const categoryContainer = document.querySelector(".blog-categories"); + const navigationContainer = document.querySelector( + ".blog-category-navigation", + ); + const categoryButtons = document.querySelectorAll(".blog-category-button"); + const categories = document.querySelectorAll(".blog-category"); + + if (!categoryContainer || !categoryButtons.length || !categories.length) { + return; + } + + /* ----------------------------------------------------- + CHECK SLIDER VIEW + ----------------------------------------------------- */ + + function isSliderView() { + return window.matchMedia("(max-width: 1024px)").matches; + } + + /* ----------------------------------------------------- + CHECK MOBILE VIEW + ----------------------------------------------------- */ + + function isMobileView() { + return window.matchMedia("(max-width: 768px)").matches; + } + + /* ----------------------------------------------------- + MOVE NAVIGATION TO ACTIVE BUTTON + Mobile Only + ----------------------------------------------------- */ + + function moveNavigationToActiveButton(targetName) { + if (!isMobileView() || !navigationContainer) { + return; + } - blogSources.forEach( - blog => loadBlogPosts(blog) + const activeButton = Array.from(categoryButtons).find( + (button) => button.dataset.blogTarget === targetName, ); + if (!activeButton) { + return; + } + + const buttonCenter = activeButton.offsetLeft + activeButton.offsetWidth / 2; + + const scrollPosition = buttonCenter - navigationContainer.clientWidth / 2; + + navigationContainer.scrollTo({ + left: Math.max(0, scrollPosition), + behavior: "smooth", + }); } -); \ No newline at end of file + + /* ----------------------------------------------------- + SET ACTIVE BUTTON + ----------------------------------------------------- */ + + function setActiveButton(targetName) { + categoryButtons.forEach((button) => { + button.classList.toggle( + "active", + button.dataset.blogTarget === targetName, + ); + }); + + moveNavigationToActiveButton(targetName); + } + + /* ----------------------------------------------------- + SLIDE TO CATEGORY + ----------------------------------------------------- */ + + function slideToCategory(targetName) { + const targetCategory = document.querySelector( + `.blog-category.${targetName}`, + ); + + if (!targetCategory) { + return; + } + + const scrollPosition = + targetCategory.offsetLeft - categoryContainer.offsetLeft; + + categoryContainer.scrollTo({ + left: scrollPosition, + behavior: "smooth", + }); + + setActiveButton(targetName); + } + + /* ----------------------------------------------------- + CATEGORY BUTTON CLICK + ----------------------------------------------------- */ + + categoryButtons.forEach((button) => { + button.addEventListener("click", () => { + if (!isSliderView()) { + return; + } + + const targetName = button.dataset.blogTarget; + + slideToCategory(targetName); + }); + }); + + /* ----------------------------------------------------- + UPDATE ACTIVE BUTTON + WHEN USER SWIPES / SCROLLS + ----------------------------------------------------- */ + + let scrollTimeout; + + categoryContainer.addEventListener("scroll", () => { + if (!isSliderView()) { + return; + } + + clearTimeout(scrollTimeout); + + scrollTimeout = setTimeout(() => { + const containerCenter = + categoryContainer.scrollLeft + categoryContainer.clientWidth / 2; + + let activeCategory = categories[0]; + let closestDistance = Infinity; + + categories.forEach((category) => { + const categoryCenter = category.offsetLeft + category.offsetWidth / 2; + + const distance = Math.abs(containerCenter - categoryCenter); + + if (distance < closestDistance) { + closestDistance = distance; + activeCategory = category; + } + }); + + if (activeCategory.classList.contains("technical-journeys")) { + setActiveButton("technical-journeys"); + } + + if (activeCategory.classList.contains("system-design")) { + setActiveButton("system-design"); + } + + if (activeCategory.classList.contains("dsa")) { + setActiveButton("dsa"); + } + }, 80); + }); +});