Enhance album cards with improved layout, Wikipedia links, and mobile optimization

- Redesigned album card layout with grid-based header (rank + details)
- Added Wikipedia links for all albums with accurate URL mappings
- Improved description formatting with proper paragraphs and typography
- Made stats cards clickable filters that maintain static numbers
- Significantly increased album art size on mobile devices (280px/240px vs 120px)
- Enhanced visual feedback for interactive elements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Johan Lundberg 2025-07-01 20:57:52 +02:00
parent 88a6434132
commit ed8ad3c02a
3 changed files with 724 additions and 29 deletions

148
script.js
View file

@ -4,6 +4,7 @@ let filteredData = [];
let currentPage = 1;
const itemsPerPage = 50;
let isReversed = false;
let wikipediaUrlMappings = {};
// DOM elements
const albumsGrid = document.getElementById('albumsGrid');
@ -19,11 +20,24 @@ const stats = document.getElementById('stats');
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
loadWikipediaMapping();
loadAlbumsData();
setupEventListeners();
handleUrlParams();
});
// Load Wikipedia URL mappings
async function loadWikipediaMapping() {
try {
const response = await fetch('wikipedia_urls_mapping.json');
if (response.ok) {
wikipediaUrlMappings = await response.json();
}
} catch (err) {
console.warn('Could not load Wikipedia URL mappings:', err);
}
}
// Setup event listeners
function setupEventListeners() {
searchInput.addEventListener('input', debounce(handleSearch, 300));
@ -35,6 +49,53 @@ function setupEventListeners() {
// Add scroll listener for infinite scroll
window.addEventListener('scroll', handleScroll);
// Add click listeners to stats cards
setupStatsClickHandlers();
}
// Setup click handlers for stats cards
function setupStatsClickHandlers() {
const totalAlbumsCard = document.querySelector('.stat-item:has(#totalAlbums)') ||
document.querySelector('#totalAlbums').closest('.stat-item');
const newAlbumsCard = document.querySelector('.stat-item:has(#newAlbums)') ||
document.querySelector('#newAlbums').closest('.stat-item');
const improvedAlbumsCard = document.querySelector('.stat-item:has(#improvedAlbums)') ||
document.querySelector('#improvedAlbums').closest('.stat-item');
const droppedAlbumsCard = document.querySelector('.stat-item:has(#droppedAlbums)') ||
document.querySelector('#droppedAlbums').closest('.stat-item');
if (totalAlbumsCard) {
totalAlbumsCard.addEventListener('click', () => handleStatsCardClick(''));
totalAlbumsCard.style.cursor = 'pointer';
}
if (newAlbumsCard) {
newAlbumsCard.addEventListener('click', () => handleStatsCardClick('New in 2023'));
newAlbumsCard.style.cursor = 'pointer';
}
if (improvedAlbumsCard) {
improvedAlbumsCard.addEventListener('click', () => handleStatsCardClick('improved'));
improvedAlbumsCard.style.cursor = 'pointer';
}
if (droppedAlbumsCard) {
droppedAlbumsCard.addEventListener('click', () => handleStatsCardClick('dropped'));
droppedAlbumsCard.style.cursor = 'pointer';
}
}
// Handle stats card clicks
function handleStatsCardClick(filterValue) {
// Update the status filter dropdown
statusFilter.value = filterValue;
// Trigger the filter change
statusFilter.dispatchEvent(new Event('change'));
// Scroll to the albums grid
albumsGrid.scrollIntoView({ behavior: 'smooth' });
}
// Load albums data from CSV
@ -51,7 +112,7 @@ async function loadAlbumsData() {
hideLoading();
renderAlbums();
updateStats();
initializeStats();
} catch (err) {
console.error('Error loading data:', err);
@ -155,13 +216,13 @@ function createAlbumCard(album) {
const coverImagePath = getCoverImagePath(album);
card.innerHTML = `
<div class="album-rank">#${album.Rank}</div>
<div class="album-content">
<div class="album-title">${escapeHtml(album.Album)}</div>
<div class="album-artist">${escapeHtml(album.Artist)}</div>
${album.Info ? `<div class="album-info">${escapeHtml(album.Info)}</div>` : ''}
<div class="album-status ${statusClass}">${statusText}</div>
${album.Description ? `<div class="album-description">${escapeHtml(album.Description)}</div>` : ''}
<div class="album-header-grid">
<div class="album-rank">#${album.Rank}</div>
<div class="album-details">
<div class="album-title">${escapeHtml(album.Album)}</div>
<div class="album-artist">${escapeHtml(album.Artist)}</div>
${album.Info ? `<div class="album-info">${escapeHtml(album.Info)}</div>` : ''}
</div>
</div>
<div class="album-cover">
${coverImagePath ?
@ -170,6 +231,13 @@ function createAlbumCard(album) {
`<div class="album-cover-icon">🎵</div>`
}
</div>
<div class="album-status ${statusClass}">${statusText}</div>
${album.Description ? `<div class="album-description">${formatDescription(album.Description)}</div>` : ''}
<div class="album-links">
<a href="${generateWikipediaUrl(album.Album, album.Artist)}" target="_blank" rel="noopener noreferrer" class="wikipedia-link">
View on Wikipedia
</a>
</div>
<button class="album-share" title="Share this album" data-rank="${album.Rank}">🔗</button>
`;
@ -315,11 +383,12 @@ function handleScroll() {
}
// Update statistics
function updateStats() {
const total = filteredData.length;
const newAlbums = filteredData.filter(album => album.Status === 'New in 2023').length;
const improved = filteredData.filter(album => album.Status.startsWith('+')).length;
const dropped = filteredData.filter(album => album.Status.startsWith('-') || album.Status.startsWith('Dropped')).length;
// Initialize stats with static values from the full dataset (called only once)
function initializeStats() {
const total = albumsData.length;
const newAlbums = albumsData.filter(album => album.Status === 'New in 2023').length;
const improved = albumsData.filter(album => album.Status.startsWith('+')).length;
const dropped = albumsData.filter(album => album.Status.startsWith('-') || album.Status.startsWith('Dropped')).length;
document.getElementById('totalAlbums').textContent = total;
document.getElementById('newAlbums').textContent = newAlbums;
@ -327,6 +396,11 @@ function updateStats() {
document.getElementById('droppedAlbums').textContent = dropped;
}
// Update stats function (now empty but kept for compatibility)
function updateStats() {
// Stats remain static and don't update when filtering
}
// Utility functions
function debounce(func, wait) {
let timeout;
@ -347,6 +421,54 @@ function escapeHtml(text) {
return div.innerHTML;
}
function formatDescription(text) {
if (!text) return '';
// Split by double line breaks or periods followed by capital letters
const paragraphs = text
.split(/\n\n|\. (?=[A-Z])/)
.map(p => p.trim())
.filter(p => p.length > 0)
.map(p => p.endsWith('.') ? p : p + '.');
// If we only have one paragraph, try to split by sentences for better formatting
if (paragraphs.length === 1 && text.length > 200) {
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
if (sentences.length > 3) {
// Group sentences into 2-3 paragraphs
const third = Math.ceil(sentences.length / 3);
return [
sentences.slice(0, third).join(' '),
sentences.slice(third, third * 2).join(' '),
sentences.slice(third * 2).join(' ')
].filter(p => p.trim()).map(p => `<p>${escapeHtml(p.trim())}</p>`).join('');
}
}
return paragraphs.map(p => `<p>${escapeHtml(p)}</p>`).join('');
}
function generateWikipediaUrl(album, artist) {
// Clean up album and artist names
const albumName = album.trim();
const artistName = artist.trim();
// Check if we have the exact Wikipedia URL from our mapping
if (wikipediaUrlMappings[albumName]) {
return `https://en.wikipedia.org/wiki/${wikipediaUrlMappings[albumName]}`;
}
// Remove "The" from the beginning of artist names for URL formatting
const artistForUrl = artistName.replace(/^The /, '');
// Format for Wikipedia URL - replace spaces with underscores
const albumUrl = albumName.replace(/ /g, '_');
const artistUrl = artistForUrl.replace(/ /g, '_');
// Default pattern: Album_Name_(Artist_Name_album)
return `https://en.wikipedia.org/wiki/${encodeURIComponent(albumUrl)}_(${encodeURIComponent(artistUrl)}_album)`;
}
function hideLoading() {
loading.style.display = 'none';
albumsGrid.style.display = 'grid';