├── .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 |
16 |
17 |
18 |

Relevant LeetCode

19 |
Most Asked Questions by Companies
20 |
21 | 35 |
36 |
37 | 38 | 39 |
40 | 41 |
42 |
43 |

Find Your Next Challenge

44 |
45 | 48 | 51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 | 65 |
66 |
67 | 72 |
73 |
74 | 78 |
79 |
80 | 81 | 82 |
83 |
84 |
85 |
86 |
87 | 88 |
89 | Companies 90 | (0 selected) 91 |
92 | 93 |
94 | 95 |
96 |
97 |
98 | 99 |
100 |
101 |
102 |
103 | 104 |
105 |
106 |
107 |
108 | 109 |
110 | Topics 111 | (0 selected) 112 |
113 | 114 |
115 | 116 |
117 |
118 |
119 | 120 |
121 |
122 |
123 |
124 |
125 |
126 | 127 | 128 |
129 |
130 |
131 |

Problems

132 | Loading... 133 |
134 |
135 | 141 | 147 |
148 |
149 | 150 | 151 |
152 |
153 |
154 | 155 | 156 |
157 | 158 | 159 |
160 |
161 |
162 |
Problem
163 |
Difficulty
164 |
Freq.
165 |
Topics
166 |
Action
167 |
168 |
169 | 170 | 171 | 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 |
321 |
322 |

${problem.title}

323 | ${problem.difficulty} 324 |
325 |
326 | 327 |
328 |
329 | 330 | ${problem.frequency} companies 331 |
332 |
333 | 334 | ${problem.topics.length} topics 335 |
336 |
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 | 356 | 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 | 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 | --------------------------------------------------------------------------------