diff --git a/.github/workflows/doc-check-linter.yml b/.github/workflows/doc-check-linter.yml index 648f68e..8550cd2 100644 --- a/.github/workflows/doc-check-linter.yml +++ b/.github/workflows/doc-check-linter.yml @@ -77,6 +77,11 @@ jobs: echo "✅ Imported GPG_PUBLIC_KEY_4 (Contributor Account)" fi + if [ -n "${{ secrets.GPG_PUBLIC_KEY_5 }}" ]; then + echo "${{ secrets.GPG_PUBLIC_KEY_5 }}" | gpg --import + echo "✅ Imported GPG_PUBLIC_KEY_5 (Contributor Account)" + fi + # ---------- Verify latest commit ---------- - name: Verify commit signature # FIX: Only run strict verification if the PR is from the same repo (internal/trusted) or if it's a direct push @@ -107,7 +112,7 @@ jobs: echo "🔑 Fingerprint: $FINGERPRINT" # Replaced the short ID with the full 40-character fingerprint - TRUSTED_KEYS="83FB991D930D7177F25456C07F4C7CA953E1C09E 5CC01F41CD084AAC0593108DD432152833DA3244 A38379B51EB75EF1877A83ADBE677DAEFE33CB57 1606642895F688C9299B9E0AC97540DA6C9FA85C F5959E720D1C3DFACAD27E7588F6CD4E295C9062" + TRUSTED_KEYS="83FB991D930D7177F25456C07F4C7CA953E1C09E 5CC01F41CD084AAC0593108DD432152833DA3244 A38379B51EB75EF1877A83ADBE677DAEFE33CB57 1606642895F688C9299B9E0AC97540DA6C9FA85C F5959E720D1C3DFACAD27E7588F6CD4E295C9062 97C68C42AB4DD4797FA5EB93E9A0198A7C7B3BD6" if echo "$TRUSTED_KEYS" | grep -q "$FINGERPRINT"; then echo "✅ Trusted key" diff --git a/aboutUs/index.html b/aboutUs/index.html index 0159955..40a9614 100644 --- a/aboutUs/index.html +++ b/aboutUs/index.html @@ -403,45 +403,37 @@

diff --git a/contactUs/index.html b/contactUs/index.html index a3641cc..4fcc5b2 100644 --- a/contactUs/index.html +++ b/contactUs/index.html @@ -433,45 +433,37 @@

diff --git a/css/blogger.css b/css/blogger.css new file mode 100644 index 0000000..8c7c233 --- /dev/null +++ b/css/blogger.css @@ -0,0 +1,249 @@ +/* ========================================================= + LATEST BLOG POSTS + ========================================================= */ + +.latest-blog-section { + width: 100%; + padding: 90px 6%; + box-sizing: border-box; +} + +.latest-blog-section .section-header { + text-align: center; + margin-bottom: 55px; +} + +.latest-blog-section .section-header h2 { + margin: 0; + font-size: 2.2rem; + font-weight: 700; + letter-spacing: 1px; +} + +.latest-blog-section .section-header h2 span { + font-weight: 400; +} + +.latest-blog-section .section-header p { + margin-top: 12px; + font-size: 1rem; + opacity: 0.75; +} + + +/* ========================================================= + CATEGORY CONTAINER + ========================================================= */ + +.blog-categories { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 25px; + width: 100%; +} + + +/* ========================================================= + CATEGORY CARD + ========================================================= */ + +.blog-category { + min-width: 0; + padding: 28px; + border: 1px solid rgba(128, 128, 128, 0.25); + 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; +} + +.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); +} + + +/* ========================================================= + CATEGORY HEADER + ========================================================= */ + +.blog-category-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 15px; + padding-bottom: 20px; + margin-bottom: 10px; + border-bottom: 1px solid rgba(128, 128, 128, 0.2); +} + +.blog-category-label { + display: block; + margin-bottom: 7px; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 1.4px; + opacity: 0.6; +} + +.blog-category h3 { + margin: 0; + font-size: 1.25rem; + font-weight: 700; +} + + +/* ========================================================= + VIEW ALL + ========================================================= */ + +.blog-view-all { + flex-shrink: 0; + 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; +} + +.blog-view-all:hover { + opacity: 0.65; +} + + +/* ========================================================= + POSTS + ========================================================= */ + +.blog-posts { + display: flex; + flex-direction: column; +} + + +/* ========================================================= + INDIVIDUAL POST + ========================================================= */ + +.blog-post { + padding: 17px 0; + border-bottom: 1px solid rgba(128, 128, 128, 0.16); +} + +.blog-post:last-child { + border-bottom: none; +} + +.blog-post a { + display: block; + color: inherit; + text-decoration: none; +} + +.blog-post-title { + margin: 0 0 7px; + + font-size: 0.98rem; + line-height: 1.45; + font-weight: 600; + + transition: opacity 0.2s ease; +} + +.blog-post a:hover .blog-post-title { + opacity: 0.65; +} + +.blog-post-date { + font-size: 0.75rem; + opacity: 0.55; +} + + +/* ========================================================= + LOADING / ERROR + ========================================================= */ + +.blog-loading, +.blog-error { + padding: 25px 0; + font-size: 0.85rem; + opacity: 0.6; +} + + +/* ========================================================= + TABLET + ========================================================= */ + +@media (max-width: 1100px) { + + .blog-categories { + grid-template-columns: 1fr; + } + + .blog-category { + width: 100%; + } + +} + + +/* ========================================================= + MOBILE + ========================================================= */ + +@media (max-width: 600px) { + + .latest-blog-section { + padding: 65px 5%; + } + + .latest-blog-section .section-header { + margin-bottom: 35px; + } + + .latest-blog-section .section-header h2 { + font-size: 1.7rem; + } + + .latest-blog-section .section-header p { + font-size: 0.9rem; + line-height: 1.6; + } + + .blog-categories { + display: flex; + flex-direction: column; + gap: 18px; + } + + .blog-category { + padding: 22px; + border-radius: 12px; + } + + .blog-category-header { + align-items: center; + } + + .blog-category h3 { + font-size: 1.1rem; + } + + .blog-view-all { + font-size: 0.72rem; + } + +} \ No newline at end of file diff --git a/expertise/index.html b/expertise/index.html index acab38e..9367dab 100644 --- a/expertise/index.html +++ b/expertise/index.html @@ -548,45 +548,37 @@

diff --git a/index.html b/index.html index 23d8ba2..f75b2a2 100644 --- a/index.html +++ b/index.html @@ -42,6 +42,8 @@ + + @@ -131,6 +133,7 @@
  • Experience
  • Offering
  • Live Works
  • +
  • Latest Blog Posts
  • Services
  • @@ -368,6 +371,91 @@

    ☕ Chai Café App

    + +
    + +
    +

    LATEST BLOG POSTS

    +

    Explore the latest insights, ideas, and technical articles from Vimal Tech.

    +
    + +
    + + +
    +
    +
    + TECHNICAL JOURNEYS +

    Technical Journeys

    +
    + + + View All + +
    + +
    +
    Loading latest posts...
    +
    +
    + + + +
    +
    +
    + SYSTEM DESIGN +

    System Design

    +
    + + + View All + +
    + +
    +
    Loading latest posts...
    +
    +
    + + + +
    +
    +
    + DATA STRUCTURES & ALGORITHMS +

    Data Structures & Algorithms

    +
    + + + View All + +
    + +
    +
    Loading latest posts...
    +
    +
    + +
    + +
    +
    @@ -495,48 +583,39 @@

    - @@ -606,6 +685,7 @@

    + \ No newline at end of file diff --git a/js/blogger.js b/js/blogger.js new file mode 100644 index 0000000..3e5fe98 --- /dev/null +++ b/js/blogger.js @@ -0,0 +1,297 @@ +/* ========================================================= + VIMAL TECH - LATEST BLOG POSTS + Blogger JSONP Integration + ========================================================= */ + +const blogSources = [ + { + container: "technical-journeys-posts", + feed: "https://blog.vimaltech.dev/feeds/posts/default", + limit: 3 + }, + { + container: "system-design-posts", + feed: "https://sd.vimaltech.dev/feeds/posts/default", + limit: 3 + }, + { + container: "dsa-posts", + feed: "https://dsa.vimaltech.dev/feeds/posts/default", + 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); + + if (!container) { + return; + } + + + /* ----------------------------------------- + Global JSONP callback + ----------------------------------------- */ + + window[callbackName] = function (data) { + + try { + + 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); + + }); + + + } catch (error) { + + console.error( + `Error processing Blogger feed:`, + error + ); + + showBlogError(container); + + } + + + /* ----------------------------------------- + Cleanup callback + ----------------------------------------- */ + + delete window[callbackName]; + + if (scriptElement.parentNode) { + scriptElement.parentNode.removeChild(scriptElement); + } + + }; + + + /* ----------------------------------------- + Create JSONP script + ----------------------------------------- */ + + const scriptElement = + document.createElement("script"); + + + scriptElement.src = + `${blog.feed}` + + `?alt=json-in-script` + + `&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}` + ); + + 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" + ); + + return alternateLink?.href || "#"; +} + + +/* ========================================================= + FORMAT DATE + ========================================================= */ + +function formatBlogDate(dateString) { + + if (!dateString) { + return ""; + } + + const date = + new Date(dateString); + + if (Number.isNaN(date.getTime())) { + return ""; + } + + return date.toLocaleDateString( + "en-IN", + { + day: "numeric", + month: "short", + year: "numeric" + } + ); +} + + +/* ========================================================= + HTML ESCAPE + ========================================================= */ + +function escapeHtml(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, """) + .replace(/'/g, "'") + .replace(//g, ">"); +} + + +/* ========================================================= + ERROR MESSAGE + ========================================================= */ + +function showBlogError(container) { + + container.innerHTML = ` +
    + Unable to load posts right now. +
    + `; + +} + + +/* ========================================================= + INITIALIZE + ========================================================= */ + +document.addEventListener( + "DOMContentLoaded", + () => { + + blogSources.forEach( + blog => loadBlogPosts(blog) + ); + + } +); \ No newline at end of file diff --git a/portfolio/index.html b/portfolio/index.html index f8b60e0..322c90e 100644 --- a/portfolio/index.html +++ b/portfolio/index.html @@ -526,43 +526,35 @@

    diff --git a/services/index.html b/services/index.html index aa2db4a..5437373 100644 --- a/services/index.html +++ b/services/index.html @@ -653,45 +653,37 @@