55 < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
66 < title > Awesome Developer Tools</ title >
77 < link rel ="icon " href ="recode-hive.png ">
8- < script src ="https://cdn.tailwindcss .com " > </ script >
8+ < link href ="https://fonts.googleapis .com/icon?family=Material+Icons " rel =" stylesheet " >
99 < link rel ="stylesheet " href ="../styles/devtools.css " />
1010</ head >
11- < body class ="bg-gray-900 text-gray-100 min-h-screen flex flex-col items-center ">
12- < header class ="w-full bg-gradient-to-r from-blue-900 to-green-900 text-white text-center py-4 sm:py-6 lg:py-8 shadow-lg top-0 ">
13- < h1 class ="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold animate-pulse "> Awesome Developer Tools</ h1 >
14- < p class ="mt-2 text-xs sm:text-sm md:text-base lg:text-lg max-w-2xl mx-auto "> A curated, interactive, and responsive collection of tools to supercharge your development workflow.</ p >
15- < div class ="mt-4 flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center items-center px-4 ">
16- < input type ="text " id ="search " placeholder ="Search by name, description, or category... " class ="w-full sm:w-80 md:w-96 p-3 rounded-full bg-gray-800 text-gray-100 border-none focus:ring-2 focus:ring-blue-500 transition-all duration-300 text-sm sm:text-base ">
17- < div class ="custom-select-wrapper ">
18- < select id ="filter-select " class ="w-full p-3 rounded-full bg-gray-800 text-gray-100 border-none focus:ring-2 focus:ring-blue-500 transition-all duration-300 text-sm sm:text-base ">
19- < option value =""> All Categories</ option >
20- <!-- Categories populated by JS -->
21- </ select >
11+ < body >
12+ < header id ="header ">
13+ < div class ="header-container ">
14+ < div class ="header-content ">
15+ < h1 > Awesome Developer Tools</ h1 >
16+ < p > A curated collection of tools to supercharge your development workflow.</ p >
17+ </ div >
18+ < div class ="toggle-switch ">
19+ < input type ="checkbox " id ="theme-toggle " aria-label ="Toggle dark mode ">
20+ < label for ="theme-toggle ">
21+ < div class ="switch-button ">
22+ < span class ="material-icons sun-icon "> wb_sunny</ span >
23+ < span class ="material-icons moon-icon "> brightness_2</ span >
24+ </ div >
25+ </ label >
2226 </ div >
2327 </ div >
2428 </ header >
25- < main class ="w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-8 ">
26- < div class ="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6 " id ="tool-grid ">
27- <!-- Tools will be rendered here by JS -->
29+
30+ < main id ="main ">
31+ < div class ="container ">
32+ <!-- Search and Filter -->
33+ < div class ="search-filter-container ">
34+ < div class ="search-wrapper ">
35+ < input type ="text " id ="search " placeholder ="Search tools... " class ="search-input ">
36+ < span class ="search-icon material-icons "> search</ span >
37+ </ div >
38+
39+ < select id ="filter-select " class ="filter-select ">
40+ < option value =""> All Categories</ option >
41+ </ select >
42+
43+ < button id ="clear-filters " class ="clear-filters-btn hidden ">
44+ < span class ="material-icons "> close</ span >
45+ Clear Filters
46+ </ button >
47+
48+ < div class ="view-toggle ">
49+ < button id ="grid-view-btn " class ="view-btn active " title ="Grid View " aria-label ="Grid View ">
50+ < span class ="material-icons "> dashboard</ span >
51+ </ button >
52+ < button id ="list-view-btn " class ="view-btn " title ="List View " aria-label ="List View ">
53+ < span class ="material-icons "> menu</ span >
54+ </ button >
55+ </ div >
56+ </ div >
57+
58+ <!-- Results Info -->
59+ < div class ="results-info ">
60+ < span id ="tool-count " class ="tool-count-badge "> Showing < strong > 0</ strong > tools</ span >
61+ </ div >
62+
63+ <!-- Tools Grid -->
64+ < div class ="tool-grid " id ="tool-grid ">
65+ <!-- Tools will be rendered here by JS -->
66+ </ div >
67+
68+ < div id ="no-results " class ="no-results hidden ">
69+ < p > No tools found</ p >
70+ </ div >
2871 </ div >
2972 </ main >
30- < button id ="back-to-top " title ="Back to Top " class ="fixed bottom-4 sm:bottom-6 right-4 sm:right-6 bg-blue-900 text-white rounded-full w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center shadow-lg opacity-0 transition-all duration-300 hover:bg-blue-700 hover:scale-110 text-sm sm:text-base "> ↑</ button >
3173 < script >
3274 // List of tools
3375 const tools = [
@@ -86,19 +128,61 @@ <h1 class="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold animate-pulse"
86128 filterSelect . appendChild ( option ) ;
87129 } ) ;
88130
131+ // Category colors for visual variety
132+ const categoryColors = {
133+ 'Analytics Tools' : 'from-purple-500/20 to-purple-600/20' ,
134+ 'Cloud Platforms' : 'from-blue-500/20 to-blue-600/20' ,
135+ 'AI Coding Tools' : 'from-pink-500/20 to-pink-600/20' ,
136+ 'IDEs & Code Editors' : 'from-orange-500/20 to-orange-600/20' ,
137+ 'CLIs & Terminal Tools' : 'from-green-500/20 to-green-600/20' ,
138+ 'DevOps & Infrastructure' : 'from-red-500/20 to-red-600/20'
139+ } ;
140+
141+ const categoryBadges = {
142+ 'Analytics Tools' : 'text-purple-400 bg-purple-500/20' ,
143+ 'Cloud Platforms' : 'text-blue-400 bg-blue-500/20' ,
144+ 'AI Coding Tools' : 'text-pink-400 bg-pink-500/20' ,
145+ 'IDEs & Code Editors' : 'text-orange-400 bg-orange-500/20' ,
146+ 'CLIs & Terminal Tools' : 'text-green-400 bg-green-500/20' ,
147+ 'DevOps & Infrastructure' : 'text-red-400 bg-red-500/20'
148+ } ;
149+
89150 // Function to render tools
90151 function renderTools ( filteredTools ) {
91152 const grid = document . getElementById ( 'tool-grid' ) ;
92- grid . innerHTML = '' ; // Clear existing
153+ const noResults = document . getElementById ( 'no-results' ) ;
154+ const toolCount = document . getElementById ( 'tool-count' ) ;
155+
156+ // Update tool count
157+ if ( filteredTools . length === 0 ) {
158+ toolCount . innerHTML = 'Showing <strong>0</strong> tools' ;
159+ } else {
160+ toolCount . innerHTML = `Showing <strong>${ filteredTools . length } </strong> tools` ;
161+ }
162+
163+ if ( filteredTools . length === 0 ) {
164+ grid . innerHTML = '' ;
165+ noResults . classList . remove ( 'hidden' ) ;
166+ return ;
167+ }
168+
169+ noResults . classList . add ( 'hidden' ) ;
170+ grid . innerHTML = '' ;
171+
93172 filteredTools . forEach ( ( tool , index ) => {
94173 const card = document . createElement ( 'div' ) ;
95- card . className = 'bg-gray-800 rounded-lg p-4 sm:p-6 shadow-xl hover:shadow-2xl hover:scale-105 transform transition-all duration-300 animate-slide-up shine-effect' ;
96- card . style . animationDelay = `${ index * 0.1 } s` ;
174+ card . className = 'tool-card' ;
175+ card . style . animationDelay = `${ index * 0.05 } s` ;
176+
97177 card . innerHTML = `
98- <span class="text-xs uppercase text-gray-500">${ tool . category } </span>
99- <h2 class="text-lg sm:text-xl font-semibold text-blue-400 mt-2">${ tool . name } </h2>
100- <p class="text-gray-300 mt-2 text-sm sm:text-base">${ tool . description } </p>
101- <a href="${ tool . link } " target="_blank" class="inline-block mt-4 bg-green-800 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-all duration-300 text-sm sm:text-base">Visit</a>
178+ <div class="tool-header">
179+ <div>
180+ <h2 class="tool-name">${ tool . name } </h2>
181+ <p class="tool-category">${ tool . category } </p>
182+ </div>
183+ </div>
184+ <p class="tool-description">${ tool . description } </p>
185+ <a href="${ tool . link } " target="_blank" rel="noopener noreferrer" class="tool-link">Visit</a>
102186 ` ;
103187 grid . appendChild ( card ) ;
104188 } ) ;
@@ -122,21 +206,102 @@ <h2 class="text-lg sm:text-xl font-semibold text-blue-400 mt-2">${tool.name}</h2
122206 tool . category . toLowerCase ( ) . includes ( query ) )
123207 ) ;
124208 renderTools ( filtered ) ;
209+ updateClearFiltersButton ( ) ;
125210 }
126211
127- // Back to Top button
128- const backToTop = document . getElementById ( 'back-to-top' ) ;
129- window . addEventListener ( 'scroll' , ( ) => {
130- if ( window . scrollY > 300 ) {
131- backToTop . classList . remove ( 'opacity-0' ) ;
212+ // Update clear filters button visibility
213+ function updateClearFiltersButton ( ) {
214+ const query = searchInput . value . trim ( ) ;
215+ const selectedCategory = filterSelect . value ;
216+ const clearBtn = document . getElementById ( 'clear-filters' ) ;
217+ if ( query !== '' || selectedCategory !== '' ) {
218+ clearBtn . style . display = 'inline-block' ;
132219 } else {
133- backToTop . classList . add ( 'opacity-0' ) ;
220+ clearBtn . style . display = 'none' ;
134221 }
135- } ) ;
222+ }
136223
137- backToTop . addEventListener ( 'click' , ( ) => {
138- window . scrollTo ( { top : 0 , behavior : 'smooth' } ) ;
139- } ) ;
224+ // Clear filters button functionality
225+ const clearFiltersBtn = document . getElementById ( 'clear-filters' ) ;
226+ if ( clearFiltersBtn ) {
227+ clearFiltersBtn . addEventListener ( 'click' , ( ) => {
228+ searchInput . value = '' ;
229+ filterSelect . value = '' ;
230+ renderTools ( tools ) ;
231+ updateClearFiltersButton ( ) ;
232+ } ) ;
233+ }
234+
235+ // View toggle functionality
236+ const gridViewBtn = document . getElementById ( 'grid-view-btn' ) ;
237+ const listViewBtn = document . getElementById ( 'list-view-btn' ) ;
238+ const toolGrid = document . getElementById ( 'tool-grid' ) ;
239+
240+ function setViewMode ( mode ) {
241+ if ( mode === 'list' ) {
242+ toolGrid . classList . remove ( 'grid-view' ) ;
243+ toolGrid . classList . add ( 'list-view' ) ;
244+ listViewBtn . classList . add ( 'active' ) ;
245+ gridViewBtn . classList . remove ( 'active' ) ;
246+ } else {
247+ toolGrid . classList . remove ( 'list-view' ) ;
248+ toolGrid . classList . add ( 'grid-view' ) ;
249+ gridViewBtn . classList . add ( 'active' ) ;
250+ listViewBtn . classList . remove ( 'active' ) ;
251+ }
252+ localStorage . setItem ( 'devtools-view-mode' , mode ) ;
253+ }
254+
255+ // Load saved view mode or default to grid
256+ const savedViewMode = localStorage . getItem ( 'devtools-view-mode' ) || 'grid' ;
257+ setViewMode ( savedViewMode ) ;
258+
259+ // Add event listeners for view toggle buttons
260+ if ( gridViewBtn ) {
261+ gridViewBtn . addEventListener ( 'click' , ( ) => setViewMode ( 'grid' ) ) ;
262+ }
263+
264+ if ( listViewBtn ) {
265+ listViewBtn . addEventListener ( 'click' , ( ) => setViewMode ( 'list' ) ) ;
266+ }
267+
268+ // Dark Mode Toggle - Similar to index.html
269+ const themeToggleCheckbox = document . querySelector ( "#theme-toggle" ) ;
270+
271+ // Function to set the theme
272+ function setTheme ( theme ) {
273+ if ( theme === "dark" ) {
274+ document . body . classList . add ( "dark-mode" ) ;
275+ if ( themeToggleCheckbox ) {
276+ themeToggleCheckbox . checked = true ;
277+ }
278+ } else {
279+ document . body . classList . remove ( "dark-mode" ) ;
280+ if ( themeToggleCheckbox ) {
281+ themeToggleCheckbox . checked = false ;
282+ }
283+ }
284+ localStorage . setItem ( "devtools-theme" , theme ) ;
285+ }
286+
287+ // Load the theme from localStorage or set it to the system default
288+ const savedTheme = localStorage . getItem ( "devtools-theme" ) ;
289+ const defaultTheme = window . matchMedia && window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ? "dark" : "light" ;
290+ setTheme ( savedTheme || defaultTheme ) ;
291+
292+ // Add event listener to toggle checkbox
293+ if ( themeToggleCheckbox ) {
294+ themeToggleCheckbox . addEventListener ( "change" , ( ) => {
295+ const newTheme = themeToggleCheckbox . checked ? "dark" : "light" ;
296+ setTheme ( newTheme ) ;
297+ } ) ;
298+ }
140299 </ script >
300+ </ body >
301+ </ html >
302+ < script >
303+ document . getElementById ( "dynamic-year" ) . textContent = new Date ( ) . getFullYear ( ) ;
304+ </ script >
305+
141306</ body>
142307</ html>
0 commit comments