1// Blog pagination 2(function() { 3 // Blog posts data 4 const blogPosts = [ 5 { 6 id: 1, 7 title: "å 3D Vision ç¸éçå «å¹´", 8 date: "2026-03-15", 9 readTime: 6, 10 category: "PhD", 11 tags: ["PhD", "reconstruction", "3D"], 12 excerpt: "Thoughts on 3D computer vision, and the journey of my PhD career.", 13 image: "./resources/images/blog01_teaser.jpg", 14 url: "./blog-posts/my_phd_journey.html", 15 featured: true 16 }, 17 { 18 id: 2, 19 title: "NeRF vs 3DGS: a unified view", 20 date: "2026-01-15", 21 readTime: 9, 22 category: "NeRF", 23 tags: ["NeRF", "math"], 24 excerpt: "Deriving the rendering equations, comparison of volumetric vs. splatting, and where we can expect convergence.", 25 image: "./resources/anti_aliasing_gaussian.gif", 26 // url: "./blog-posts/nerf-vs-3dgs.html" 27 }, 28 ]; 29 30 const POSTS_PER_PAGE = 3; // Show 3 posts per page 31 let currentPage = 1; 32 const totalPages = Math.ceil(blogPosts.length / POSTS_PER_PAGE); 33 34 // Function to format date 35 function formatDate(dateStr) { 36 const options = { year: 'numeric', month: 'short', day: 'numeric' }; 37 return new Date(dateStr).toLocaleDateString(undefined, options); 38 } 39 40 // Function to render blog posts for current page 41 function renderBlogPosts() { 42 const container = document.getElementById('blogPostsContainer'); 43 if (!container) return; 44 45 const start = (currentPage - 1) * POSTS_PER_PAGE; 46 const end = start + POSTS_PER_PAGE; 47 const currentPosts = blogPosts.slice(start, end); 48 49 container.innerHTML = currentPosts.map(post => ` 50 <article class="blog-post-card"> 51 <div class="blog-card-image"> 52 <img src="${post.image}" alt="${post.title}" onerror="this.src='./resources/profile.jpg'"> 53 ${post.featured ? '<span class="featured-badge"><i class="fas fa-star"></i> Featured</span>' : ''} 54 </div> 55 <div class="blog-card-content"> 56 <div class="blog-card-meta"> 57 <span class="blog-date"><i class="far fa-calendar-alt"></i> ${formatDate(post.date)}</span> 58 <span class="blog-reading"><i class="far fa-clock"></i> ${post.readTime} min read</span> 59 <span class="blog-category"><i class="fas fa-folder"></i> ${post.category}</span> 60 </div> 61 <h4 class="blog-card-title">${post.title}</h4> 62 <p class="blog-card-excerpt">${post.excerpt}</p> 63 <div class="blog-card-tags"> 64 ${post.tags.map(tag => `<span class="tag"><i class="fas fa-tag"></i> ${tag}</span>`).join('')} 65 </div> 66 <a href="${post.url}" class="blog-card-link">Read full article <i class="fas fa-arrow-right"></i></a> 67 </div> 68 </article> 69 `).join(''); 70 71 // Update pagination buttons 72 updatePaginationButtons(); 73 } 74 75 // Function to update pagination buttons 76 function updatePaginationButtons() { 77 const prevBtn = document.getElementById('prevPage'); 78 const nextBtn = document.getElementById('nextPage'); 79 const paginationNumbers = document.getElementById('paginationNumbers'); 80 81 if (prevBtn) { 82 prevBtn.disabled = currentPage === 1; 83 } 84 if (nextBtn) { 85 nextBtn.disabled = currentPage === totalPages; 86 } 87 88 // Generate page numbers 89 if (paginationNumbers) { 90 let numbersHTML = ''; 91 92 // Always show first page 93 numbersHTML += `<button class="page-number ${currentPage === 1 ? 'active' : ''}" data-page="1">1</button>`; 94 95 // Show ellipsis if needed 96 if (currentPage > 3) { 97 numbersHTML += `<span class="page-ellipsis">...</span>`; 98 } 99 100 // Show pages around current page 101 for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) { 102 if (i > 1 && i < totalPages) { 103 numbersHTML += `<button class="page-number ${currentPage === i ? 'active' : ''}" data-page="${i}">${i}</button>`; 104 } 105 } 106 107 // Show ellipsis if needed 108 if (currentPage < totalPages - 2) { 109 numbersHTML += `<span class="page-ellipsis">...</span>`; 110 } 111 112 // Always show last page if there is more than one page 113 if (totalPages > 1) { 114 numbersHTML += `<button class="page-number ${currentPage === totalPages ? 'active' : ''}" data-page="${totalPages}">${totalPages}</button>`; 115 } 116 117 paginationNumbers.innerHTML = numbersHTML; 118 119 // Add click handlers to page numbers 120 document.querySelectorAll('.page-number').forEach(btn => { 121 btn.addEventListener('click', () => { 122 const page = parseInt(btn.dataset.page); 123 if (page !== currentPage) { 124 currentPage = page; 125 renderBlogPosts();
126 // Scroll to blog section smoothly 127 document.getElementById('blog').scrollIntoView({ behavior: 'smooth' }); 128 } 129 }); 130 }); 131 } 132 } 133 134 // Initialize pagination 135 function initPagination() { 136 const prevBtn = document.getElementById('prevPage'); 137 const nextBtn = document.getElementById('nextPage'); 138 139 if (prevBtn) { 140 prevBtn.addEventListener('click', () => { 141 if (currentPage > 1) { 142 currentPage--; 143 renderBlogPosts(); 144 document.getElementById('blog').scrollIntoView({ behavior: 'smooth' }); 145 } 146 }); 147 } 148 149 if (nextBtn) { 150 nextBtn.addEventListener('click', () => { 151 if (currentPage < totalPages) { 152 currentPage++; 153 renderBlogPosts(); 154 document.getElementById('blog').scrollIntoView({ behavior: 'smooth' }); 155 } 156 }); 157 } 158 } 159 160 // Load blog posts when section is in view (lazy loading) 161 function observeBlogSection() { 162 const blogSection = document.getElementById('blog'); 163 if (!blogSection) return; 164 165 const observer = new IntersectionObserver((entries) => { 166 entries.forEach(entry => { 167 if (entry.isIntersecting) { 168 renderBlogPosts(); 169 initPagination(); 170 observer.disconnect(); // Stop observing once loaded 171 } 172 }); 173 }, { threshold: 0.1 }); 174 175 observer.observe(blogSection); 176 } 177 178 // Initialize if blog section exists 179 if (document.getElementById('blog')) { 180 // Check if we should load immediately (if section is visible) 181 const blogSection = document.getElementById('blog'); 182 const rect = blogSection.getBoundingClientRect(); 183 if (rect.top < window.innerHeight) { 184 renderBlogPosts(); 185 initPagination(); 186 } else { 187 observeBlogSection(); 188 } 189 } 190})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.