Json026 commited on
Commit
07a7ea5
·
verified ·
1 Parent(s): 5eb0f94

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +252 -1023
index.html CHANGED
@@ -1,1070 +1,299 @@
1
  <!DOCTYPE html>
2
  <html lang="en" class="dark">
 
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>DSA Portfolio | Data Structures & Algorithms Tracker</title>
7
- <meta name="description" content="A personal portfolio tracking 30+ Data Structures & Algorithms problems solved across topics like Arrays, Graphs, DP, Trees, and more. Built with a focus on clean code and consistent practice.">
8
- <meta name="keywords" content="DSA, Data Structures, Algorithms, LeetCode, Portfolio, Developer, Problem Solving, Coding">
9
- <meta name="author" content="DSA Enthusiast">
10
-
11
- <!-- Open Graph / Social Sharing -->
12
- <meta property="og:title" content="DSA Portfolio | Problem Solving Tracker">
13
- <meta property="og:description" content="Tracking my journey through 30 essential Data Structures & Algorithms problems. Filter by difficulty, topic, or search by name.">
14
- <meta property="og:type" content="website">
15
- <meta property="og:url" content="https://your-domain.com/dsa-portfolio">
16
- <meta property="og:image" content="https://your-domain.com/dsa-portfolio/og-image.png">
17
- <meta property="og:image:width" content="1200">
18
- <meta property="og:image:height" content="630">
19
- <meta property="og:image:alt" content="DSA Portfolio - Problem Solving Tracker with glassmorphism design">
20
-
21
- <!-- Twitter Card -->
22
- <meta name="twitter:card" content="summary_large_image">
23
- <meta name="twitter:title" content="DSA Portfolio | Problem Solving Tracker">
24
- <meta name="twitter:description" content="Tracking 30 essential DSA problems with a clean, modern interface. Filter, search, and track progress.">
25
- <meta name="twitter:image" content="https://your-domain.com/dsa-portfolio/og-image.png">
26
-
27
- <!-- Canonical -->
28
- <link rel="canonical" href="https://your-domain.com/dsa-portfolio">
29
-
30
- <!-- Fonts: Inter for body, JetBrains Mono for code elements -->
31
- <link rel="preconnect" href="https://fonts.googleapis.com">
32
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
33
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
34
-
35
- <!-- Tailwind CSS CDN -->
36
- <script src="https://cdn.tailwindcss.com">
37
- </script>
38
- <script>
39
- tailwind.config = {
40
- darkMode: 'class',
41
- theme: {
42
- extend: {
43
- fontFamily: {
44
- 'sans': ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
45
- 'mono': ['JetBrains Mono', 'Fira Code', 'Consolas', 'Monaco', 'monospace'],
46
- },
47
- animation: {
48
- 'fade-in': 'fadeIn 0.5s ease-out forwards',
49
- 'slide-up': 'slideUp 0.5s ease-out forwards',
50
- 'pulse-soft': 'pulseSoft 3s ease-in-out infinite',
51
- 'shimmer': 'shimmer 2s ease-in-out infinite',
52
- },
53
- keyframes: {
54
- fadeIn: {
55
- '0%': { opacity: '0' },
56
- '100%': { opacity: '1' },
57
- },
58
- slideUp: {
59
- '0%': { opacity: '0', transform: 'translateY(20px)' },
60
- '100%': { opacity: '1', transform: 'translateY(0)' },
61
- },
62
- pulseSoft: {
63
- '0%, 100%': { opacity: '0.5' },
64
- '50%': { opacity: '0.8' },
65
- },
66
- shimmer: {
67
- '0%': { backgroundPosition: '-200% 0' },
68
- '100%': { backgroundPosition: '200% 0' },
69
- },
70
- },
71
- },
72
- },
73
- };
74
- </script>
75
-
76
- <style type="text/tailwindcss">
77
- /* Custom scrollbar */
78
- ::-webkit-scrollbar {
79
- width: 6px;
80
- height: 6px;
81
- }
82
- ::-webkit-scrollbar-track {
83
- background: transparent;
84
- }
85
- ::-webkit-scrollbar-thumb {
86
- background: rgba(148, 163, 184, 0.4);
87
- border-radius: 9999px;
88
- }
89
- ::-webkit-scrollbar-thumb:hover {
90
- background: rgba(148, 163, 184, 0.6);
91
- }
92
- .dark ::-webkit-scrollbar-thumb {
93
- background: rgba(71, 85, 105, 0.5);
94
- }
95
- .dark ::-webkit-scrollbar-thumb:hover {
96
- background: rgba(71, 85, 105, 0.7);
97
- }
98
 
99
- /* Smooth theme transitions */
100
- html {
101
- transition: background-color 0.4s ease, color 0.4s ease;
102
- }
103
- html * {
104
- transition: background-color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
105
- }
106
 
107
- /* Card hover glow */
108
- .card-glow {
109
- transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
110
- box-shadow 0.3s ease,
111
- border-color 0.3s ease;
112
- }
113
- .card-glow:hover {
114
- transform: translateY(-6px);
115
- box-shadow: 0 20px 50px -12px rgba(0, 0, 0, 0.25),
116
- 0 0 0 1px rgba(255, 255, 255, 0.2) inset;
117
- }
118
- .dark .card-glow:hover {
119
- box-shadow: 0 20px 50px -12px rgba(0, 0, 0, 0.5),
120
- 0 0 40px -10px rgba(99, 102, 241, 0.25),
121
- 0 0 0 1px rgba(255, 255, 255, 0.15) inset;
122
- }
123
 
124
- /* Filter chip active state */
125
- .chip-active {
126
- box-shadow: 0 0 20px -5px rgba(99, 102, 241, 0.5);
127
- transform: scale(1.03);
128
- }
129
 
130
- /* Skeleton loading */
131
- .skeleton {
132
- background: linear-gradient(90deg,
133
- transparent 0%,
134
- rgba(148, 163, 184, 0.2) 50%,
135
- transparent 100%);
136
- background-size: 200% 100%;
137
- animation: shimmer 1.5s ease-in-out infinite;
138
- }
139
- .dark .skeleton {
140
- background: linear-gradient(90deg,
141
- transparent 0%,
142
- rgba(71, 85, 105, 0.3) 50%,
143
- transparent 100%);
144
- background-size: 200% 100%;
 
 
145
  }
146
- </style>
 
 
147
  </head>
148
 
149
- <body class="font-sans antialiased min-h-screen relative overflow-x-hidden
150
- bg-gradient-to-br from-slate-50 via-gray-100 to-slate-200
151
- dark:from-gray-950 dark:via-slate-900 dark:to-gray-950
152
- text-gray-800 dark:text-gray-100 transition-colors duration-500">
153
-
154
- <!-- ==================== BACKGROUND ORBS ==================== -->
155
- <div aria-hidden="true" class="fixed inset-0 pointer-events-none z-0 overflow-hidden">
156
- <!-- Orb 1 - Top Right -->
157
- <div class="absolute -top-32 -right-32 w-[600px] h-[600px] rounded-full
158
- bg-blue-400/15 dark:bg-indigo-500/10 blur-[120px]
159
- animate-pulse-soft transition-colors duration-700">
160
- </div>
161
- <!-- Orb 2 - Bottom Left -->
162
- <div class="absolute -bottom-40 -left-40 w-[500px] h-[500px] rounded-full
163
- bg-purple-400/12 dark:bg-purple-600/8 blur-[100px]
164
- animate-pulse-soft transition-colors duration-700"
165
- style="animation-delay: 1.5s;">
166
- </div>
167
- <!-- Orb 3 - Center subtle -->
168
- <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
169
- w-[700px] h-[700px] rounded-full
170
- bg-teal-300/8 dark:bg-cyan-500/5 blur-[150px]
171
- animate-pulse-soft transition-colors duration-700"
172
- style="animation-delay: 0.8s;">
173
- </div>
174
- <!-- Orb 4 - Top Left accent -->
175
- <div class="absolute top-20 left-10 w-[300px] h-[300px] rounded-full
176
- bg-amber-300/10 dark:bg-orange-500/6 blur-[80px]
177
- animate-pulse-soft transition-colors duration-700"
178
- style="animation-delay: 2.2s;">
179
- </div>
180
  </div>
181
 
182
- <!-- ==================== HEADER / NAVBAR ==================== -->
183
- <header class="relative z-20 backdrop-blur-2xl bg-white/40 dark:bg-gray-900/40
184
- border-b border-gray-200/40 dark:border-gray-700/30
185
- sticky top-0 shadow-sm dark:shadow-lg dark:shadow-black/10"
186
- role="banner">
187
- <nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-between"
188
- aria-label="Main navigation">
189
- <!-- Logo / Brand -->
190
- <div class="flex items-center gap-3">
191
- <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500 to-purple-600
192
- flex items-center justify-center shadow-lg shadow-indigo-500/25
193
- dark:shadow-indigo-500/30"
194
- aria-hidden="true">
195
- <svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
196
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
197
- d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
198
- </svg>
199
- </div>
200
- <div>
201
- <h1 class="text-lg font-bold tracking-tight text-gray-900 dark:text-white leading-tight">
202
- DSA Portfolio
203
- </h1>
204
- <p class="text-xs text-gray-500 dark:text-gray-400 font-mono tracking-wide">
205
- &lt;problem-solver /&gt;
206
- </p>
207
- </div>
208
- </div>
209
 
210
- <!-- Dark/Light Toggle -->
211
- <button id="themeToggle"
212
- class="relative w-14 h-7 rounded-full
213
- bg-gray-200 dark:bg-gray-700
214
- border border-gray-300 dark:border-gray-600
215
- cursor-pointer
216
- focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2
217
- dark:focus-visible:ring-offset-gray-900
218
- transition-all duration-300 group"
219
- aria-label="Toggle dark/light mode"
220
- title="Toggle theme"
221
- type="button"
222
- role="switch"
223
- aria-checked="true">
224
- <!-- Toggle thumb -->
225
- <span id="themeThumb"
226
- class="absolute top-0.5 left-0.5 w-6 h-6 rounded-full
227
- bg-white dark:bg-gray-800
228
- shadow-md dark:shadow-lg
229
- flex items-center justify-center
230
- transition-transform duration-300 ease-in-out
231
- translate-x-7">
232
- <!-- Sun icon (visible in dark mode, when thumb is on the right) -->
233
- <svg id="sunIcon" class="w-3.5 h-3.5 text-amber-500 opacity-0 scale-0 transition-all duration-300"
234
- fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
235
- <circle cx="12" cy="12" r="5" stroke-width="2" />
236
- <path stroke-linecap="round" stroke-width="2" d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
237
- </svg>
238
- <!-- Moon icon (visible in light mode, when thumb is on the left) -->
239
- <svg id="moonIcon" class="w-3.5 h-3.5 text-indigo-400 opacity-100 scale-100 transition-all duration-300 absolute"
240
- fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
241
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
242
- d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
243
- </svg>
244
- </span>
245
- </button>
246
- </nav>
247
- </header>
248
-
249
- <!-- ==================== MAIN CONTENT ==================== -->
250
- <main class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-12" role="main">
251
-
252
- <!-- ========== HERO / STATS SECTION ========== -->
253
- <section class="mb-10 animate-fade-in" aria-labelledby="stats-heading">
254
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 items-stretch">
255
- <!-- Intro Card -->
256
- <div class="lg:col-span-2 backdrop-blur-xl bg-white/50 dark:bg-gray-800/40
257
- rounded-2xl p-6 sm:p-8
258
- border border-gray-200/50 dark:border-gray-700/40
259
- shadow-lg dark:shadow-2xl dark:shadow-black/20"
260
- aria-label="Portfolio introduction">
261
- <span class="inline-block px-3 py-1 text-xs font-semibold font-mono
262
- rounded-full bg-indigo-100 dark:bg-indigo-900/50
263
- text-indigo-700 dark:text-indigo-300
264
- border border-indigo-200 dark:border-indigo-700/50
265
- mb-4">
266
- 📊 DSA TRACKER
267
- </span>
268
- <h2 id="stats-heading" class="text-2xl sm:text-3xl lg:text-4xl font-bold
269
- text-gray-900 dark:text-white leading-tight mb-3">
270
- Mastering Data Structures <br class="hidden sm:block">
271
- <span class="bg-gradient-to-r from-indigo-600 to-purple-600 dark:from-indigo-400 dark:to-purple-400
272
- bg-clip-text text-transparent">
273
- & Algorithms
274
- </span>
275
- </h2>
276
- <p class="text-gray-600 dark:text-gray-400 max-w-xl leading-relaxed text-sm sm:text-base">
277
- A curated collection of 30 essential DSA problems. Track progress,
278
- filter by topic or difficulty, and build consistent problem-solving habits.
279
- </p>
280
- </div>
281
-
282
- <!-- Progress Card -->
283
- <div class="backdrop-blur-xl bg-white/50 dark:bg-gray-800/40
284
- rounded-2xl p-6 sm:p-8
285
- border border-gray-200/50 dark:border-gray-700/40
286
- shadow-lg dark:shadow-2xl dark:shadow-black/20
287
- flex flex-col justify-center"
288
- aria-label="Progress statistics">
289
- <p class="text-sm font-medium text-gray-500 dark:text-gray-400 font-mono mb-2">
290
- PROGRAMS SOLVED
291
- </p>
292
- <div class="flex items-baseline gap-2 mb-3">
293
- <span id="solvedCount" class="text-5xl sm:text-6xl font-extrabold
294
- bg-gradient-to-r from-emerald-500 to-teal-500
295
- dark:from-emerald-400 dark:to-teal-400
296
- bg-clip-text text-transparent">
297
- 0
298
- </span>
299
- <span class="text-2xl font-light text-gray-400 dark:text-gray-500">/ 30</span>
300
- </div>
301
- <!-- Progress Bar -->
302
- <div class="w-full h-3 rounded-full bg-gray-200/70 dark:bg-gray-700/60
303
- overflow-hidden relative"
304
- role="progressbar"
305
- aria-valuenow="0"
306
- aria-valuemin="0"
307
- aria-valuemax="30"
308
- aria-label="Solved problems progress">
309
- <div id="progressFill"
310
- class="h-full rounded-full transition-all duration-700 ease-out
311
- bg-gradient-to-r from-emerald-400 via-teal-400 to-cyan-400
312
- dark:from-emerald-500 dark:via-teal-500 dark:to-cyan-500
313
- shadow-inner"
314
- style="width: 0%;">
315
- </div>
316
- </div>
317
- <p id="progressLabel" class="text-xs text-gray-500 dark:text-gray-400 mt-2 font-mono text-right">
318
- 0% complete
319
- </p>
320
- </div>
321
- </div>
322
- </section>
323
-
324
- <!-- ========== SEARCH & FILTER SECTION ========== -->
325
- <section class="mb-8 space-y-5 animate-slide-up" style="animation-delay: 0.1s;"
326
- aria-labelledby="filter-heading">
327
- <h3 id="filter-heading" class="sr-only">Search and Filter Problems</h3>
328
-
329
- <!-- Search Bar -->
330
- <div class="relative group max-w-2xl">
331
- <div class="absolute inset-0 rounded-2xl bg-gradient-to-r from-indigo-500/20 to-purple-500/20
332
- dark:from-indigo-400/25 dark:to-purple-400/25
333
- blur-lg opacity-0 group-focus-within:opacity-100 transition-opacity duration-400
334
- pointer-events-none"
335
- aria-hidden="true">
336
- </div>
337
- <div class="relative backdrop-blur-xl bg-white/60 dark:bg-gray-800/50
338
- rounded-2xl border border-gray-200/60 dark:border-gray-700/50
339
- shadow-md dark:shadow-lg
340
- group-focus-within:border-indigo-400/60 dark:group-focus-within:border-indigo-500/50
341
- transition-all duration-300">
342
- <svg class="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5
343
- text-gray-400 dark:text-gray-500
344
- group-focus-within:text-indigo-500 dark:group-focus-within:text-indigo-400
345
- transition-colors duration-300"
346
- fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
347
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
348
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
349
- </svg>
350
- <input type="text" id="searchInput"
351
- class="w-full pl-12 pr-4 py-3.5 sm:py-4
352
- bg-transparent
353
- text-gray-900 dark:text-white
354
- placeholder-gray-400 dark:placeholder-gray-500
355
- font-mono text-sm sm:text-base
356
- rounded-2xl
357
- focus:outline-none
358
- transition-all duration-300"
359
- placeholder='Search problems... (e.g. "two sum")'
360
- aria-label="Search problems by name"
361
- autocomplete="off"
362
- />
363
- <button id="clearSearch"
364
- class="absolute right-3 top-1/2 -translate-y-1/2
365
- w-7 h-7 rounded-full
366
- bg-gray-300/60 dark:bg-gray-600/60
367
- flex items-center justify-center
368
- text-gray-600 dark:text-gray-300
369
- hover:bg-gray-400/70 dark:hover:bg-gray-500/70
370
- transition-all duration-200
371
- opacity-0 scale-75 pointer-events-none"
372
- aria-label="Clear search"
373
- type="button"
374
- title="Clear search">
375
- <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
376
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12" />
377
- </svg>
378
- </button>
379
- </div>
380
- </div>
381
 
382
- <!-- Filter Chips -->
383
- <div class="space-y-3">
384
- <!-- Difficulty Chips -->
385
- <div class="flex flex-wrap items-center gap-2">
386
- <span class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider
387
- font-mono mr-1"
388
- aria-hidden="true">
389
- Difficulty:
390
- </span>
391
- <div id="difficultyChips" class="flex flex-wrap gap-2" role="group"
392
- aria-label="Filter by difficulty">
393
- <!-- Dynamically populated -->
394
- </div>
395
- </div>
396
 
397
- <!-- Topic Chips -->
398
- <div class="flex flex-wrap items-center gap-2">
399
- <span class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider
400
- font-mono mr-1"
401
- aria-hidden="true">
402
- Topics:
403
- </span>
404
- <div id="topicChips" class="flex flex-wrap gap-2" role="group" aria-label="Filter by topic">
405
- <!-- Dynamically populated -->
406
- </div>
407
- </div>
408
 
409
- <!-- Active Filters & Clear -->
410
- <div id="activeFiltersInfo" class="flex items-center gap-3 flex-wrap opacity-0 transition-opacity duration-300">
411
- <span id="resultCount" class="text-sm text-gray-500 dark:text-gray-400 font-mono"></span>
412
- <button id="clearAllFilters"
413
- class="text-xs font-semibold px-3 py-1.5 rounded-full
414
- bg-red-100 dark:bg-red-900/40
415
- text-red-600 dark:text-red-400
416
- border border-red-200 dark:border-red-800/50
417
- hover:bg-red-200 dark:hover:bg-red-800/60
418
- transition-all duration-200
419
- hidden"
420
- type="button">
421
- ✕ Clear All Filters
422
- </button>
423
  </div>
424
- </div>
425
- </section>
426
-
427
- <!-- ========== PROBLEM CARDS GRID ========== -->
428
- <section aria-labelledby="problems-heading">
429
- <div class="flex items-center justify-between mb-5">
430
- <h3 id="problems-heading" class="text-lg font-semibold text-gray-800 dark:text-gray-200
431
- flex items-center gap-2">
432
- <span class="w-2 h-2 rounded-full bg-indigo-500 animate-pulse-soft" aria-hidden="true"></span>
433
- Problems
434
- </h3>
435
- <span id="showingCount" class="text-xs text-gray-500 dark:text-gray-400 font-mono"></span>
436
- </div>
437
 
438
- <!-- Cards Grid -->
439
- <div id="cardsGrid"
440
- class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-5"
441
- role="list"
442
- aria-label="Problem cards">
443
- <!-- Dynamically populated -->
444
  </div>
445
 
446
- <!-- Empty State -->
447
- <div id="emptyState"
448
- class="hidden flex-col items-center justify-center py-16 text-center"
449
- aria-live="polite">
450
- <div class="w-20 h-20 rounded-full bg-gray-200/60 dark:bg-gray-700/50
451
- flex items-center justify-center mb-4"
452
- aria-hidden="true">
453
- <svg class="w-10 h-10 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
454
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
455
- d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
456
- </svg>
457
- </div>
458
- <p class="text-gray-500 dark:text-gray-400 text-lg font-medium mb-1">No problems found</p>
459
- <p class="text-gray-400 dark:text-gray-500 text-sm">Try adjusting your filters or search query.</p>
460
- <button id="resetEmptyState"
461
- class="mt-4 px-4 py-2 rounded-xl
462
- bg-indigo-100 dark:bg-indigo-900/50
463
- text-indigo-700 dark:text-indigo-300
464
- font-medium text-sm
465
- hover:bg-indigo-200 dark:hover:bg-indigo-800/60
466
- transition-all duration-200"
467
- type="button">
468
- Reset All Filters
469
- </button>
470
  </div>
471
 
472
- <!-- Loading State -->
473
- <div id="loadingState"
474
- class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-5"
475
- aria-label="Loading problems">
476
- <!-- Skeleton cards -->
477
- <div class="rounded-2xl p-6 h-40 skeleton" aria-hidden="true"></div>
478
- <div class="rounded-2xl p-6 h-40 skeleton" aria-hidden="true" style="animation-delay: 0.1s;"></div>
479
- <div class="rounded-2xl p-6 h-40 skeleton" aria-hidden="true" style="animation-delay: 0.2s;"></div>
480
- <div class="rounded-2xl p-6 h-40 skeleton hidden md:block" aria-hidden="true" style="animation-delay: 0.3s;"></div>
481
- <div class="rounded-2xl p-6 h-40 skeleton hidden md:block" aria-hidden="true" style="animation-delay: 0.4s;"></div>
482
- <div class="rounded-2xl p-6 h-40 skeleton hidden lg:block" aria-hidden="true" style="animation-delay: 0.5s;"></div>
483
  </div>
484
- </section>
485
- </main>
486
-
487
- <!-- ==================== FOOTER ==================== -->
488
- <footer class="relative z-10 border-t border-gray-200/40 dark:border-gray-700/30
489
- backdrop-blur-xl bg-white/30 dark:bg-gray-900/30 mt-12"
490
- role="contentinfo">
491
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6
492
- flex flex-col sm:flex-row items-center justify-between gap-3 text-sm
493
- text-gray-500 dark:text-gray-400 font-mono">
494
- <p>&copy; <span id="currentYear">2024</span> DSA Portfolio. Built with precision.</p>
495
- <p class="flex items-center gap-1.5">
496
- <span class="inline-block w-2 h-2 rounded-full bg-emerald-400 animate-pulse-soft" aria-hidden="true"></span>
497
- Tracking progress, one problem at a time.
498
- </p>
499
- </div>
500
- </footer>
501
-
502
- <!-- ==================== SCRIPTS ==================== -->
503
- <script>
504
- (function() {
505
- // ─────────────────────────────────────
506
- // DOM REFERENCES
507
- // ─────────────────────────────────────
508
- const searchInput = document.getElementById('searchInput');
509
- const clearSearchBtn = document.getElementById('clearSearch');
510
- const difficultyChipsContainer = document.getElementById('difficultyChips');
511
- const topicChipsContainer = document.getElementById('topicChips');
512
- const cardsGrid = document.getElementById('cardsGrid');
513
- const loadingState = document.getElementById('loadingState');
514
- const emptyState = document.getElementById('emptyState');
515
- const solvedCountEl = document.getElementById('solvedCount');
516
- const progressFill = document.getElementById('progressFill');
517
- const progressLabel = document.getElementById('progressLabel');
518
- const showingCount = document.getElementById('showingCount');
519
- const resultCount = document.getElementById('resultCount');
520
- const activeFiltersInfo = document.getElementById('activeFiltersInfo');
521
- const clearAllFiltersBtn = document.getElementById('clearAllFilters');
522
- const resetEmptyStateBtn = document.getElementById('resetEmptyState');
523
- const themeToggle = document.getElementById('themeToggle');
524
- const themeThumb = document.getElementById('themeThumb');
525
- const sunIcon = document.getElementById('sunIcon');
526
- const moonIcon = document.getElementById('moonIcon');
527
- const htmlEl = document.documentElement;
528
- const currentYearSpan = document.getElementById('currentYear');
529
-
530
- // ─────────────────────────────────────
531
- // STATE
532
- // ─────────────────────────────────────
533
- let allProblems = [];
534
- let activeDifficultyFilters = new Set(); // e.g., {"Easy", "Medium"}
535
- let activeTopicFilters = new Set(); // e.g., {"Arrays", "Graphs"}
536
- let searchQuery = '';
537
- const TOTAL_PROBLEMS = 30;
538
-
539
- // ─────────────────────────────────────
540
- // THEME MANAGEMENT
541
- // ─────────────────────────────────────
542
- function getSystemTheme() {
543
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
544
- }
545
 
546
- function getStoredTheme() {
547
- try {
548
- return localStorage.getItem('dsa-portfolio-theme');
549
- } catch (e) {
550
- return null;
551
- }
552
- }
553
 
554
- function applyTheme(theme) {
555
- if (theme === 'dark') {
556
- htmlEl.classList.add('dark');
557
- themeToggle.setAttribute('aria-checked', 'true');
558
- themeThumb.style.transform = 'translateX(28px)';
559
- sunIcon.style.opacity = '0';
560
- sunIcon.style.transform = 'scale(0)';
561
- moonIcon.style.opacity = '1';
562
- moonIcon.style.transform = 'scale(1)';
563
- } else {
564
- htmlEl.classList.remove('dark');
565
- themeToggle.setAttribute('aria-checked', 'false');
566
- themeThumb.style.transform = 'translateX(0px)';
567
- sunIcon.style.opacity = '1';
568
- sunIcon.style.transform = 'scale(1)';
569
- moonIcon.style.opacity = '0';
570
- moonIcon.style.transform = 'scale(0)';
571
- }
572
- }
573
 
574
- function toggleTheme() {
575
- const current = htmlEl.classList.contains('dark') ? 'dark' : 'light';
576
- const next = current === 'dark' ? 'light' : 'dark';
577
- applyTheme(next);
578
- try {
579
- localStorage.setItem('dsa-portfolio-theme', next);
580
- } catch (e) {
581
- // localStorage unavailable
582
- }
583
- }
584
 
585
- function initTheme() {
586
- const stored = getStoredTheme();
587
- const theme = stored || getSystemTheme();
588
- applyTheme(theme);
589
-
590
- // Listen for system changes if no stored preference
591
- if (!stored) {
592
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
593
- if (!getStoredTheme()) {
594
- applyTheme(e.matches ? 'dark' : 'light');
595
- }
596
- });
597
- }
598
- }
599
 
600
- themeToggle.addEventListener('click', toggleTheme);
601
-
602
- // ─────────────────────────────────────
603
- // DIFFICULTY COLOR MAP
604
- // ─────────────────────────────────────
605
- function getDifficultyColors(difficulty) {
606
- const map = {
607
- 'Easy': {
608
- bg: 'bg-emerald-100 dark:bg-emerald-900/40',
609
- text: 'text-emerald-700 dark:text-emerald-300',
610
- border: 'border-emerald-200 dark:border-emerald-700/50',
611
- dot: 'bg-emerald-500',
612
- },
613
- 'Medium': {
614
- bg: 'bg-amber-100 dark:bg-amber-900/40',
615
- text: 'text-amber-700 dark:text-amber-300',
616
- border: 'border-amber-200 dark:border-amber-700/50',
617
- dot: 'bg-amber-500',
618
- },
619
- 'Hard': {
620
- bg: 'bg-red-100 dark:bg-red-900/40',
621
- text: 'text-red-700 dark:text-red-300',
622
- border: 'border-red-200 dark:border-red-700/50',
623
- dot: 'bg-red-500',
624
- },
625
- };
626
- return map[difficulty] || {
627
- bg: 'bg-gray-100 dark:bg-gray-800',
628
- text: 'text-gray-700 dark:text-gray-300',
629
- border: 'border-gray-200 dark:border-gray-700',
630
- dot: 'bg-gray-500',
631
- };
632
- }
633
 
634
- // ─────────────────────────────────────
635
- // RENDER FUNCTIONS
636
- // ─────────────────────────────────────
637
- function renderDifficultyChips() {
638
- const difficulties = ['All', 'Easy', 'Medium', 'Hard'];
639
- difficultyChipsContainer.innerHTML = '';
640
-
641
- difficulties.forEach((diff, index) => {
642
- const chip = document.createElement('button');
643
- chip.type = 'button';
644
- chip.textContent = diff;
645
- chip.dataset.difficulty = diff;
646
-
647
- const isAll = diff === 'All';
648
- const isActive = isAll ?
649
- activeDifficultyFilters.size === 0 :
650
- activeDifficultyFilters.has(diff);
651
-
652
- const baseClasses =
653
- 'px-3.5 py-1.5 rounded-full text-sm font-medium border transition-all duration-250 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-gray-900';
654
-
655
- let activeClasses, inactiveClasses;
656
- if (isAll) {
657
- activeClasses =
658
- 'bg-indigo-600 dark:bg-indigo-500 text-white border-indigo-600 dark:border-indigo-500 shadow-md chip-active';
659
- inactiveClasses =
660
- 'bg-white/50 dark:bg-gray-700/50 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600/60 backdrop-blur-sm';
661
- } else if (diff === 'Easy') {
662
- activeClasses =
663
- 'bg-emerald-500 text-white border-emerald-500 shadow-md chip-active shadow-emerald-500/30';
664
- inactiveClasses =
665
- 'bg-white/50 dark:bg-gray-700/50 text-emerald-700 dark:text-emerald-300 border-emerald-300 dark:border-emerald-700/50 hover:bg-emerald-50 dark:hover:bg-emerald-900/30 backdrop-blur-sm';
666
- } else if (diff === 'Medium') {
667
- activeClasses =
668
- 'bg-amber-500 text-white border-amber-500 shadow-md chip-active shadow-amber-500/30';
669
- inactiveClasses =
670
- 'bg-white/50 dark:bg-gray-700/50 text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-700/50 hover:bg-amber-50 dark:hover:bg-amber-900/30 backdrop-blur-sm';
671
- } else if (diff === 'Hard') {
672
- activeClasses =
673
- 'bg-red-500 text-white border-red-500 shadow-md chip-active shadow-red-500/30';
674
- inactiveClasses =
675
- 'bg-white/50 dark:bg-gray-700/50 text-red-700 dark:text-red-300 border-red-300 dark:border-red-700/50 hover:bg-red-50 dark:hover:bg-red-900/30 backdrop-blur-sm';
676
- }
677
-
678
- chip.className = `${baseClasses} ${isActive ? activeClasses : inactiveClasses}`;
679
- chip.setAttribute('aria-pressed', isActive ? 'true' : 'false');
680
- chip.style.animationDelay = `${index * 0.05}s`;
681
- chip.classList.add('animate-fade-in');
682
-
683
- chip.addEventListener('click', () => {
684
- if (isAll) {
685
- // Clicking "All" clears all difficulty filters
686
- activeDifficultyFilters.clear();
687
- } else {
688
- // Remove "All" behavior: toggle specific difficulty
689
- if (activeDifficultyFilters.has(diff)) {
690
- activeDifficultyFilters.delete(diff);
691
- } else {
692
- activeDifficultyFilters.add(diff);
693
- }
694
- }
695
- renderDifficultyChips();
696
- applyFiltersAndRender();
697
- });
698
-
699
- difficultyChipsContainer.appendChild(chip);
700
- });
701
- }
702
 
703
- function renderTopicChips() {
704
- // Get unique topics
705
- const topicsSet = new Set();
706
- allProblems.forEach(p => topicsSet.add(p.topic));
707
- const topics = ['All Topics', ...Array.from(topicsSet).sort()];
708
-
709
- topicChipsContainer.innerHTML = '';
710
-
711
- topics.forEach((topic, index) => {
712
- const chip = document.createElement('button');
713
- chip.type = 'button';
714
- chip.textContent = topic;
715
- chip.dataset.topic = topic;
716
-
717
- const isAll = topic === 'All Topics';
718
- const isActive = isAll ?
719
- activeTopicFilters.size === 0 :
720
- activeTopicFilters.has(topic);
721
-
722
- const baseClasses =
723
- 'px-3 py-1.5 rounded-full text-sm font-medium border transition-all duration-250 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-gray-900';
724
-
725
- const activeClasses =
726
- 'bg-purple-600 dark:bg-purple-500 text-white border-purple-600 dark:border-purple-500 shadow-md chip-active shadow-purple-500/25';
727
- const inactiveClasses =
728
- 'bg-white/50 dark:bg-gray-700/50 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600/60 backdrop-blur-sm';
729
-
730
- chip.className = `${baseClasses} ${isActive ? activeClasses : inactiveClasses}`;
731
- chip.setAttribute('aria-pressed', isActive ? 'true' : 'false');
732
- chip.style.animationDelay = `${index * 0.04}s`;
733
- chip.classList.add('animate-fade-in');
734
-
735
- chip.addEventListener('click', () => {
736
- if (isAll) {
737
- activeTopicFilters.clear();
738
- } else {
739
- if (activeTopicFilters.has(topic)) {
740
- activeTopicFilters.delete(topic);
741
- } else {
742
- activeTopicFilters.add(topic);
743
- }
744
- }
745
- renderTopicChips();
746
- applyFiltersAndRender();
747
- });
748
-
749
- topicChipsContainer.appendChild(chip);
750
- });
751
- }
752
 
753
- function getFilteredProblems() {
754
- return allProblems.filter(problem => {
755
- // Difficulty filter
756
- if (activeDifficultyFilters.size > 0 && !activeDifficultyFilters.has(problem.difficulty)) {
757
- return false;
758
- }
759
- // Topic filter
760
- if (activeTopicFilters.size > 0 && !activeTopicFilters.has(problem.topic)) {
761
- return false;
762
- }
763
- // Search filter
764
- if (searchQuery.trim() && !problem.title.toLowerCase().includes(searchQuery.toLowerCase())) {
765
- return false;
766
- }
767
- return true;
768
- });
769
- }
770
 
771
- function renderProblemCards(problems) {
772
- cardsGrid.innerHTML = '';
773
-
774
- if (problems.length === 0) {
775
- cardsGrid.classList.add('hidden');
776
- emptyState.classList.remove('hidden');
777
- emptyState.classList.add('flex');
778
- } else {
779
- cardsGrid.classList.remove('hidden');
780
- emptyState.classList.add('hidden');
781
- emptyState.classList.remove('flex');
782
- }
783
-
784
- problems.forEach((problem, index) => {
785
- const diffColors = getDifficultyColors(problem.difficulty);
786
- const card = document.createElement('a');
787
- card.href = problem.url;
788
- card.target = '_blank';
789
- card.rel = 'noopener noreferrer';
790
- card.className = `
791
- card-glow group relative block
792
- backdrop-blur-xl
793
- bg-white/50 dark:bg-gray-800/40
794
- rounded-2xl p-5 sm:p-6
795
- border border-gray-200/50 dark:border-gray-700/40
796
- shadow-md dark:shadow-lg
797
- cursor-pointer
798
- animate-slide-up
799
- decoration-none
800
- focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2
801
- dark:focus-visible:ring-offset-gray-900
802
- `;
803
- card.style.animationDelay = `${index * 0.04}s`;
804
- card.setAttribute('role', 'listitem');
805
- card.setAttribute('aria-label', `${problem.title} - ${problem.difficulty} - ${problem.topic}`);
806
-
807
- // Solved indicator
808
- const solvedBadge = problem.solved ?
809
- `
810
- <span class="absolute top-3 right-3 flex items-center gap-1
811
- px-2 py-0.5 rounded-full text-xs font-semibold
812
- bg-emerald-100 dark:bg-emerald-900/50
813
- text-emerald-700 dark:text-emerald-300
814
- border border-emerald-200 dark:border-emerald-700/40"
815
- aria-label="Solved">
816
- <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
817
- <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
818
- </svg>
819
- Solved
820
- </span>
821
- ` :
822
- '';
823
-
824
- // Platform badge
825
- const platformInitials = (problem.platform || 'DS')
826
- .split(' ')
827
- .map(w => w[0])
828
- .join('')
829
- .toUpperCase()
830
- .slice(0, 2);
831
-
832
- card.innerHTML = `
833
- ${solvedBadge}
834
- <!-- Platform badge -->
835
- <span class="inline-block px-2 py-0.5 rounded-md text-[10px] font-bold font-mono
836
- bg-gray-100 dark:bg-gray-700/70
837
- text-gray-500 dark:text-gray-400
838
- border border-gray-200 dark:border-gray-600/50
839
- mb-3"
840
- aria-label="Platform: ${problem.platform || 'Unknown'}">
841
- ${platformInitials}
842
- </span>
843
-
844
- <!-- Title -->
845
- <h4 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-white
846
- mb-3 leading-tight
847
- group-hover:text-indigo-600 dark:group-hover:text-indigo-400
848
- transition-colors duration-200
849
- pr-16"
850
- title="${problem.title}">
851
- ${problem.title}
852
- </h4>
853
-
854
- <!-- Bottom row: difficulty + topic -->
855
- <div class="flex flex-wrap items-center gap-2 mt-auto">
856
- <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium
857
- ${diffColors.bg} ${diffColors.text} ${diffColors.border} border">
858
- <span class="w-1.5 h-1.5 rounded-full ${diffColors.dot}" aria-hidden="true"></span>
859
- ${problem.difficulty}
860
- </span>
861
- <span class="inline-block px-2.5 py-1 rounded-full text-xs font-medium
862
- bg-gray-100 dark:bg-gray-700/60
863
- text-gray-600 dark:text-gray-400
864
- border border-gray-200 dark:border-gray-600/40">
865
- ${problem.topic}
866
- </span>
867
- </div>
868
-
869
- <!-- Subtle arrow indicator -->
870
- <svg class="absolute bottom-4 right-4 w-4 h-4
871
- text-gray-300 dark:text-gray-600
872
- group-hover:text-indigo-500 dark:group-hover:text-indigo-400
873
- transition-all duration-300
874
- group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
875
- fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
876
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
877
- d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
878
- </svg>
879
- `;
880
-
881
- cardsGrid.appendChild(card);
882
- });
883
- }
884
 
885
- function updateProgressBar() {
886
- const solvedCount = allProblems.filter(p => p.solved).length;
887
- const percentage = Math.round((solvedCount / TOTAL_PROBLEMS) * 100);
888
 
889
- // Animate count
890
- solvedCountEl.textContent = solvedCount;
891
- progressFill.style.width = `${percentage}%`;
892
- progressLabel.textContent = `${percentage}% complete`;
893
 
894
- // Update ARIA
895
- const progressBar = progressFill.parentElement;
896
- progressBar.setAttribute('aria-valuenow', solvedCount);
897
- }
898
 
899
- function updateUIInfo(filteredProblems) {
900
- const total = allProblems.length;
901
- const filtered = filteredProblems.length;
902
- const hasActiveFilters =
903
- activeDifficultyFilters.size > 0 ||
904
- activeTopicFilters.size > 0 ||
905
- searchQuery.trim().length > 0;
906
-
907
- showingCount.textContent = `Showing ${filtered} of ${total} problems`;
908
- resultCount.textContent = hasActiveFilters ? `${filtered} result${filtered !== 1 ? 's' : ''} found` : '';
909
-
910
- if (hasActiveFilters) {
911
- activeFiltersInfo.style.opacity = '1';
912
- clearAllFiltersBtn.classList.remove('hidden');
913
- } else {
914
- activeFiltersInfo.style.opacity = '0';
915
- clearAllFiltersBtn.classList.add('hidden');
916
- }
917
- }
918
 
919
- function applyFiltersAndRender() {
920
- const filtered = getFilteredProblems();
921
- renderProblemCards(filtered);
922
- updateUIInfo(filtered);
923
- }
924
 
925
- function clearAllFilters() {
926
- activeDifficultyFilters.clear();
927
- activeTopicFilters.clear();
928
- searchQuery = '';
929
- searchInput.value = '';
930
- clearSearchBtn.style.opacity = '0';
931
- clearSearchBtn.style.pointerEvents = 'none';
932
- clearSearchBtn.style.transform = 'scale(0.75)';
933
- renderDifficultyChips();
934
- renderTopicChips();
935
- applyFiltersAndRender();
936
- }
937
 
938
- // ─────────────────────────────────────
939
- // EVENT LISTENERS
940
- // ─────────────────────────────────────
941
- searchInput.addEventListener('input', (e) => {
942
- searchQuery = e.target.value;
943
- if (searchQuery.trim()) {
944
- clearSearchBtn.style.opacity = '1';
945
- clearSearchBtn.style.pointerEvents = 'auto';
946
- clearSearchBtn.style.transform = 'scale(1)';
947
- } else {
948
- clearSearchBtn.style.opacity = '0';
949
- clearSearchBtn.style.pointerEvents = 'none';
950
- clearSearchBtn.style.transform = 'scale(0.75)';
951
- }
952
- applyFiltersAndRender();
953
- });
954
-
955
- clearSearchBtn.addEventListener('click', () => {
956
- searchQuery = '';
957
- searchInput.value = '';
958
- clearSearchBtn.style.opacity = '0';
959
- clearSearchBtn.style.pointerEvents = 'none';
960
- clearSearchBtn.style.transform = 'scale(0.75)';
961
- searchInput.focus();
962
- applyFiltersAndRender();
963
- });
964
-
965
- clearAllFiltersBtn.addEventListener('click', clearAllFilters);
966
- resetEmptyStateBtn.addEventListener('click', clearAllFilters);
967
-
968
- // Keyboard shortcut: Escape to clear search
969
- searchInput.addEventListener('keydown', (e) => {
970
- if (e.key === 'Escape') {
971
- searchQuery = '';
972
- searchInput.value = '';
973
- clearSearchBtn.style.opacity = '0';
974
- clearSearchBtn.style.pointerEvents = 'none';
975
- clearSearchBtn.style.transform = 'scale(0.75)';
976
- searchInput.blur();
977
- applyFiltersAndRender();
978
- }
979
- });
980
-
981
- // ─────────────────────────────────────
982
- // DATA FETCHING
983
- // ─────────────────────────────────────
984
- async function fetchData() {
985
- try {
986
- const response = await fetch('./data.json');
987
- if (!response.ok) {
988
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
989
- }
990
- const data = await response.json();
991
-
992
- // Validate data structure
993
- if (!Array.isArray(data)) {
994
- throw new Error('data.json must contain an array of problems.');
995
- }
996
-
997
- allProblems = data.map((item, index) => ({
998
- id: item.id || index + 1,
999
- title: item.title || 'Untitled Problem',
1000
- difficulty: item.difficulty || 'Medium',
1001
- topic: item.topic || 'General',
1002
- url: item.url || '#',
1003
- platform: item.platform || 'Platform',
1004
- solved: Boolean(item.solved),
1005
- }));
1006
-
1007
- // Hide loading state
1008
- loadingState.classList.add('hidden');
1009
- loadingState.style.display = 'none';
1010
-
1011
- // Initialize UI
1012
- updateProgressBar();
1013
- renderDifficultyChips();
1014
- renderTopicChips();
1015
- const filtered = getFilteredProblems();
1016
- renderProblemCards(filtered);
1017
- updateUIInfo(filtered);
1018
-
1019
- // Show the cards grid
1020
- cardsGrid.classList.remove('hidden');
1021
-
1022
- } catch (error) {
1023
- console.error('Failed to load data.json:', error);
1024
- loadingState.classList.add('hidden');
1025
- loadingState.style.display = 'none';
1026
- cardsGrid.classList.remove('hidden');
1027
- emptyState.classList.remove('hidden');
1028
- emptyState.classList.add('flex');
1029
-
1030
- // Update empty state with error message
1031
- const emptyTitle = emptyState.querySelector('p:first-of-type');
1032
- const emptyDesc = emptyState.querySelector('p:last-of-type');
1033
- if (emptyTitle) emptyTitle.textContent = 'Failed to load data';
1034
- if (emptyDesc) emptyDesc.textContent =
1035
- 'Make sure data.json exists in the same directory. Check the console for details.';
1036
-
1037
- // Hide the reset button in error state
1038
- if (resetEmptyStateBtn) resetEmptyStateBtn.style.display = 'none';
1039
- }
1040
- }
1041
 
1042
- // ─────────────────────────────────────
1043
- // INITIALIZATION
1044
- // ─────────────────────────────────────
1045
- function init() {
1046
- // Set current year
1047
- currentYearSpan.textContent = new Date().getFullYear();
1048
 
1049
- // Initialize theme
1050
- initTheme();
1051
 
1052
- // Clear search button initial state
1053
- clearSearchBtn.style.opacity = '0';
1054
- clearSearchBtn.style.pointerEvents = 'none';
1055
- clearSearchBtn.style.transform = 'scale(0.75)';
1056
 
1057
- // Fetch data and boot the app
1058
- fetchData();
1059
- }
 
 
 
 
 
 
 
 
 
 
 
1060
 
1061
- // Run on DOM ready
1062
- if (document.readyState === 'loading') {
1063
- document.addEventListener('DOMContentLoaded', init);
1064
- } else {
1065
- init();
1066
  }
1067
- })();
1068
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1069
  </body>
 
1070
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en" class="dark">
3
+
4
  <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ <title>DSA Portfolio</title>
 
 
 
 
 
 
9
 
10
+ <meta name="description"
11
+ content="Modern DSA portfolio showcasing solved coding problems with explanations and solutions.">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ <script src="https://cdn.tailwindcss.com"></script>
 
 
 
 
14
 
15
+ <link rel="stylesheet" href="./assets/style.css">
16
+
17
+ <link rel="preconnect" href="https://fonts.googleapis.com">
18
+
19
+ <link
20
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700;800&family=JetBrains+Mono:wght@400;500&display=swap"
21
+ rel="stylesheet">
22
+
23
+ <script>
24
+ tailwind.config = {
25
+ darkMode: 'class',
26
+ theme: {
27
+ extend: {
28
+ fontFamily: {
29
+ inter: ['Inter', 'sans-serif'],
30
+ mono: ['JetBrains Mono', 'monospace']
31
+ }
32
  }
33
+ }
34
+ }
35
+ </script>
36
  </head>
37
 
38
+ <body class="bg-slate-950 text-white font-inter min-h-screen">
39
+
40
+ <!-- Header -->
41
+ <header class="border-b border-white/10 sticky top-0 bg-slate-950/80 glass z-50">
42
+
43
+ <div class="max-w-7xl mx-auto px-6 py-5 flex items-center justify-between">
44
+
45
+ <div>
46
+ <h1 class="text-3xl font-extrabold gradient-text">
47
+ DSA Portfolio
48
+ </h1>
49
+
50
+ <p class="text-slate-400 mt-1">
51
+ Personal Knowledge Repository
52
+ </p>
53
+ </div>
54
+
55
+ <button id="themeToggle"
56
+ class="px-4 py-2 rounded-xl bg-white/10 hover:bg-white/20 transition">
57
+ 🌙
58
+ </button>
59
+
 
 
 
 
 
 
 
 
 
60
  </div>
61
 
62
+ </header>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ <!-- Hero -->
65
+ <section class="max-w-7xl mx-auto px-6 py-16">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
+ <h2 class="text-5xl font-black leading-tight max-w-3xl">
68
+ Documenting My
69
+ <span class="gradient-text">
70
+ DSA Journey
71
+ </span>
72
+ </h2>
 
 
 
 
 
 
 
 
73
 
74
+ <p class="text-slate-400 mt-6 text-lg max-w-2xl">
75
+ Every problem includes intuition, explanation, dry run,
76
+ complexity analysis, and optimized solutions.
77
+ </p>
78
+
79
+ <!-- Progress -->
80
+ <div class="mt-10 max-w-xl">
 
 
 
 
81
 
82
+ <div class="flex justify-between mb-2">
83
+ <span>Programs Solved</span>
84
+ <span id="progressText">0/30</span>
85
+ </div>
86
+
87
+ <div class="w-full bg-white/10 rounded-full h-3 overflow-hidden">
88
+ <div id="progressBar"
89
+ class="bg-gradient-to-r from-cyan-400 to-violet-500 h-full rounded-full"
90
+ style="width:0%">
 
 
 
 
 
91
  </div>
92
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
93
 
 
 
 
 
 
 
94
  </div>
95
 
96
+ </section>
97
+
98
+ <!-- Controls -->
99
+ <section class="max-w-7xl mx-auto px-6 mb-10">
100
+
101
+ <input type="text"
102
+ id="searchInput"
103
+ placeholder="Search problems..."
104
+ class="w-full px-5 py-4 rounded-2xl bg-white/10 border border-white/10 outline-none focus:ring-2 focus:ring-cyan-400">
105
+
106
+ <div id="filterContainer"
107
+ class="flex flex-wrap gap-3 mt-5">
 
 
 
 
 
 
 
 
 
 
 
 
108
  </div>
109
 
110
+ </section>
111
+
112
+ <!-- Problem Cards -->
113
+ <main class="max-w-7xl mx-auto px-6 pb-20">
114
+
115
+ <div id="problemGrid"
116
+ class="grid md:grid-cols-2 xl:grid-cols-3 gap-8">
 
 
 
 
117
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ </main>
 
 
 
 
 
 
120
 
121
+ <!-- Footer -->
122
+ <footer class="border-t border-white/10 py-8 text-center text-slate-500">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ Built with HTML + Tailwind + Vanilla JS
 
 
 
 
 
 
 
 
 
125
 
126
+ </footer>
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
+ let problems = [];
131
+ let activeFilter = "All";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
+ const grid = document.getElementById("problemGrid");
134
+ const searchInput = document.getElementById("searchInput");
135
+ const filterContainer = document.getElementById("filterContainer");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ async function fetchProblems() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ const response = await fetch("./data.json");
140
+ problems = await response.json();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ renderStats();
143
+ renderFilters();
144
+ renderProblems(problems);
145
 
146
+ }
 
 
 
147
 
148
+ function renderStats() {
 
 
 
149
 
150
+ const solved = problems.length;
151
+ const target = 30;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
+ document.getElementById("progressText").textContent =
154
+ `${solved}/${target}`;
 
 
 
155
 
156
+ document.getElementById("progressBar").style.width =
157
+ `${(solved / target) * 100}%`;
 
 
 
 
 
 
 
 
 
 
158
 
159
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
+ function renderFilters() {
 
 
 
 
 
162
 
163
+ const topics =
164
+ [...new Set(problems.map(p => p.topic))];
165
 
166
+ const filters =
167
+ ["All", "Easy", "Medium", "Hard", ...topics];
 
 
168
 
169
+ filters.forEach(filter => {
170
+
171
+ const btn = document.createElement("button");
172
+
173
+ btn.textContent = filter;
174
+
175
+ btn.className =
176
+ "px-4 py-2 rounded-full bg-white/10 hover:bg-cyan-500 transition";
177
+
178
+ btn.onclick = () => {
179
+
180
+ activeFilter = filter;
181
+
182
+ filterProblems();
183
 
 
 
 
 
 
184
  }
185
+
186
+ filterContainer.appendChild(btn);
187
+
188
+ });
189
+
190
+ }
191
+
192
+ function filterProblems() {
193
+
194
+ const search =
195
+ searchInput.value.toLowerCase();
196
+
197
+ const filtered = problems.filter(problem => {
198
+
199
+ const matchesSearch =
200
+ problem.title.toLowerCase().includes(search);
201
+
202
+ const matchesFilter =
203
+ activeFilter === "All" ||
204
+ problem.difficulty === activeFilter ||
205
+ problem.topic === activeFilter;
206
+
207
+ return matchesSearch && matchesFilter;
208
+
209
+ });
210
+
211
+ renderProblems(filtered);
212
+
213
+ }
214
+
215
+ searchInput.addEventListener("input", filterProblems);
216
+
217
+ function difficultyColor(level) {
218
+
219
+ if (level === "Easy")
220
+ return "text-green-400 bg-green-500/10";
221
+
222
+ if (level === "Medium")
223
+ return "text-yellow-400 bg-yellow-500/10";
224
+
225
+ return "text-red-400 bg-red-500/10";
226
+
227
+ }
228
+
229
+ function renderProblems(data) {
230
+
231
+ grid.innerHTML = "";
232
+
233
+ data.forEach(problem => {
234
+
235
+ const card = document.createElement("article");
236
+
237
+ card.className =
238
+ "p-6 rounded-3xl border border-white/10 bg-white/5 hover:-translate-y-2 transition";
239
+
240
+ card.innerHTML = `
241
+
242
+ <div class="flex items-start justify-between gap-3">
243
+
244
+ <div>
245
+
246
+ <p class="text-cyan-400 text-sm font-mono">
247
+ ${problem.topic}
248
+ </p>
249
+
250
+ <h2 class="text-2xl font-bold mt-3">
251
+ ${problem.title}
252
+ </h2>
253
+
254
+ </div>
255
+
256
+ <span class="px-3 py-1 rounded-full text-xs ${difficultyColor(problem.difficulty)}">
257
+ ${problem.difficulty}
258
+ </span>
259
+
260
+ </div>
261
+
262
+ <div class="mt-5 flex flex-wrap gap-2">
263
+
264
+ ${problem.tags.map(tag => `
265
+ <span class="text-xs px-3 py-1 rounded-full bg-white/10">
266
+ ${tag}
267
+ </span>
268
+ `).join("")}
269
+
270
+ </div>
271
+
272
+ <div class="mt-8 flex items-center justify-between">
273
+
274
+ <span class="text-slate-400 text-sm">
275
+ ${problem.dateSolved}
276
+ </span>
277
+
278
+ <a href="${problem.url}"
279
+ class="px-5 py-3 rounded-xl bg-gradient-to-r from-cyan-500 to-violet-500 font-semibold">
280
+ View →
281
+ </a>
282
+
283
+ </div>
284
+
285
+ `;
286
+
287
+ grid.appendChild(card);
288
+
289
+ });
290
+
291
+ }
292
+
293
+ fetchProblems();
294
+
295
+ </script>
296
+
297
  </body>
298
+
299
  </html>