├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── index.html
├── questions_data.json
├── script.js
└── styles.css
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveServer.settings.port": 5501
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Nikhil Maan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Relevant Leetcode
2 | A website that shows the most frequently asked questions by companies.
3 |
4 | Check it out: https://nikhilm25.github.io/RelevantLeetcode/
5 |
6 | All data is present in questions_data.json.
7 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Relevant LeetCode
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
37 |
38 |
39 |
40 |
41 |
42 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Difficulty
61 | Easy
62 | Medium
63 | Hard
64 |
65 |
66 |
67 |
68 | All Questions
69 | Completed
70 | Incomplete
71 |
72 |
73 |
74 |
75 |
76 | Clear All
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
94 |
95 |
98 |
99 |
102 |
103 |
104 |
105 |
115 |
116 |
119 |
120 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
149 |
150 |
151 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
No problems found
176 |
Try adjusting your filters or search terms
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | // Simplified state management
2 | let allProblems = [];
3 | let filteredProblems = [];
4 | let completedProblems = new Set();
5 | let currentView = 'grid';
6 | let currentSort = 'frequency';
7 | let sortReversed = false;
8 | let activeCompanies = new Set();
9 | let activeTopics = new Set();
10 | let allCompanies = [];
11 | let allTopics = [];
12 |
13 | // Cache keys
14 | const CACHE_KEYS = {
15 | COMPLETED: 'rlc_completed_v3',
16 | PREFERENCES: 'rlc_preferences_v3',
17 | FILTERS: 'rlc_filters_v3'
18 | };
19 |
20 | // DOM elements
21 | const elements = {
22 | searchInput: document.getElementById('searchInput'),
23 | difficultyFilter: document.getElementById('difficultyFilter'),
24 | completionFilter: document.getElementById('completionFilter'),
25 | problemsCount: document.getElementById('problemsCount'),
26 | problemsGrid: document.getElementById('problemsGrid'),
27 | problemsList: document.getElementById('problemsList'),
28 | loadingState: document.getElementById('loadingState'),
29 | emptyState: document.getElementById('emptyState'),
30 | companySearch: document.getElementById('companySearch'),
31 | topicSearch: document.getElementById('topicSearch'),
32 | selectedCompaniesBubbles: document.getElementById('selectedCompaniesBubbles'),
33 | selectedTopicsBubbles: document.getElementById('selectedTopicsBubbles'),
34 | searchCompaniesBubbles: document.getElementById('searchCompaniesBubbles'),
35 | searchTopicsBubbles: document.getElementById('searchTopicsBubbles'),
36 | activeCompaniesCount: document.getElementById('activeCompaniesCount'),
37 | activeTopicsCount: document.getElementById('activeTopicsCount'),
38 | clearFiltersBtn: document.getElementById('clearFiltersBtn')
39 | };
40 |
41 | // Utility functions
42 | function saveToCache(key, data) {
43 | try {
44 | localStorage.setItem(key, JSON.stringify(data));
45 | } catch (e) {
46 | console.warn('Cache save failed:', e);
47 | }
48 | }
49 |
50 | function loadFromCache(key) {
51 | try {
52 | const item = localStorage.getItem(key);
53 | return item ? JSON.parse(item) : null;
54 | } catch (e) {
55 | console.warn('Cache load failed:', e);
56 | return null;
57 | }
58 | }
59 |
60 | function debounce(func, wait) {
61 | let timeout;
62 | return function(...args) {
63 | clearTimeout(timeout);
64 | timeout = setTimeout(() => func.apply(this, args), wait);
65 | };
66 | }
67 |
68 | // Data persistence
69 | function saveCompletedProblems() {
70 | saveToCache(CACHE_KEYS.COMPLETED, [...completedProblems]);
71 | }
72 |
73 | function loadCompletedProblems() {
74 | const saved = loadFromCache(CACHE_KEYS.COMPLETED);
75 | if (saved) completedProblems = new Set(saved);
76 | }
77 |
78 | function savePreferences() {
79 | const prefs = {
80 | currentView,
81 | currentSort,
82 | sortReversed,
83 | searchTerm: elements.searchInput?.value || '',
84 | selectedDifficulty: elements.difficultyFilter?.value || '',
85 | selectedCompletion: elements.completionFilter?.value || ''
86 | };
87 | saveToCache(CACHE_KEYS.PREFERENCES, prefs);
88 | }
89 |
90 | function loadPreferences() {
91 | const prefs = loadFromCache(CACHE_KEYS.PREFERENCES);
92 | if (!prefs) return;
93 |
94 | currentView = prefs.currentView || 'grid';
95 | currentSort = prefs.currentSort || 'frequency';
96 | sortReversed = prefs.sortReversed || false;
97 |
98 | if (elements.searchInput) elements.searchInput.value = prefs.searchTerm || '';
99 | if (elements.difficultyFilter) elements.difficultyFilter.value = prefs.selectedDifficulty || '';
100 | if (elements.completionFilter) elements.completionFilter.value = prefs.selectedCompletion || '';
101 |
102 | updateViewToggle();
103 | updateSortButtons();
104 | }
105 |
106 | function saveFilters() {
107 | const filters = {
108 | companies: [...activeCompanies],
109 | topics: [...activeTopics]
110 | };
111 | saveToCache(CACHE_KEYS.FILTERS, filters);
112 | }
113 |
114 | function loadFilters() {
115 | const saved = loadFromCache(CACHE_KEYS.FILTERS);
116 | if (saved) {
117 | activeCompanies = new Set(saved.companies || []);
118 | activeTopics = new Set(saved.topics || []);
119 | }
120 | }
121 |
122 | // UI updates
123 | function updateViewToggle() {
124 | document.querySelectorAll('.view-btn').forEach(btn => {
125 | btn.classList.toggle('active', btn.dataset.view === currentView);
126 | });
127 | }
128 |
129 | function updateSortButtons() {
130 | document.querySelectorAll('.sort-btn').forEach(btn => {
131 | const isActive = btn.dataset.sort === currentSort;
132 | btn.classList.toggle('active', isActive);
133 | btn.classList.toggle('reverse', isActive && sortReversed);
134 |
135 | const indicator = btn.querySelector('.sort-indicator i');
136 | if (indicator) {
137 | indicator.className = isActive && sortReversed ? 'fas fa-sort-up' : 'fas fa-sort-down';
138 | }
139 | });
140 | }
141 |
142 | // Bubble management
143 | function createBubble(text, isActive, type) {
144 | const bubble = document.createElement('div');
145 | bubble.className = `filter-bubble ${isActive ? 'active' : 'available'}`;
146 | bubble.innerHTML = isActive ?
147 | `${text}× ` :
148 | `${text}+ `;
149 |
150 | bubble.onclick = () => {
151 | if (type === 'company') toggleCompany(text);
152 | else if (type === 'topic') toggleTopic(text);
153 | };
154 |
155 | return bubble;
156 | }
157 |
158 | function renderSelectedBubbles() {
159 | // Companies
160 | const companiesContainer = elements.selectedCompaniesBubbles.querySelector('.bubbles-container');
161 | companiesContainer.innerHTML = activeCompanies.size === 0 ?
162 | 'No companies selected
' :
163 | [...activeCompanies].map(company => createBubble(company, true, 'company').outerHTML).join('');
164 |
165 | // Topics
166 | const topicsContainer = elements.selectedTopicsBubbles.querySelector('.bubbles-container');
167 | topicsContainer.innerHTML = activeTopics.size === 0 ?
168 | 'No topics selected
' :
169 | [...activeTopics].map(topic => createBubble(topic, true, 'topic').outerHTML).join('');
170 |
171 | // Re-attach event listeners
172 | companiesContainer.querySelectorAll('.filter-bubble').forEach(bubble => {
173 | bubble.onclick = () => toggleCompany(bubble.textContent.replace('×', ''));
174 | });
175 | topicsContainer.querySelectorAll('.filter-bubble').forEach(bubble => {
176 | bubble.onclick = () => toggleTopic(bubble.textContent.replace('×', ''));
177 | });
178 |
179 | // Update counters
180 | elements.activeCompaniesCount.textContent = `(${activeCompanies.size} selected)`;
181 | elements.activeTopicsCount.textContent = `(${activeTopics.size} selected)`;
182 | }
183 |
184 | function renderSearchBubbles(searchTerm, type) {
185 | const container = type === 'company' ?
186 | elements.searchCompaniesBubbles.querySelector('.bubbles-container') :
187 | elements.searchTopicsBubbles.querySelector('.bubbles-container');
188 |
189 | const allItems = type === 'company' ? allCompanies : allTopics;
190 | const activeItems = type === 'company' ? activeCompanies : activeTopics;
191 |
192 | if (!searchTerm.trim()) {
193 | container.innerHTML = '';
194 | return;
195 | }
196 |
197 | const filtered = allItems
198 | .filter(item => item.toLowerCase().includes(searchTerm.toLowerCase()) && !activeItems.has(item))
199 | .slice(0, 20);
200 |
201 | if (filtered.length === 0) {
202 | container.innerHTML = `No ${type === 'company' ? 'companies' : 'topics'} found
`;
203 | return;
204 | }
205 |
206 | container.innerHTML = filtered.map(item => createBubble(item, false, type).outerHTML).join('');
207 |
208 | // Re-attach event listeners
209 | container.querySelectorAll('.filter-bubble').forEach(bubble => {
210 | const text = bubble.textContent.replace('+', '');
211 | bubble.onclick = () => {
212 | if (type === 'company') toggleCompany(text);
213 | else toggleTopic(text);
214 | };
215 | });
216 | }
217 |
218 | function toggleCompany(company) {
219 | if (activeCompanies.has(company)) {
220 | activeCompanies.delete(company);
221 | } else {
222 | activeCompanies.add(company);
223 | }
224 | saveFilters();
225 | renderSelectedBubbles();
226 | renderSearchBubbles(elements.companySearch.value, 'company');
227 | updateClearButtonState();
228 | applyFilters();
229 | }
230 |
231 | function toggleTopic(topic) {
232 | if (activeTopics.has(topic)) {
233 | activeTopics.delete(topic);
234 | } else {
235 | activeTopics.add(topic);
236 | }
237 | saveFilters();
238 | renderSelectedBubbles();
239 | renderSearchBubbles(elements.topicSearch.value, 'topic');
240 | updateClearButtonState();
241 | applyFilters();
242 | }
243 |
244 | // Data loading
245 | async function loadProblemsData() {
246 | try {
247 | const response = await fetch('questions_data.json');
248 | if (!response.ok) throw new Error('Failed to load data');
249 |
250 | const data = await response.json();
251 | allProblems = data.map(problem => ({
252 | id: problem['Link of Question'] || Math.random().toString(36),
253 | title: problem.Question,
254 | difficulty: problem.Difficulty,
255 | frequency: parseInt(problem['Frequency (Number of Companies)']) || 0,
256 | link: problem['Link of Question'],
257 | companies: (problem['Companies Asking This Question'] || '').split(',').map(c => c.trim()).filter(Boolean),
258 | topics: (problem.Topics || '').split(',').map(t => t.trim()).filter(Boolean)
259 | }));
260 |
261 | // Extract unique companies and topics
262 | const companyFreq = {};
263 | const topicFreq = {};
264 |
265 | allProblems.forEach(problem => {
266 | problem.companies.forEach(company => {
267 | companyFreq[company] = (companyFreq[company] || 0) + 1;
268 | });
269 | problem.topics.forEach(topic => {
270 | topicFreq[topic] = (topicFreq[topic] || 0) + 1;
271 | });
272 | });
273 |
274 | allCompanies = Object.keys(companyFreq).sort((a, b) => companyFreq[b] - companyFreq[a]);
275 | allTopics = Object.keys(topicFreq).sort((a, b) => topicFreq[b] - topicFreq[a]);
276 |
277 | filteredProblems = [...allProblems];
278 | renderSelectedBubbles();
279 | updateClearButtonState();
280 | applyFilters();
281 | hideLoading();
282 |
283 | } catch (error) {
284 | console.error('Error loading problems:', error);
285 | hideLoading();
286 | showEmptyState();
287 | }
288 | }
289 |
290 | // Problem rendering - optimized
291 | function renderProblems() {
292 | elements.problemsCount.textContent = `${filteredProblems.length.toLocaleString()} problems`;
293 |
294 | if (filteredProblems.length === 0) {
295 | showEmptyState();
296 | return;
297 | }
298 |
299 | hideEmptyState();
300 |
301 | if (currentView === 'grid') {
302 | renderGridView();
303 | } else {
304 | renderListView();
305 | }
306 | }
307 |
308 | function renderGridView() {
309 | // Use DocumentFragment for better performance
310 | const fragment = document.createDocumentFragment();
311 |
312 | filteredProblems.forEach(problem => {
313 | const card = document.createElement('div');
314 | const isCompleted = completedProblems.has(problem.id);
315 |
316 | card.className = `problem-card ${getDifficultyColor(problem.difficulty)} ${isCompleted ? 'completed' : ''}`;
317 | card.dataset.problemId = problem.id;
318 |
319 | card.innerHTML = `
320 |
326 |
327 |
337 |
338 |
339 | ${problem.topics.slice(0, 4).map(topic => `${topic} `).join('')}
340 | ${problem.topics.length > 4 ? `+${problem.topics.length - 4} more ` : ''}
341 |
342 |
343 |
344 |
Top Companies:
345 |
346 | ${problem.companies.slice(0, 6).map(company => `${company} `).join('')}
347 | ${problem.companies.length > 6 ? `+${problem.companies.length - 6} ` : ''}
348 |
349 |
350 |
351 |
352 |
353 |
354 | Solve
355 |
356 |
357 |
358 | ${isCompleted ? 'Completed' : 'Mark Done'}
359 |
360 |
361 | `;
362 |
363 | fragment.appendChild(card);
364 | });
365 |
366 | elements.problemsGrid.innerHTML = '';
367 | elements.problemsGrid.appendChild(fragment);
368 | }
369 |
370 | function renderListView() {
371 | const listContainer = elements.problemsList;
372 |
373 | // Clear existing items but keep header
374 | const existingItems = listContainer.querySelectorAll('.problem-item');
375 | existingItems.forEach(item => item.remove());
376 |
377 | const fragment = document.createDocumentFragment();
378 |
379 | filteredProblems.forEach(problem => {
380 | const row = document.createElement('div');
381 | const isCompleted = completedProblems.has(problem.id);
382 |
383 | row.className = `problem-item ${isCompleted ? 'completed' : ''}`;
384 | row.dataset.problemId = problem.id;
385 |
386 | row.innerHTML = `
387 |
388 |
390 |
391 | ${problem.title}
392 |
393 | ${problem.difficulty}
394 |
395 | ${problem.frequency}
396 | ${problem.topics.slice(0, 2).join(', ')}${problem.topics.length > 2 ? '...' : ''}
397 |
398 |
399 |
400 |
401 |
402 | `;
403 |
404 | fragment.appendChild(row);
405 | });
406 |
407 | listContainer.appendChild(fragment);
408 |
409 | // Add event listeners to checkboxes
410 | listContainer.querySelectorAll('.complete-checkbox').forEach(checkbox => {
411 | checkbox.onchange = function(e) {
412 | e.stopPropagation();
413 | toggleComplete(this.dataset.problemId);
414 | };
415 | });
416 | }
417 |
418 | // Actions
419 | function solveProblem(link) {
420 | if (link) window.open(link, '_blank');
421 | }
422 |
423 | function toggleComplete(problemId) {
424 | const wasCompleted = completedProblems.has(problemId);
425 |
426 | if (wasCompleted) {
427 | completedProblems.delete(problemId);
428 | } else {
429 | completedProblems.add(problemId);
430 | }
431 |
432 | saveCompletedProblems();
433 | updateProblemUI(problemId, !wasCompleted);
434 | updateClearButtonState();
435 | }
436 |
437 | // Optimized UI update - no re-render needed
438 | function updateProblemUI(problemId, isCompleted) {
439 | // Update grid card
440 | const gridCard = elements.problemsGrid.querySelector(`[data-problem-id="${problemId}"]`);
441 | if (gridCard) {
442 | gridCard.classList.toggle('completed', isCompleted);
443 |
444 | const button = gridCard.querySelector('.completion-btn');
445 | const icon = button.querySelector('i');
446 | const text = button.childNodes[button.childNodes.length - 1];
447 |
448 | if (isCompleted) {
449 | button.classList.add('completed-button');
450 | icon.className = 'fas fa-check';
451 | text.textContent = ' Completed';
452 | } else {
453 | button.classList.remove('completed-button');
454 | icon.className = 'fas fa-plus';
455 | text.textContent = ' Mark Done';
456 | }
457 | }
458 |
459 | // Update list item
460 | const listItem = elements.problemsList.querySelector(`[data-problem-id="${problemId}"]`);
461 | if (listItem) {
462 | listItem.classList.toggle('completed', isCompleted);
463 | const checkbox = listItem.querySelector('.complete-checkbox');
464 | if (checkbox) checkbox.checked = isCompleted;
465 | }
466 | }
467 |
468 | // Filtering and sorting
469 | function applyFilters() {
470 | const searchTerm = (elements.searchInput?.value || '').toLowerCase();
471 | const selectedDifficulty = (elements.difficultyFilter?.value || '').toLowerCase();
472 | const selectedCompletion = (elements.completionFilter?.value || '').toLowerCase();
473 |
474 | filteredProblems = allProblems.filter(problem => {
475 | const matchesSearch = !searchTerm || problem.title.toLowerCase().includes(searchTerm);
476 | const matchesDifficulty = !selectedDifficulty || problem.difficulty.toLowerCase() === selectedDifficulty;
477 |
478 | const isCompleted = completedProblems.has(problem.id);
479 | const matchesCompletion = !selectedCompletion ||
480 | (selectedCompletion === 'completed' && isCompleted) ||
481 | (selectedCompletion === 'incomplete' && !isCompleted);
482 |
483 | const matchesCompany = activeCompanies.size === 0 ||
484 | problem.companies.some(c => activeCompanies.has(c));
485 |
486 | const matchesTopic = activeTopics.size === 0 ||
487 | problem.topics.some(t => activeTopics.has(t));
488 |
489 | return matchesSearch && matchesDifficulty && matchesCompletion && matchesCompany && matchesTopic;
490 | });
491 |
492 | sortProblems();
493 | renderProblems();
494 | updateClearButtonState();
495 | savePreferences();
496 | }
497 |
498 | function sortProblems() {
499 | filteredProblems.sort((a, b) => {
500 | let valA, valB;
501 |
502 | if (currentSort === 'frequency') {
503 | valA = a.frequency;
504 | valB = b.frequency;
505 | } else if (currentSort === 'difficulty') {
506 | const diffOrder = { 'EASY': 1, 'MEDIUM': 2, 'HARD': 3 };
507 | valA = diffOrder[a.difficulty.toUpperCase()] || 4;
508 | valB = diffOrder[b.difficulty.toUpperCase()] || 4;
509 | } else {
510 | return 0;
511 | }
512 |
513 | return sortReversed ? valA - valB : valB - valA;
514 | });
515 | }
516 |
517 | function clearAllFilters() {
518 | if (elements.searchInput) elements.searchInput.value = '';
519 | if (elements.difficultyFilter) elements.difficultyFilter.value = '';
520 | if (elements.completionFilter) elements.completionFilter.value = '';
521 | if (elements.companySearch) elements.companySearch.value = '';
522 | if (elements.topicSearch) elements.topicSearch.value = '';
523 |
524 | activeCompanies.clear();
525 | activeTopics.clear();
526 |
527 | saveFilters();
528 | renderSelectedBubbles();
529 | renderSearchBubbles('', 'company');
530 | renderSearchBubbles('', 'topic');
531 | updateClearButtonState();
532 | applyFilters();
533 | }
534 |
535 | function updateClearButtonState() {
536 | const hasActiveFilters =
537 | (elements.searchInput?.value || '') ||
538 | (elements.difficultyFilter?.value || '') ||
539 | (elements.completionFilter?.value || '') ||
540 | activeCompanies.size > 0 ||
541 | activeTopics.size > 0;
542 |
543 | if (elements.clearFiltersBtn) {
544 | elements.clearFiltersBtn.disabled = !hasActiveFilters;
545 | }
546 | }
547 |
548 | // Utility functions
549 | function getDifficultyColor(difficulty) {
550 | const colors = { 'EASY': 'easy', 'MEDIUM': 'medium', 'HARD': 'hard' };
551 | return colors[difficulty.toUpperCase()] || 'medium';
552 | }
553 |
554 | function showEmptyState() {
555 | elements.emptyState.style.display = 'block';
556 | elements.problemsGrid.style.display = 'none';
557 | elements.problemsList.style.display = 'none';
558 | }
559 |
560 | function hideEmptyState() {
561 | elements.emptyState.style.display = 'none';
562 | if (currentView === 'grid') {
563 | elements.problemsGrid.style.display = 'grid';
564 | elements.problemsList.style.display = 'none';
565 | } else {
566 | elements.problemsGrid.style.display = 'none';
567 | elements.problemsList.style.display = 'block';
568 | }
569 | }
570 |
571 | function hideLoading() {
572 | elements.loadingState.style.display = 'none';
573 | }
574 |
575 | // Event listeners
576 | document.addEventListener('DOMContentLoaded', () => {
577 | loadCompletedProblems();
578 | loadFilters();
579 | loadPreferences();
580 | loadProblemsData();
581 |
582 | // Remove dangerous button entirely
583 | const dangerBtn = document.getElementById('dangerousButton');
584 | if (dangerBtn) dangerBtn.remove();
585 |
586 | // Clear filters
587 | elements.clearFiltersBtn?.addEventListener('click', clearAllFilters);
588 |
589 | // Search and filters
590 | const debouncedFilter = debounce(applyFilters, 200);
591 | elements.searchInput?.addEventListener('input', debouncedFilter);
592 | elements.difficultyFilter?.addEventListener('change', applyFilters);
593 | elements.completionFilter?.addEventListener('change', applyFilters);
594 |
595 | // Company and topic search
596 | const debouncedCompanySearch = debounce((e) => renderSearchBubbles(e.target.value, 'company'), 200);
597 | const debouncedTopicSearch = debounce((e) => renderSearchBubbles(e.target.value, 'topic'), 200);
598 |
599 | elements.companySearch?.addEventListener('input', debouncedCompanySearch);
600 | elements.topicSearch?.addEventListener('input', debouncedTopicSearch);
601 |
602 | // View toggle
603 | document.querySelectorAll('.view-btn').forEach(btn => {
604 | btn.onclick = () => {
605 | document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
606 | btn.classList.add('active');
607 | currentView = btn.dataset.view;
608 | savePreferences();
609 | renderProblems();
610 | };
611 | });
612 |
613 | // Sort buttons
614 | document.querySelectorAll('.sort-btn').forEach(btn => {
615 | btn.onclick = () => {
616 | const clickedSort = btn.dataset.sort;
617 |
618 | if (currentSort === clickedSort) {
619 | sortReversed = !sortReversed;
620 | } else {
621 | currentSort = clickedSort;
622 | sortReversed = false;
623 | }
624 |
625 | updateSortButtons();
626 | savePreferences();
627 | sortProblems();
628 | renderProblems();
629 | };
630 | });
631 | });
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary: #3b82f6;
3 | --primary-hover: #2563eb;
4 | --success: #10b981;
5 | --success-hover: #059669;
6 | --warning: #f59e0b;
7 | --danger: #ef4444;
8 |
9 | --bg-primary: #0f172a;
10 | --bg-secondary: #1e293b;
11 | --bg-card: rgba(255, 255, 255, 0.05);
12 | --bg-card-hover: rgba(255, 255, 255, 0.08);
13 | --text-primary: #f8fafc;
14 | --text-secondary: #94a3b8;
15 | --border: rgba(255, 255, 255, 0.1);
16 | --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
17 | }
18 |
19 | * {
20 | margin: 0;
21 | padding: 0;
22 | box-sizing: border-box;
23 | }
24 |
25 | body {
26 | font-family: 'Inter', sans-serif;
27 | background: var(--bg-primary);
28 | color: var(--text-primary);
29 | line-height: 1.6;
30 | }
31 |
32 | /* Remove the heavy background animation entirely */
33 | .bg-animation {
34 | display: none;
35 | }
36 |
37 | /* Simplified header */
38 | .header {
39 | background: rgba(30, 41, 59, 0.8);
40 | backdrop-filter: blur(8px);
41 | border-bottom: 1px solid var(--border);
42 | padding: 1rem 0;
43 | position: sticky;
44 | top: 0;
45 | z-index: 100;
46 | }
47 |
48 | .header-content {
49 | max-width: 1400px;
50 | margin: 0 auto;
51 | padding: 0 2rem;
52 | display: flex;
53 | justify-content: space-between;
54 | align-items: center;
55 | }
56 |
57 | .brand h1 {
58 | font-size: 1.5rem;
59 | font-weight: 700;
60 | color: var(--primary);
61 | margin: 0;
62 | }
63 |
64 | .brand-subtitle {
65 | font-size: 0.8rem;
66 | color: var(--text-secondary);
67 | }
68 |
69 | .header-links {
70 | display: flex;
71 | align-items: center;
72 | gap: 1rem;
73 | }
74 |
75 | .header-link {
76 | display: flex;
77 | align-items: center;
78 | gap: 0.5rem;
79 | padding: 0.5rem 1rem;
80 | background: var(--bg-card);
81 | border: 1px solid var(--border);
82 | border-radius: 8px;
83 | color: var(--text-secondary);
84 | text-decoration: none;
85 | font-size: 0.85rem;
86 | transition: all 0.2s ease;
87 | }
88 |
89 | .header-link:hover {
90 | background: var(--bg-card-hover);
91 | color: var(--text-primary);
92 | }
93 |
94 | .header-link.primary {
95 | background: var(--primary);
96 | color: white;
97 | border-color: var(--primary);
98 | }
99 |
100 | .header-link.primary:hover {
101 | background: var(--primary-hover);
102 | }
103 |
104 | /* YouTube header link styling */
105 | .header-link.youtube {
106 | background: #ff0000;
107 | color: white;
108 | border-color: #ff0000;
109 | }
110 |
111 | .header-link.youtube:hover {
112 | background: #cc0000;
113 | color: white;
114 | }
115 |
116 | /* Remove all the dangerous button nonsense */
117 | .dangerous-button-container {
118 | display: none;
119 | }
120 |
121 | /* Container */
122 | .container {
123 | max-width: 1400px;
124 | margin: 0 auto;
125 | padding: 2rem;
126 | }
127 |
128 | /* Search section */
129 | .search-section {
130 | background: var(--bg-card);
131 | border: 1px solid var(--border);
132 | border-radius: 12px;
133 | padding: 2rem;
134 | margin-bottom: 2rem;
135 | }
136 |
137 | .search-header {
138 | display: flex;
139 | justify-content: space-between;
140 | align-items: center;
141 | margin-bottom: 1.5rem;
142 | }
143 |
144 | .search-title {
145 | font-size: 1.2rem;
146 | font-weight: 600;
147 | }
148 |
149 | .view-toggle {
150 | display: flex;
151 | background: var(--bg-secondary);
152 | border-radius: 8px;
153 | padding: 4px;
154 | }
155 |
156 | .view-btn {
157 | padding: 0.5rem 1rem;
158 | border: none;
159 | background: transparent;
160 | color: var(--text-secondary);
161 | border-radius: 4px;
162 | cursor: pointer;
163 | transition: all 0.15s ease;
164 | }
165 |
166 | .view-btn.active {
167 | background: var(--primary);
168 | color: white;
169 | }
170 |
171 | .search-filters {
172 | display: grid;
173 | grid-template-columns: 2fr 1fr 1fr 1fr auto;
174 | gap: 1rem;
175 | margin-bottom: 1.5rem;
176 | }
177 |
178 | .filter-input, .filter-select {
179 | width: 100%;
180 | padding: 0.75rem 1rem;
181 | background: var(--bg-secondary);
182 | border: 1px solid var(--border);
183 | border-radius: 8px;
184 | color: var(--text-primary);
185 | font-size: 0.9rem;
186 | transition: border-color 0.15s ease;
187 | }
188 |
189 | .filter-input:focus, .filter-select:focus {
190 | outline: none;
191 | border-color: var(--primary);
192 | }
193 |
194 | .filter-input::placeholder {
195 | color: var(--text-secondary);
196 | }
197 |
198 | .clear-filters-btn {
199 | padding: 0.75rem 1.5rem;
200 | background: var(--danger);
201 | border: none;
202 | border-radius: 8px;
203 | color: white;
204 | font-size: 0.85rem;
205 | cursor: pointer;
206 | transition: background-color 0.15s ease;
207 | display: flex;
208 | align-items: center;
209 | gap: 0.5rem;
210 | }
211 |
212 | .clear-filters-btn:hover {
213 | background: #dc2626;
214 | }
215 |
216 | /* Simplified bubbles */
217 | .filter-bubbles-section {
218 | margin-top: 1.5rem;
219 | }
220 |
221 | .filter-category {
222 | margin-bottom: 1.5rem;
223 | }
224 |
225 | .filter-category-header {
226 | display: flex;
227 | align-items: center;
228 | gap: 0.5rem;
229 | margin-bottom: 0.75rem;
230 | }
231 |
232 | .filter-category-title {
233 | font-size: 0.9rem;
234 | font-weight: 600;
235 | display: flex;
236 | align-items: center;
237 | gap: 0.5rem;
238 | }
239 |
240 | .filter-category-icon {
241 | width: 20px;
242 | height: 20px;
243 | display: flex;
244 | align-items: center;
245 | justify-content: center;
246 | font-size: 0.8rem;
247 | background: var(--primary);
248 | color: white;
249 | border-radius: 4px;
250 | }
251 |
252 | .filter-search {
253 | flex: 1;
254 | padding: 0.5rem 0.75rem;
255 | background: var(--bg-secondary);
256 | border: 1px solid var(--border);
257 | border-radius: 6px;
258 | color: var(--text-primary);
259 | font-size: 0.8rem;
260 | }
261 |
262 | .bubbles-container {
263 | display: flex;
264 | flex-wrap: wrap;
265 | gap: 0.5rem;
266 | min-height: 20px;
267 | }
268 |
269 | .filter-bubble {
270 | padding: 0.4rem 0.8rem;
271 | border-radius: 16px;
272 | font-size: 0.8rem;
273 | cursor: pointer;
274 | transition: all 0.15s ease;
275 | border: 1px solid var(--border);
276 | display: flex;
277 | align-items: center;
278 | gap: 0.5rem;
279 | user-select: none;
280 | }
281 |
282 | .filter-bubble.available {
283 | background: var(--bg-secondary);
284 | color: var(--text-secondary);
285 | }
286 |
287 | .filter-bubble.available:hover {
288 | background: var(--bg-card-hover);
289 | color: var(--text-primary);
290 | }
291 |
292 | .filter-bubble.active {
293 | background: var(--primary);
294 | color: white;
295 | border-color: var(--primary);
296 | }
297 |
298 | .selected-bubbles {
299 | margin-bottom: 0.75rem;
300 | }
301 |
302 | .search-bubbles {
303 | max-height: 200px;
304 | overflow-y: auto;
305 | margin-top: 0.5rem; /* Add gap between selected and search results */
306 | }
307 |
308 | /* Problems section */
309 | .problems-header {
310 | display: flex;
311 | justify-content: space-between;
312 | align-items: center;
313 | margin-bottom: 1.5rem;
314 | }
315 |
316 | .problems-title {
317 | font-size: 1.2rem;
318 | font-weight: 600;
319 | }
320 |
321 | .problems-count {
322 | color: var(--text-secondary);
323 | font-size: 0.9rem;
324 | }
325 |
326 | .sort-options {
327 | display: flex;
328 | gap: 1rem;
329 | }
330 |
331 | .sort-btn {
332 | padding: 0.5rem 1rem;
333 | background: var(--bg-card);
334 | border: 1px solid var(--border);
335 | border-radius: 6px;
336 | color: var(--text-secondary);
337 | cursor: pointer;
338 | transition: all 0.15s ease;
339 | font-size: 0.8rem;
340 | display: flex;
341 | align-items: center;
342 | gap: 0.5rem;
343 | }
344 |
345 | .sort-btn.active {
346 | background: var(--primary);
347 | color: white;
348 | border-color: var(--primary);
349 | }
350 |
351 | .sort-btn.difficulty-sort.active {
352 | background: var(--danger);
353 | border-color: var(--danger);
354 | }
355 |
356 | /* Grid view - simplified */
357 | .problems-grid {
358 | display: grid;
359 | grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
360 | gap: 1.5rem;
361 | }
362 |
363 | .problem-card {
364 | background: var(--bg-card);
365 | border: 1px solid var(--border);
366 | border-radius: 12px;
367 | padding: 1.25rem; /* Reduced from 1.5rem */
368 | transition: all 0.2s ease;
369 | cursor: pointer;
370 | position: relative;
371 | will-change: transform;
372 | display: flex;
373 | flex-direction: column;
374 | min-height: 280px; /* Reduced from 350px */
375 | }
376 |
377 | .problem-card::before {
378 | content: '';
379 | position: absolute;
380 | top: 0;
381 | left: 0;
382 | right: 0;
383 | height: 3px;
384 | border-radius: 12px 12px 0 0;
385 | }
386 |
387 | .problem-card.easy::before {
388 | background: var(--success);
389 | }
390 |
391 | .problem-card.medium::before {
392 | background: var(--warning);
393 | }
394 |
395 | .problem-card.hard::before {
396 | background: var(--danger);
397 | }
398 |
399 | .problem-card:hover {
400 | background: var(--bg-card-hover);
401 | transform: translateY(-2px);
402 | box-shadow: var(--shadow);
403 | }
404 |
405 | /* CLEAN completion animation - just a subtle glow and checkmark */
406 | .problem-card.completed {
407 | background: rgba(16, 185, 129, 0.05);
408 | border-color: rgba(16, 185, 129, 0.3);
409 | transform: scale(0.99);
410 | }
411 |
412 | .problem-card.completed::after {
413 | content: '✓';
414 | position: absolute;
415 | top: 1rem;
416 | right: 1rem;
417 | width: 32px;
418 | height: 32px;
419 | background: var(--success);
420 | border-radius: 50%;
421 | display: flex;
422 | align-items: center;
423 | justify-content: center;
424 | font-weight: bold;
425 | color: white;
426 | font-size: 1rem;
427 | opacity: 1;
428 | transition: opacity 0.2s ease;
429 | }
430 |
431 | .problem-card:not(.completed)::after {
432 | opacity: 0;
433 | }
434 |
435 | .problem-card.completed .problem-title {
436 | color: rgba(248, 250, 252, 0.7);
437 | text-decoration: line-through;
438 | text-decoration-color: rgba(16, 185, 129, 0.7);
439 | }
440 |
441 | .problem-header {
442 | display: flex;
443 | justify-content: space-between;
444 | align-items: flex-start;
445 | margin-bottom: 0.75rem; /* Reduced from 1rem */
446 | }
447 |
448 | .problem-title {
449 | font-size: 1.1rem;
450 | font-weight: 600;
451 | margin-bottom: 0.25rem; /* Reduced from 0.5rem */
452 | transition: color 0.2s ease;
453 | }
454 |
455 | .problem-difficulty {
456 | padding: 0.25rem 0.75rem;
457 | border-radius: 12px;
458 | font-size: 0.75rem;
459 | font-weight: 500;
460 | text-transform: uppercase;
461 | }
462 |
463 | .problem-difficulty.easy {
464 | background: rgba(16, 185, 129, 0.2);
465 | color: var(--success);
466 | }
467 |
468 | .problem-difficulty.medium {
469 | background: rgba(245, 158, 11, 0.2);
470 | color: var(--warning);
471 | }
472 |
473 | .problem-difficulty.hard {
474 | background: rgba(239, 68, 68, 0.2);
475 | color: var(--danger);
476 | }
477 |
478 | .problem-content {
479 | display: flex;
480 | flex-direction: column;
481 | flex-grow: 1; /* Takes up remaining space */
482 | }
483 |
484 | .problem-tags {
485 | display: flex;
486 | flex-wrap: wrap;
487 | gap: 0.5rem;
488 | margin-bottom: 0.75rem; /* Reduced from 1rem */
489 | min-height: 1.5rem; /* Reduced from 2rem */
490 | }
491 |
492 | .tag {
493 | background: var(--success);
494 | color: white;
495 | padding: 0.2rem 0.5rem;
496 | border-radius: 4px;
497 | font-size: 0.65rem;
498 | font-weight: 500;
499 | }
500 |
501 | .problem-meta {
502 | display: flex;
503 | align-items: center;
504 | gap: 1rem;
505 | margin-bottom: 0.75rem; /* Reduced from 1rem */
506 | font-size: 0.8rem;
507 | color: var(--text-secondary);
508 | }
509 |
510 | .meta-item {
511 | display: flex;
512 | align-items: center;
513 | gap: 0.5rem;
514 | }
515 |
516 | .problem-companies {
517 | margin-bottom: 0.75rem; /* Reduced from 1rem */
518 | flex-grow: 1;
519 | min-height: 3rem; /* Reduced from 4rem */
520 | }
521 |
522 | .companies-list {
523 | display: flex;
524 | flex-wrap: wrap;
525 | gap: 0.25rem;
526 | margin-top: 0.5rem;
527 | }
528 |
529 | .company-tag {
530 | background: var(--primary);
531 | color: white;
532 | padding: 0.2rem 0.5rem;
533 | border-radius: 4px;
534 | font-size: 0.65rem;
535 | }
536 |
537 | .problem-actions {
538 | display: flex;
539 | gap: 0.5rem;
540 | margin-top: auto; /* Pushes buttons to bottom */
541 | flex-shrink: 0; /* Prevents buttons from shrinking */
542 | }
543 |
544 | .action-btn {
545 | flex: 1;
546 | padding: 0.6rem;
547 | border: none;
548 | border-radius: 6px;
549 | cursor: pointer;
550 | font-size: 0.8rem;
551 | transition: all 0.15s ease;
552 | display: flex;
553 | align-items: center;
554 | justify-content: center;
555 | gap: 0.5rem;
556 | }
557 |
558 | .btn-primary {
559 | background: var(--primary);
560 | color: white;
561 | }
562 |
563 | .btn-primary:hover {
564 | background: var(--primary-hover);
565 | }
566 |
567 | .btn-secondary {
568 | background: var(--bg-secondary);
569 | color: var(--text-secondary);
570 | border: 1px solid var(--border);
571 | }
572 |
573 | .btn-secondary:hover {
574 | background: var(--bg-card-hover);
575 | color: var(--text-primary);
576 | }
577 |
578 | /* Clean completion button state */
579 | .completion-btn.completed-button {
580 | background: var(--success) !important;
581 | color: white !important;
582 | border-color: var(--success) !important;
583 | }
584 |
585 | /* List view */
586 | .problems-list {
587 | display: none;
588 | }
589 |
590 | .problems-list.active {
591 | display: block;
592 | }
593 |
594 | .list-header {
595 | background: var(--bg-card);
596 | border: 1px solid var(--border);
597 | border-radius: 8px 8px 0 0;
598 | padding: 1rem 1.5rem;
599 | display: grid;
600 | grid-template-columns: 50px 1fr 120px 80px 120px 80px;
601 | align-items: center;
602 | gap: 1rem;
603 | font-weight: 600;
604 | font-size: 0.85rem;
605 | color: var(--text-secondary);
606 | }
607 |
608 | .problem-item {
609 | background: var(--bg-card);
610 | border: 1px solid var(--border);
611 | border-top: none;
612 | padding: 1rem 1.5rem;
613 | display: grid;
614 | grid-template-columns: 50px 1fr 120px 80px 120px 80px;
615 | align-items: center;
616 | gap: 1rem;
617 | transition: background-color 0.15s ease;
618 | }
619 |
620 | .problem-item:last-child {
621 | border-radius: 0 0 8px 8px;
622 | }
623 |
624 | .problem-item:hover {
625 | background: var(--bg-card-hover);
626 | }
627 |
628 | .problem-item.completed {
629 | background: rgba(16, 185, 129, 0.05);
630 | border-left: 3px solid var(--success);
631 | }
632 |
633 | .problem-item.completed .list-problem-title {
634 | color: rgba(248, 250, 252, 0.7);
635 | text-decoration: line-through;
636 | text-decoration-color: rgba(16, 185, 129, 0.7);
637 | }
638 |
639 | .complete-checkbox {
640 | width: 20px;
641 | height: 20px;
642 | accent-color: var(--primary);
643 | cursor: pointer;
644 | transition: all 0.15s ease;
645 | }
646 |
647 | .problem-item.completed .complete-checkbox {
648 | accent-color: var(--success);
649 | }
650 |
651 | .list-problem-title {
652 | font-weight: 600;
653 | transition: color 0.15s ease;
654 | }
655 |
656 | /* Loading and empty states */
657 | .loading {
658 | display: flex;
659 | justify-content: center;
660 | align-items: center;
661 | padding: 4rem;
662 | }
663 |
664 | .spinner {
665 | width: 32px;
666 | height: 32px;
667 | border: 2px solid var(--border);
668 | border-top: 2px solid var(--primary);
669 | border-radius: 50%;
670 | animation: spin 1s linear infinite;
671 | }
672 |
673 | @keyframes spin {
674 | 0% { transform: rotate(0deg); }
675 | 100% { transform: rotate(360deg); }
676 | }
677 |
678 | .empty-state {
679 | text-align: center;
680 | padding: 4rem 2rem;
681 | color: var(--text-secondary);
682 | }
683 |
684 | .empty-icon {
685 | font-size: 3rem;
686 | margin-bottom: 1rem;
687 | opacity: 0.5;
688 | }
689 |
690 | /* Simple fade-in for new elements only */
691 | .fade-in {
692 | animation: fadeIn 0.3s ease-out;
693 | }
694 |
695 | @keyframes fadeIn {
696 | from { opacity: 0; }
697 | to { opacity: 1; }
698 | }
699 |
700 | /* Remove custom scrollbar styling to improve performance */
701 | ::-webkit-scrollbar {
702 | width: 8px;
703 | }
704 |
705 | ::-webkit-scrollbar-track {
706 | background: var(--bg-secondary);
707 | }
708 |
709 | ::-webkit-scrollbar-thumb {
710 | background: var(--border);
711 | border-radius: 4px;
712 | }
713 |
714 |
--------------------------------------------------------------------------------