Skip to content

Commit 8e47581

Browse files
committed
- add browser history state management for filters using pushState/popstate
- implement URL parameter handling for persistent filter state - refactor filter methods to support history navigation - remove console.log from card click handler - completely revamp timer tool with modern features: - add theme toggle with dark mode support - add visual progress bar - implement timer presets (1m, 5m, etc.) - add pause functionality - display end time - add sound and notification toggles - implement keyboard shortcuts - persist user preferences in localStorage
1 parent 6c3ed1e commit 8e47581

File tree

2 files changed

+626
-136
lines changed

2 files changed

+626
-136
lines changed

index.html

Lines changed: 111 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -355,10 +355,11 @@ <h3 class="card-title" title="${item.title}">${item.title}</h3>
355355
}
356356

357357
// Filter and display items
358-
function filterAndDisplayItems(resetPage = true) {
358+
let isInitialLoad = true;
359+
360+
function filterAndDisplayItems(resetPage = true, updateHistory = true) {
359361
if (resetPage) currentPage = 1;
360362

361-
// Apply filters
362363
let filtered = [...allItems];
363364

364365
if (currentFilter === 'game' || currentFilter === 'tool') {
@@ -371,8 +372,8 @@ <h3 class="card-title" title="${item.title}">${item.title}</h3>
371372
filtered = filtered.filter(item => isNewItem(item));
372373
}
373374

374-
375375
statsContainer.style.display = currentCategory === 'all' && currentFilter === 'all' ? 'flex' : 'none';
376+
376377
if (currentCategory !== 'all') {
377378
filtered = filtered.filter(item => getCategory(item) === currentCategory);
378379
}
@@ -388,54 +389,144 @@ <h3 class="card-title" title="${item.title}">${item.title}</h3>
388389
clearSearch.style.display = 'none';
389390
}
390391

391-
// Apply sorting
392392
filtered = sortItems(filtered, currentSort);
393-
394-
// Update title
395393
updateTitle(filtered);
396394

397-
// Check if no results
395+
if (updateHistory && !isInitialLoad) {
396+
const params = new URLSearchParams();
397+
if (currentFilter !== 'all') params.set('filter', currentFilter);
398+
if (currentCategory !== 'all') params.set('category', currentCategory);
399+
if (currentSort !== 'featured') params.set('sort', currentSort);
400+
if (currentSearch) params.set('search', currentSearch);
401+
if (isListView) params.set('view', 'list');
402+
403+
const url = params.toString() ? `?${params.toString()}` : window.location.pathname;
404+
history.pushState({ filter: currentFilter, category: currentCategory, sort: currentSort,
405+
search: currentSearch, view: isListView ? 'list' : 'grid', page: currentPage }, '', url);
406+
}
407+
398408
if (filtered.length === 0) {
399409
mainGrid.innerHTML = '';
400410
emptyState.style.display = 'block';
401411
return;
402412
}
403413

404-
// Show results
405414
emptyState.style.display = 'none';
406415
displayedItems = filtered;
407416

408-
// Paginate
409417
const end = currentPage * itemsPerPage;
410418
const itemsToShow = filtered.slice(0, end);
411419

412-
// Set view class
413420
mainGrid.className = isListView ? 'list-view' : 'grid-view';
414-
415-
// Render cards
416421
mainGrid.innerHTML = itemsToShow.map(createCard).join('');
417422

418-
// Set up read more buttons
419423
document.querySelectorAll('.read-more').forEach(btn => {
420424
btn.addEventListener('click', function() {
421-
const id = this.getAttribute('data-id');
422425
const descContainer = this.previousElementSibling;
423426
descContainer.classList.toggle('truncated');
424427
this.textContent = descContainer.classList.contains('truncated') ? 'Read more' : 'Read less';
425428
});
426429
});
427430

428-
// make full card clickable
429431
document.querySelectorAll('.card').forEach(card => {
430432
card.addEventListener('click', e => {
431-
if (!e.target.closest('.read-more')) {
432-
console.log(e.currentTarget);
433-
card.querySelector('a').click();
434-
}
433+
if (!e.target.closest('.read-more')) card.querySelector('a').click();
435434
});
436-
})
435+
});
436+
}
437+
438+
async function fetchData() {
439+
try {
440+
const [gamesResponse, toolsResponse] = await Promise.all([
441+
fetch('data/games.json'),
442+
fetch('data/tools.json')
443+
]);
444+
445+
const gamesData = await gamesResponse.json();
446+
const toolsData = await toolsResponse.json();
447+
448+
allItems = [...gamesData, ...toolsData];
449+
updateStats();
450+
451+
const params = new URLSearchParams(window.location.search);
452+
453+
if (params.get('filter')) {
454+
currentFilter = params.get('filter');
455+
filterButtons.forEach(btn => {
456+
btn.classList.toggle('active', btn.getAttribute('data-filter') === currentFilter);
457+
});
458+
}
459+
460+
if (params.get('category')) {
461+
currentCategory = params.get('category');
462+
categoryFilter.value = currentCategory;
463+
}
464+
465+
if (params.get('sort')) {
466+
currentSort = params.get('sort');
467+
sortSelect.value = currentSort;
468+
}
469+
470+
if (params.get('search')) {
471+
currentSearch = params.get('search');
472+
searchInput.value = currentSearch;
473+
}
474+
475+
if (params.get('view') === 'list') {
476+
isListView = true;
477+
listViewBtn.classList.add('active');
478+
gridViewBtn.classList.remove('active');
479+
}
480+
481+
filterAndDisplayItems(true, false);
482+
483+
const stateParams = new URLSearchParams();
484+
if (currentFilter !== 'all') stateParams.set('filter', currentFilter);
485+
if (currentCategory !== 'all') stateParams.set('category', currentCategory);
486+
if (currentSort !== 'featured') stateParams.set('sort', currentSort);
487+
if (currentSearch) stateParams.set('search', currentSearch);
488+
if (isListView) stateParams.set('view', 'list');
489+
490+
const url = stateParams.toString() ? `?${stateParams.toString()}` : window.location.pathname;
491+
history.replaceState({ filter: currentFilter, category: currentCategory, sort: currentSort,
492+
search: currentSearch, view: isListView ? 'list' : 'grid', page: currentPage }, '', url);
493+
494+
isInitialLoad = false;
495+
} catch (error) {
496+
console.error('Error fetching data:', error);
497+
mainGrid.innerHTML = `<div class="empty-state"><i class="fas fa-exclamation-circle"></i><h3>Error loading data</h3><p>Please try again later</p></div>`;
498+
}
437499
}
438500

501+
window.addEventListener('popstate', function(event) {
502+
if (event.state) {
503+
currentFilter = event.state.filter || 'all';
504+
currentCategory = event.state.category || 'all';
505+
currentSort = event.state.sort || 'featured';
506+
currentSearch = event.state.search || '';
507+
isListView = event.state.view === 'list';
508+
currentPage = event.state.page || 1;
509+
510+
filterButtons.forEach(btn => {
511+
btn.classList.toggle('active', btn.getAttribute('data-filter') === currentFilter);
512+
});
513+
514+
categoryFilter.value = currentCategory;
515+
sortSelect.value = currentSort;
516+
searchInput.value = currentSearch;
517+
518+
if (isListView) {
519+
listViewBtn.classList.add('active');
520+
gridViewBtn.classList.remove('active');
521+
} else {
522+
gridViewBtn.classList.add('active');
523+
listViewBtn.classList.remove('active');
524+
}
525+
526+
filterAndDisplayItems(false, false);
527+
}
528+
});
529+
439530
// Sort items
440531
function sortItems(items, sortMethod) {
441532
switch (sortMethod) {
@@ -506,30 +597,6 @@ <h3 class="card-title" title="${item.title}">${item.title}</h3>
506597
window.location.href = randomItem.url;
507598
}
508599

509-
// Fetch data
510-
async function fetchData() {
511-
try {
512-
const [gamesResponse, toolsResponse] = await Promise.all([
513-
fetch('data/games.json'),
514-
fetch('data/tools.json')
515-
]);
516-
517-
const gamesData = await gamesResponse.json();
518-
const toolsData = await toolsResponse.json();
519-
520-
allItems = [...gamesData, ...toolsData];
521-
522-
// Update statistics
523-
updateStats();
524-
525-
// Initial display - featured items first, then newest
526-
filterAndDisplayItems();
527-
} catch (error) {
528-
console.error('Error fetching data:', error);
529-
mainGrid.innerHTML = `<div class="empty-state"><i class="fas fa-exclamation-circle"></i><h3>Error loading data</h3><p>Please try again later</p></div>`;
530-
}
531-
}
532-
533600
// Event listeners
534601
searchInput.addEventListener('input', function() {
535602
currentSearch = this.value.trim();

0 commit comments

Comments
 (0)