@@ -93,7 +93,7 @@ <h1>{{ name }}'s Blog</h1>
9393 < h2 class ="section-heading ">
9494 < span style ="display: inline-flex; align-items: baseline; gap: 0.5rem; ">
9595 Recent Posts
96- < span
96+ < span id =" total-posts-count "
9797 style ="font-size: 0.65em; font-weight: 500; opacity: 0.6; text-transform: uppercase; font-style: italic; letter-spacing: 0.5px; "> Total
9898 Posts: {{ blogs | length }}</ span >
9999 </ span >
@@ -183,15 +183,18 @@ <h2 class="section-heading">Filter by Technology</h2>
183183
184184 let currentMax = 5 ;
185185 const POSTS_PER_PAGE = 5 ;
186- let currentFilter = 'all' ;
186+ // Multi-select: Set of active filter tags (empty = show all)
187+ let activeFilters = new Set ( ) ;
187188
188189 function updatePostsDisplay ( ) {
189190 let visibleCount = 0 ;
190191
191- // Filter posts
192+ // Filter posts: if no filters active, show all; otherwise OR logic
192193 const matchingPosts = blogPosts . filter ( post => {
193- const postTags = post . getAttribute ( 'data-tags' ) ;
194- return currentFilter === 'all' || postTags . includes ( currentFilter ) ;
194+ if ( activeFilters . size === 0 ) return true ;
195+ const postTags = post . getAttribute ( 'data-tags' ) . split ( ',' ) ;
196+ // OR: show post if ANY of its tags match ANY active filter
197+ return postTags . some ( tag => activeFilters . has ( tag . trim ( ) ) ) ;
195198 } ) ;
196199
197200 // Hide all posts first
@@ -211,32 +214,56 @@ <h2 class="section-heading">Filter by Technology</h2>
211214 } else {
212215 loadMoreBtn . style . display = 'inline-block' ;
213216 }
217+
218+ // Update total posts counter
219+ const totalPostsEl = document . getElementById ( 'total-posts-count' ) ;
220+ if ( totalPostsEl ) {
221+ totalPostsEl . textContent = 'Total Posts: ' + matchingPosts . length ;
222+ }
214223 }
215224
216- // Sync active state across both filter containers
217- function syncFilterState ( clickedTag , sourceContainer ) {
225+ // Sync the visual active state across both filter containers
226+ function syncFilterUI ( ) {
218227 const allContainers = [ tagContainer , mobileTagContainer ] . filter ( Boolean ) ;
219- const selectedTag = clickedTag . getAttribute ( 'data-tag' ) . toLowerCase ( ) ;
220228
221229 allContainers . forEach ( container => {
222230 container . querySelectorAll ( '.filter-tag' ) . forEach ( btn => {
223- btn . classList . remove ( 'active' ) ;
224- if ( btn . getAttribute ( 'data-tag' ) . toLowerCase ( ) === selectedTag ) {
225- btn . classList . add ( 'active' ) ;
231+ const tag = btn . getAttribute ( 'data-tag' ) . toLowerCase ( ) ;
232+ if ( tag === 'all' ) {
233+ // "Show All" is active when no filters are selected
234+ btn . classList . toggle ( 'active' , activeFilters . size === 0 ) ;
235+ } else {
236+ btn . classList . toggle ( 'active' , activeFilters . has ( tag ) ) ;
226237 }
227238 } ) ;
228239 } ) ;
240+ }
241+
242+ // Handle a filter tag click
243+ function handleFilterClick ( clickedBtn ) {
244+ const tag = clickedBtn . getAttribute ( 'data-tag' ) . toLowerCase ( ) ;
245+
246+ if ( tag === 'all' ) {
247+ // "Show All" clicked — clear all filters
248+ activeFilters . clear ( ) ;
249+ } else if ( activeFilters . has ( tag ) ) {
250+ // Already active — deselect it (toggle off)
251+ activeFilters . delete ( tag ) ;
252+ } else {
253+ // Not active — add it (multi-select)
254+ activeFilters . add ( tag ) ;
255+ }
229256
230- currentFilter = selectedTag ;
231- currentMax = POSTS_PER_PAGE ;
257+ currentMax = POSTS_PER_PAGE ; // Reset pagination on filter change
258+ syncFilterUI ( ) ;
232259 updatePostsDisplay ( ) ;
233260 }
234261
235262 // Desktop sidebar filter
236263 if ( tagContainer ) {
237264 tagContainer . addEventListener ( 'click' , function ( e ) {
238265 if ( e . target . matches ( '.filter-tag' ) ) {
239- syncFilterState ( e . target , tagContainer ) ;
266+ handleFilterClick ( e . target ) ;
240267 }
241268 } ) ;
242269 }
@@ -245,23 +272,45 @@ <h2 class="section-heading">Filter by Technology</h2>
245272 if ( mobileTagContainer ) {
246273 mobileTagContainer . addEventListener ( 'click' , function ( e ) {
247274 if ( e . target . matches ( '.filter-tag' ) ) {
248- syncFilterState ( e . target , mobileTagContainer ) ;
275+ handleFilterClick ( e . target ) ;
249276 }
250277 } ) ;
251278 }
252279
253280 // Mobile filter toggle button
254281 if ( mobileFilterToggle && mobileFilterPanel ) {
255- mobileFilterToggle . addEventListener ( 'click' , function ( ) {
282+ mobileFilterToggle . addEventListener ( 'click' , function ( e ) {
283+ e . stopPropagation ( ) ;
256284 const isOpen = mobileFilterPanel . classList . toggle ( 'open' ) ;
257285 mobileFilterToggle . classList . toggle ( 'active' , isOpen ) ;
258286 } ) ;
287+
288+ // Close mobile filter when clicking outside
289+ document . addEventListener ( 'click' , function ( e ) {
290+ if ( ! mobileFilterPanel . contains ( e . target ) && ! mobileFilterToggle . contains ( e . target ) ) {
291+ mobileFilterPanel . classList . remove ( 'open' ) ;
292+ mobileFilterToggle . classList . remove ( 'active' ) ;
293+ }
294+ } ) ;
259295 }
260296
297+ // Load More Posts — scroll to the first newly visible post
261298 if ( loadMoreBtn ) {
262299 loadMoreBtn . addEventListener ( 'click' , function ( ) {
300+ const previousMax = currentMax ;
263301 currentMax += POSTS_PER_PAGE ;
264302 updatePostsDisplay ( ) ;
303+
304+ // Find the first newly revealed post and scroll to it
305+ const visiblePosts = blogPosts . filter ( p => p . style . display !== 'none' ) ;
306+ if ( visiblePosts . length > previousMax ) {
307+ const firstNewPost = visiblePosts [ previousMax ] ;
308+ if ( firstNewPost ) {
309+ setTimeout ( ( ) => {
310+ firstNewPost . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
311+ } , 50 ) ;
312+ }
313+ }
265314 } ) ;
266315 }
267316
0 commit comments