Json026 commited on
Commit
b19431d
Β·
verified Β·
1 Parent(s): 9cd7988

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1023 -395
index.html CHANGED
@@ -1,442 +1,1070 @@
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
-
7
- <!-- SEO -->
8
- <title>DSA Portfolio | Problem Solving Journey</title>
9
- <meta
10
- name="description"
11
- content="A modern DSA Portfolio showcasing solved Data Structures & Algorithms problems with filters, search, progress tracking, and elegant UI."
12
- />
13
-
14
- <!-- Open Graph -->
15
- <meta property="og:title" content="DSA Portfolio" />
16
- <meta
17
- property="og:description"
18
- content="Track and showcase solved DSA problems with a modern portfolio."
19
- />
20
- <meta property="og:type" content="website" />
21
- <meta property="og:image" content="https://dummyimage.com/1200x630/111827/ffffff&text=DSA+Portfolio" />
22
-
23
- <!-- Fonts -->
24
- <link rel="preconnect" href="https://fonts.googleapis.com" />
25
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
26
-
27
- <link
28
- href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap"
29
- rel="stylesheet"
30
- />
31
-
32
- <!-- Tailwind -->
33
- <script src="https://cdn.tailwindcss.com"></script>
34
-
35
- <script>
36
- tailwind.config = {
37
- darkMode: "class",
38
- theme: {
39
- extend: {
40
- fontFamily: {
41
- inter: ["Inter", "sans-serif"],
42
- mono: ["JetBrains Mono", "monospace"],
43
- },
44
- animation: {
45
- float: "float 5s ease-in-out infinite",
46
- },
47
- keyframes: {
48
- float: {
49
- "0%, 100%": { transform: "translateY(0px)" },
50
- "50%": { transform: "translateY(-8px)" },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  },
52
- },
53
- },
54
- },
55
- };
56
- </script>
57
-
58
- <style>
59
- ::-webkit-scrollbar {
60
- width: 8px;
61
- }
62
-
63
- ::-webkit-scrollbar-thumb {
64
- background: #6b7280;
65
- border-radius: 999px;
66
- }
67
-
68
- html {
69
- scroll-behavior: smooth;
70
- }
71
-
72
- body {
73
- transition: background 0.3s ease;
74
- }
75
-
76
- .glass {
77
- backdrop-filter: blur(14px);
78
- -webkit-backdrop-filter: blur(14px);
79
- }
80
- </style>
81
- </head>
82
-
83
- <body class="bg-gradient-to-br from-slate-100 via-white to-slate-200 dark:from-[#0b1120] dark:via-[#0f172a] dark:to-[#020617] text-slate-900 dark:text-white font-inter min-h-screen">
84
-
85
- <!-- Decorative Blobs -->
86
- <div class="fixed inset-0 overflow-hidden -z-10">
87
- <div class="absolute top-10 left-10 w-72 h-72 bg-cyan-500/20 rounded-full blur-3xl animate-float"></div>
88
- <div class="absolute bottom-10 right-10 w-72 h-72 bg-violet-500/20 rounded-full blur-3xl animate-float"></div>
89
- </div>
90
-
91
- <!-- Header -->
92
- <header class="sticky top-0 z-50 border-b border-white/10 bg-white/10 dark:bg-white/5 glass">
93
- <nav class="max-w-7xl mx-auto px-5 py-4 flex items-center justify-between">
94
-
95
- <div>
96
- <h1 class="text-2xl md:text-3xl font-extrabold tracking-tight">
97
- DSA Portfolio
98
- </h1>
99
- <p class="text-sm text-slate-600 dark:text-slate-400 mt-1">
100
- Track. Solve. Improve.
101
- </p>
102
- </div>
103
-
104
- <button
105
- id="themeToggle"
106
- aria-label="Toggle Theme"
107
- class="px-4 py-2 rounded-xl border border-white/10 bg-white/10 hover:bg-white/20 transition"
108
- >
109
- πŸŒ™
110
- </button>
111
- </nav>
112
- </header>
113
-
114
- <!-- Hero -->
115
- <section class="max-w-7xl mx-auto px-5 pt-12 pb-8">
116
- <div class="grid lg:grid-cols-2 gap-10 items-center">
117
-
118
- <div>
119
- <span class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-cyan-500/10 border border-cyan-500/20 text-cyan-400 text-sm font-medium">
120
- πŸš€ Data Structures & Algorithms
121
- </span>
122
-
123
- <h2 class="mt-6 text-5xl md:text-6xl font-black leading-tight">
124
- Build Your
125
- <span class="bg-gradient-to-r from-cyan-400 to-violet-400 bg-clip-text text-transparent">
126
- Coding Journey
127
- </span>
128
- </h2>
129
-
130
- <p class="mt-6 text-lg text-slate-600 dark:text-slate-400 leading-relaxed max-w-2xl">
131
- A modern portfolio showcasing solved DSA problems with filtering,
132
- searching, progress tracking, and elegant UI inspired by modern developer tools.
133
- </p>
134
-
135
- <!-- Progress -->
136
- <div class="mt-8 max-w-xl">
137
- <div class="flex items-center justify-between mb-2">
138
- <span class="text-sm font-medium text-slate-700 dark:text-slate-300">
139
- Programs Solved
140
- </span>
141
-
142
- <span
143
- id="progressText"
144
- class="font-mono text-cyan-400"
145
- >
146
- 0/30
147
- </span>
148
- </div>
149
-
150
- <div class="w-full h-3 bg-white/10 rounded-full overflow-hidden">
151
- <div
152
- id="progressBar"
153
- class="h-full bg-gradient-to-r from-cyan-400 to-violet-500 rounded-full transition-all duration-500"
154
- style="width: 0%"
155
- ></div>
156
- </div>
157
- </div>
158
- </div>
159
-
160
- <!-- Stats Card -->
161
- <div class="glass border border-white/10 bg-white/10 dark:bg-white/5 rounded-3xl p-8 shadow-2xl">
162
- <div class="grid grid-cols-2 gap-5">
163
 
164
- <div class="p-5 rounded-2xl bg-white/10 border border-white/10">
165
- <h3 class="text-sm text-slate-400">Total Problems</h3>
166
- <p id="totalProblems" class="text-4xl font-black mt-2">0</p>
167
- </div>
 
 
 
168
 
169
- <div class="p-5 rounded-2xl bg-white/10 border border-white/10">
170
- <h3 class="text-sm text-slate-400">Topics Covered</h3>
171
- <p id="topicsCount" class="text-4xl font-black mt-2">0</p>
172
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
- <div class="p-5 rounded-2xl bg-white/10 border border-white/10">
175
- <h3 class="text-sm text-slate-400">Easy</h3>
176
- <p id="easyCount" class="text-4xl font-black mt-2 text-green-400">0</p>
177
- </div>
 
178
 
179
- <div class="p-5 rounded-2xl bg-white/10 border border-white/10">
180
- <h3 class="text-sm text-slate-400">Medium/Hard</h3>
181
- <p id="mediumHardCount" class="text-4xl font-black mt-2 text-orange-400">0</p>
182
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  </div>
185
- </div>
186
-
187
  </div>
188
- </section>
189
-
190
- <!-- Main -->
191
- <main class="max-w-7xl mx-auto px-5 pb-16">
192
-
193
- <!-- Controls -->
194
- <section class="mb-10">
195
-
196
- <!-- Search -->
197
- <div class="relative mb-6">
198
- <input
199
- type="text"
200
- id="searchInput"
201
- placeholder="Search problems by name..."
202
- class="w-full px-5 py-4 rounded-2xl bg-white/10 dark:bg-white/5 border border-white/10 glass outline-none focus:ring-2 focus:ring-cyan-400 text-lg placeholder:text-slate-500"
203
- />
204
-
205
- <span class="absolute right-5 top-1/2 -translate-y-1/2 text-slate-400">
206
- πŸ”
207
- </span>
208
- </div>
209
-
210
- <!-- Filter Chips -->
211
- <div id="filterContainer" class="flex flex-wrap gap-3">
212
- </div>
213
-
214
- </section>
215
-
216
- <!-- Problem Grid -->
217
- <section>
218
- <div
219
- id="problemGrid"
220
- class="grid sm:grid-cols-2 xl:grid-cols-3 gap-6"
221
- >
222
- </div>
223
- </section>
224
-
225
- </main>
226
-
227
- <!-- Footer -->
228
- <footer class="border-t border-white/10 py-8 text-center text-slate-500">
229
- <p>
230
- Built with ❀️ using HTML, Tailwind CSS & Vanilla JavaScript
231
- </p>
232
- </footer>
233
 
234
- <script>
235
- const problemGrid = document.getElementById("problemGrid");
236
- const searchInput = document.getElementById("searchInput");
237
- const filterContainer = document.getElementById("filterContainer");
238
- const progressBar = document.getElementById("progressBar");
239
- const progressText = document.getElementById("progressText");
240
-
241
- const totalProblems = document.getElementById("totalProblems");
242
- const topicsCount = document.getElementById("topicsCount");
243
- const easyCount = document.getElementById("easyCount");
244
- const mediumHardCount = document.getElementById("mediumHardCount");
245
-
246
- let problems = [];
247
- let activeFilter = "All";
248
-
249
- // Theme Toggle
250
- const themeToggle = document.getElementById("themeToggle");
 
 
 
 
 
 
 
 
 
 
251
 
252
- themeToggle.addEventListener("click", () => {
253
- document.documentElement.classList.toggle("dark");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
- if (document.documentElement.classList.contains("dark")) {
256
- themeToggle.textContent = "πŸŒ™";
257
- } else {
258
- themeToggle.textContent = "β˜€οΈ";
259
- }
260
- });
 
 
 
 
 
 
 
 
261
 
262
- // Fetch JSON Data
263
- async function fetchProblems() {
264
- try {
265
- const response = await fetch("./data.json");
266
- problems = await response.json();
 
 
 
 
 
 
267
 
268
- renderStats();
269
- renderFilters();
270
- renderProblems(problems);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
- } catch (error) {
273
- console.error("Error loading JSON:", error);
 
 
 
 
 
274
 
275
- problemGrid.innerHTML = `
276
- <div class="col-span-full text-center py-20">
277
- <h2 class="text-3xl font-bold text-red-400">
278
- Failed to load data.json
279
- </h2>
280
- </div>
281
- `;
282
- }
283
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
- // Render Stats
286
- function renderStats() {
287
- totalProblems.textContent = problems.length;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
- const uniqueTopics = [...new Set(problems.map(p => p.topic))];
290
- topicsCount.textContent = uniqueTopics.length;
 
 
 
 
 
291
 
292
- const easy = problems.filter(p => p.difficulty === "Easy").length;
293
- easyCount.textContent = easy;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
- mediumHardCount.textContent = problems.length - easy;
 
 
 
 
 
 
 
 
 
296
 
297
- const solved = problems.length;
298
- const target = 30;
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- progressText.textContent = `${solved}/${target}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
- const percentage = Math.min((solved / target) * 100, 100);
303
- progressBar.style.width = `${percentage}%`;
304
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
- // Create Filters
307
- function renderFilters() {
308
- const difficulties = [...new Set(problems.map(p => p.difficulty))];
309
- const topics = [...new Set(problems.map(p => p.topic))];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
- const filters = ["All", ...difficulties, ...topics];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
- filters.forEach(filter => {
314
- const btn = document.createElement("button");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
- btn.textContent = filter;
 
 
317
 
318
- btn.className =
319
- "px-4 py-2 rounded-full border border-white/10 bg-white/10 hover:bg-cyan-500 hover:text-white transition text-sm font-medium";
 
 
320
 
321
- if (filter === "All") {
322
- btn.classList.add("bg-cyan-500", "text-white");
 
323
  }
324
 
325
- btn.addEventListener("click", () => {
326
- activeFilter = filter;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
- document.querySelectorAll("#filterContainer button")
329
- .forEach(b => b.classList.remove("bg-cyan-500", "text-white"));
 
 
 
330
 
331
- btn.classList.add("bg-cyan-500", "text-white");
 
 
 
 
 
 
 
 
 
 
 
332
 
333
- filterProblems();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  });
335
 
336
- filterContainer.appendChild(btn);
337
- });
338
- }
339
-
340
- // Filter Logic
341
- function filterProblems() {
342
- const searchValue = searchInput.value.toLowerCase();
343
-
344
- const filtered = problems.filter(problem => {
345
- const matchesSearch =
346
- problem.title.toLowerCase().includes(searchValue);
347
-
348
- const matchesFilter =
349
- activeFilter === "All" ||
350
- problem.difficulty === activeFilter ||
351
- problem.topic === activeFilter;
352
-
353
- return matchesSearch && matchesFilter;
354
- });
355
-
356
- renderProblems(filtered);
357
- }
358
-
359
- // Search
360
- searchInput.addEventListener("input", filterProblems);
361
-
362
- // Difficulty Badge Colors
363
- function getDifficultyColor(level) {
364
- if (level === "Easy") {
365
- return "bg-green-500/15 text-green-400 border-green-500/20";
366
- }
367
-
368
- if (level === "Medium") {
369
- return "bg-yellow-500/15 text-yellow-400 border-yellow-500/20";
370
- }
371
-
372
- return "bg-red-500/15 text-red-400 border-red-500/20";
373
- }
374
-
375
- // Render Problems
376
- function renderProblems(data) {
377
- problemGrid.innerHTML = "";
378
-
379
- if (data.length === 0) {
380
- problemGrid.innerHTML = `
381
- <div class="col-span-full text-center py-20">
382
- <h2 class="text-3xl font-bold">
383
- No Problems Found 😒
384
- </h2>
385
- </div>
386
- `;
387
- return;
388
- }
389
-
390
- data.forEach(problem => {
391
- const card = document.createElement("article");
392
-
393
- card.className =
394
- "group glass border border-white/10 bg-white/10 dark:bg-white/5 rounded-3xl p-6 hover:-translate-y-2 transition duration-300 shadow-xl";
395
-
396
- card.innerHTML = `
397
- <div class="flex items-start justify-between gap-4">
398
-
399
- <div>
400
- <span class="font-mono text-xs text-cyan-400">
401
- ${problem.topic}
402
- </span>
403
-
404
- <h2 class="mt-3 text-2xl font-bold leading-tight group-hover:text-cyan-400 transition">
405
- ${problem.title}
406
- </h2>
407
- </div>
408
-
409
- <span class="px-3 py-1 rounded-full text-xs border ${getDifficultyColor(problem.difficulty)}">
410
- ${problem.difficulty}
411
- </span>
412
 
413
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
- <div class="mt-8 flex items-center justify-between">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
- <a
418
- href="${problem.url}"
419
- target="_blank"
420
- rel="noopener noreferrer"
421
- class="inline-flex items-center gap-2 px-5 py-3 rounded-xl bg-gradient-to-r from-cyan-500 to-violet-500 text-white font-semibold hover:scale-105 transition"
422
- aria-label="Open ${problem.title}"
423
- >
424
- Solve Problem β†’
425
- </a>
426
 
427
- <div class="text-sm text-slate-400 font-mono">
428
- #DSA
429
- </div>
430
 
431
- </div>
432
- `;
 
 
433
 
434
- problemGrid.appendChild(card);
435
- });
436
- }
437
 
438
- // Initialize
439
- fetchProblems();
440
- </script>
 
 
 
 
 
441
  </body>
442
  </html>
 
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>