@@ -20,7 +20,7 @@ const sampleProjects = [
2020 description : 'A responsive weather application with beautiful animations and detailed forecasts. Features location-based weather data and interactive charts.' ,
2121 repoUrl : 'https://github.com/example/weather-dashboard' ,
2222 demoUrl : 'https://example.github.io/weather-dashboard/' ,
23- difficulty : 'Intermediate ' ,
23+ difficulty : 'intermediate ' ,
2424 upvotes : 28 ,
2525 hasDemo : true ,
2626 hasReadme : true ,
@@ -59,7 +59,7 @@ const sampleProjects = [
5959 description : 'A simple and intuitive expense tracker app to monitor daily spending, manage budgets, and gain financial insights.' ,
6060 repoUrl : 'https://github.com/DineshPabboju/Expense-Tracker-App' ,
6161 demoUrl : 'https://expense-tracker-app-04.netlify.app/' ,
62- difficulty : 'Intermediate ' ,
62+ difficulty : 'intermediate ' ,
6363 upvotes : 21 ,
6464 hasDemo : true ,
6565 hasReadme : false ,
@@ -97,25 +97,43 @@ const sampleProjects = [
9797
9898 // Store the current projects array
9999 let currentProjects = [ ...sampleProjects ] ;
100+ let selectedTag = null ;
101+
102+ //Store all the unique tags
103+ const allTagSet = new Set ( ) ;
104+ sampleProjects . forEach ( project => {
105+ project . tags . forEach ( tag => allTagSet . add ( tag ) ) ;
106+ } )
107+
108+ const uniqueTags = Array . from ( allTagSet ) ;
100109
101110 // DOM elements
102111 const projectsContainer = document . getElementById ( 'projects-container' ) ;
103112 const loadingElement = document . getElementById ( 'loading' ) ;
104113 const emptyStateElement = document . getElementById ( 'empty-state' ) ;
105114 const difficultyFilter = document . getElementById ( 'difficulty' ) ;
106115 const hasDemoFilter = document . getElementById ( 'has-demo' ) ;
107- const hasReadmeFilter = document . getElementById ( 'has-readme' ) ;
108116 const applyFiltersBtn = document . getElementById ( 'apply-filters' ) ;
109117 const resetFiltersBtn = document . getElementById ( 'reset-filters' ) ;
110118 const searchInput = document . getElementById ( 'search-input' ) ;
111119 const clearSearchBtn = document . getElementById ( 'clear-search' ) ;
120+ const tagFiltersContainer = document . querySelector ( '.tag-filters' ) ;
121+
122+ uniqueTags . forEach ( tag => {
123+ const button = document . createElement ( 'button' ) ;
124+ button . textContent = tag ;
125+ button . classList . add ( 'tag-filter-btn' )
126+ button . dataset . tag = tag
127+ tagFiltersContainer . appendChild ( button )
128+ } )
112129
113130 // Initialize the app
114131 function init ( ) {
115132 setTimeout ( ( ) => {
116133 hideLoading ( ) ;
117134 renderProjects ( currentProjects ) ;
118135 setupEventListeners ( ) ;
136+ initializeTagFilterListener ( ) ;
119137 } , 1000 ) ; // Simulate loading time
120138 }
121139
@@ -143,6 +161,28 @@ const sampleProjects = [
143161 } ) ;
144162 }
145163
164+ function initializeTagFilterListener ( ) {
165+ tagFiltersContainer . addEventListener ( 'click' , ( e ) => {
166+ if ( e . target . classList . contains ( 'tag-filter-btn' ) ) {
167+ const clickedTag = e . target . dataset . tag ;
168+
169+ // Toggle selection
170+ if ( selectedTag === clickedTag ) {
171+ selectedTag = null ;
172+ e . target . classList . remove ( 'active' ) ;
173+ } else {
174+ selectedTag = clickedTag ;
175+ // Remove active from all buttons
176+ document . querySelectorAll ( '.tag-filter-btn' ) . forEach ( btn => btn . classList . remove ( 'active' ) ) ;
177+ e . target . classList . add ( 'active' ) ;
178+ }
179+
180+ applyFilters ( ) ; // Re-apply filters based on tag
181+ }
182+ } ) ;
183+ }
184+
185+
146186 // Render projects
147187 function renderProjects ( projects ) {
148188 if ( projects . length === 0 ) {
@@ -185,6 +225,12 @@ const sampleProjects = [
185225 : ' • <i class="fas fa-exclamation-triangle meta-icon"></i> No README'
186226 }
187227 </div>
228+
229+ <div class="project-tags">
230+ ${ project . tags . map ( ( item , index ) => `
231+ <span class="tag-badge">${ item } </span>
232+ ` ) . join ( '' ) }
233+ </div>
188234
189235 <div class="upvote-section">
190236 ${ project . hasDemo && project . demoUrl
@@ -231,7 +277,6 @@ const sampleProjects = [
231277
232278 const difficulty = difficultyFilter . value ;
233279 const needsDemo = hasDemoFilter . checked ;
234- const needsReadme = hasReadmeFilter . checked ;
235280 const searchTerm = searchInput . value . toLowerCase ( ) . trim ( ) ;
236281
237282 // Apply search filter
@@ -252,8 +297,8 @@ const sampleProjects = [
252297 filtered = filtered . filter ( p => p . hasDemo ) ;
253298 }
254299
255- if ( needsReadme ) {
256- filtered = filtered . filter ( p => p . hasReadme ) ;
300+ if ( selectedTag ) {
301+ filtered = filtered . filter ( project => project . tags . includes ( selectedTag ) ) ;
257302 }
258303
259304 return filtered ;
@@ -293,88 +338,89 @@ const sampleProjects = [
293338 function resetFilters ( ) {
294339 difficultyFilter . value = 'all' ;
295340 hasDemoFilter . checked = false ;
296- hasReadmeFilter . checked = false ;
297341 searchInput . value = '' ;
298342 clearSearchBtn . style . display = 'none' ;
343+ selectedTag = null ;
344+ document . querySelectorAll ( '.tag-filter-btn' ) . forEach ( btn => btn . classList . remove ( 'active' ) ) ;
299345 renderProjects ( sampleProjects ) ;
300346 }
301347
302348 // Make handleUpvote globally available
303349 window . handleUpvote = handleUpvote ;
304350
305- // Start the app
306- document . addEventListener ( "DOMContentLoaded" , init ) ;
307- // Adding m own version and also added a feature where the input field will get clear on clicking the send message button
308- function validateForm ( ) {
309- const name = document . getElementById ( "name" ) . value . trim ( ) ;
310- const lastname = document . getElementById ( "lastname" ) . value . trim ( ) ;
311- const email = document . getElementById ( "email" ) . value . trim ( ) ;
312- const message = document . getElementById ( "message" ) . value . trim ( ) ;
313- if ( ! name || ! lastname || ! email || ! message ) {
314- alert ( "Please fill in all fields." ) ;
315- return false ;
316- }
317- const emailPattern = / ^ [ ^ ] + @ [ ^ ] + \. [ a - z ] { 2 , 3 } $ / ;
318- if ( ! email . match ( emailPattern ) ) {
319- alert ( "Please enter a valid email." ) ;
320- return false ;
321- }
322-
323- // Show the overlay
324- const overlay = document . getElementById ( "message-overlay" ) ;
325- overlay . style . opacity = "1" ;
326- overlay . style . pointerEvents = "auto" ;
327-
328- // Hide the overlay after 3 seconds
329- setTimeout ( ( ) => {
330- overlay . style . opacity = "0" ;
331- overlay . style . pointerEvents = "none" ;
332- } , 3000 ) ;
333-
334- // Clear form
335- document . getElementById ( "contact-form" ) . reset ( ) ;
336-
337- return false ; // Prevent actual form submission
338- }
339-
340- const toggle = document . getElementById ( 'darkModeToggle' ) ;
341- const body = document . body ;
342- const icon = document . getElementById ( 'themeIcon' ) ;
343-
344- // Load preference
345- const savedTheme = localStorage . getItem ( 'theme' ) ;
346- if ( savedTheme === 'dark' ) {
347- body . classList . add ( 'dark-theme' ) ;
348- icon . textContent = '☀️' ; // Sun in dark mode
349- } else {
350- icon . textContent = '🌙' ; // Moon in light mode
351- }
352-
353- toggle . addEventListener ( 'click' , ( ) => {
354- body . classList . toggle ( 'dark-theme' ) ;
355- const theme = body . classList . contains ( 'dark-theme' ) ? 'dark' : 'light' ;
356- localStorage . setItem ( 'theme' , theme ) ;
357-
358- // Update icon
359- icon . textContent = theme === 'dark' ? '☀️' : '🌙' ;
360- } ) ;
361-
362- //Review Section JS
363- const swiper = new Swiper ( ".review-swiper" , {
364- loop : true ,
365- slidesPerView : 1 ,
366- spaceBetween : 20 ,
367- navigation : {
368- nextEl : ".swiper-button-next" ,
369- prevEl : ".swiper-button-prev" ,
370- } ,
371- keyboard : {
372- enabled : true ,
373- } ,
374- mousewheel : {
375- forceToAxis : true ,
376- } ,
377- grabCursor : true ,
378- speed : 600 ,
379- } ) ;
380-
351+ // Start the app
352+ document . addEventListener ( "DOMContentLoaded" , init ) ;
353+ // Adding m own version and also added a feature where the input field will get clear on clicking the send message button
354+ function validateForm ( ) {
355+ const name = document . getElementById ( "name" ) . value . trim ( ) ;
356+ const lastname = document . getElementById ( "lastname" ) . value . trim ( ) ;
357+ const email = document . getElementById ( "email" ) . value . trim ( ) ;
358+ const message = document . getElementById ( "message" ) . value . trim ( ) ;
359+ if ( ! name || ! lastname || ! email || ! message ) {
360+ alert ( "Please fill in all fields." ) ;
361+ return false ;
362+ }
363+ const emailPattern = / ^ [ ^ ] + @ [ ^ ] + \. [ a - z ] { 2 , 3 } $ / ;
364+ if ( ! email . match ( emailPattern ) ) {
365+ alert ( "Please enter a valid email." ) ;
366+ return false ;
367+ }
368+
369+ // Show the overlay
370+ const overlay = document . getElementById ( "message-overlay" ) ;
371+ overlay . style . opacity = "1" ;
372+ overlay . style . pointerEvents = "auto" ;
373+
374+ // Hide the overlay after 3 seconds
375+ setTimeout ( ( ) => {
376+ overlay . style . opacity = "0" ;
377+ overlay . style . pointerEvents = "none" ;
378+ } , 3000 ) ;
379+
380+ // Clear form
381+ document . getElementById ( "contact-form" ) . reset ( ) ;
382+
383+ return false ; // Prevent actual form submission
384+ }
385+
386+ const toggle = document . getElementById ( 'darkModeToggle' ) ;
387+ const body = document . body ;
388+ const icon = document . getElementById ( 'themeIcon' ) ;
389+
390+ // Load preference
391+ const savedTheme = localStorage . getItem ( 'theme' ) ;
392+ if ( savedTheme === 'dark' ) {
393+ body . classList . add ( 'dark-theme' ) ;
394+ icon . textContent = '☀️' ; // Sun in dark mode
395+ } else {
396+ icon . textContent = '🌙' ; // Moon in light mode
397+ }
398+
399+ toggle . addEventListener ( 'click' , ( ) => {
400+ body . classList . toggle ( 'dark-theme' ) ;
401+ const theme = body . classList . contains ( 'dark-theme' ) ? 'dark' : 'light' ;
402+ localStorage . setItem ( 'theme' , theme ) ;
403+
404+ // Update icon
405+ icon . textContent = theme === 'dark' ? '☀️' : '🌙' ;
406+ } ) ;
407+
408+ //Review Section JS
409+ const swiper = new Swiper ( ".review-swiper" , {
410+ loop : true ,
411+ slidesPerView : 1 ,
412+ spaceBetween : 20 ,
413+ navigation : {
414+ nextEl : ".swiper-button-next" ,
415+ prevEl : ".swiper-button-prev" ,
416+ } ,
417+ keyboard : {
418+ enabled : true ,
419+ } ,
420+ mousewheel : {
421+ forceToAxis : true ,
422+ } ,
423+ grabCursor : true ,
424+ speed : 600 ,
425+ } ) ;
426+
0 commit comments