artist design
This commit is contained in:
parent
c29c5d6495
commit
cff48b2498
3 changed files with 274 additions and 21 deletions
|
|
@ -1596,8 +1596,9 @@ def get_artist_discography(artist_id):
|
|||
|
||||
print(f"🎤 Fetching discography for artist: {artist_id}")
|
||||
|
||||
# Get all albums and singles for the artist
|
||||
albums = spotify_client.get_artist_albums(artist_id, album_type='album,single,appears_on', limit=50)
|
||||
# Get artist's albums and singles (temporarily include appears_on for debugging)
|
||||
albums = spotify_client.get_artist_albums(artist_id, album_type='album,single', limit=50)
|
||||
print(f"📊 Raw albums returned from Spotify: {len(albums)}")
|
||||
|
||||
if not albums:
|
||||
return jsonify({
|
||||
|
|
@ -1618,13 +1619,22 @@ def get_artist_discography(artist_id):
|
|||
continue
|
||||
seen_albums.add(album.id)
|
||||
|
||||
# Skip compilations and appears_on that aren't the main artist's work
|
||||
if hasattr(album, 'album_type') and album.album_type == 'appears_on':
|
||||
# Only include if the artist is the main artist
|
||||
if hasattr(album, 'artists') and album.artists:
|
||||
main_artist_id = album.artists[0].id if hasattr(album.artists[0], 'id') else None
|
||||
if main_artist_id != artist_id:
|
||||
continue
|
||||
# Debug: Check artist information
|
||||
print(f"🔍 Checking album: {album.name}")
|
||||
if hasattr(album, 'artists') and album.artists:
|
||||
primary_artist_id = album.artists[0].id if hasattr(album.artists[0], 'id') else None
|
||||
primary_artist_name = album.artists[0].name if hasattr(album.artists[0], 'name') else None
|
||||
print(f" Primary artist: {primary_artist_name} (ID: {primary_artist_id})")
|
||||
print(f" Requested artist ID: {artist_id}")
|
||||
|
||||
# Skip if the primary artist doesn't match our requested artist
|
||||
if primary_artist_id and primary_artist_id != artist_id:
|
||||
print(f"🚫 Skipping '{album.name}' - primary artist mismatch")
|
||||
continue
|
||||
elif not primary_artist_id:
|
||||
print(f"⚠️ No primary artist ID found for '{album.name}' - including anyway")
|
||||
else:
|
||||
print(f"⚠️ No artists found for '{album.name}' - including anyway")
|
||||
|
||||
album_data = {
|
||||
"id": album.id,
|
||||
|
|
@ -1636,11 +1646,15 @@ def get_artist_discography(artist_id):
|
|||
"external_urls": album.external_urls if hasattr(album, 'external_urls') else {}
|
||||
}
|
||||
|
||||
# Skip obvious compilation issues but be more lenient for now
|
||||
if hasattr(album, 'album_type') and album.album_type == 'compilation':
|
||||
print(f"📀 Found compilation: '{album.name}' - including for now")
|
||||
|
||||
# Categorize by album type
|
||||
if hasattr(album, 'album_type'):
|
||||
if album.album_type in ['single', 'ep']:
|
||||
singles_list.append(album_data)
|
||||
else: # 'album' or 'compilation'
|
||||
else: # 'album' or approved 'compilation'
|
||||
album_list.append(album_data)
|
||||
else:
|
||||
# Default to album if no type specified
|
||||
|
|
@ -1659,7 +1673,13 @@ def get_artist_discography(artist_id):
|
|||
album_list.sort(key=get_release_year, reverse=True)
|
||||
singles_list.sort(key=get_release_year, reverse=True)
|
||||
|
||||
print(f"✅ Found {len(album_list)} albums and {len(singles_list)} singles for artist")
|
||||
print(f"✅ Found {len(album_list)} albums and {len(singles_list)} singles for artist {artist_id}")
|
||||
|
||||
# Debug: Log the final album list
|
||||
for album in album_list:
|
||||
print(f"📀 Album: {album['name']} ({album['album_type']}) - {album['release_date']}")
|
||||
for single in singles_list:
|
||||
print(f"🎵 Single/EP: {single['name']} ({single['album_type']}) - {single['release_date']}")
|
||||
|
||||
return jsonify({
|
||||
"albums": album_list,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ let artistsPageState = {
|
|||
},
|
||||
cache: {
|
||||
searches: {}, // Cache search results by query
|
||||
discography: {} // Cache discography by artist ID
|
||||
discography: {}, // Cache discography by artist ID
|
||||
colors: {} // Cache extracted colors by image URL
|
||||
}
|
||||
};
|
||||
let artistsSearchTimeout = null;
|
||||
|
|
@ -9293,6 +9294,14 @@ function displayArtistsResults(query, results) {
|
|||
// Add event listeners to cards
|
||||
container.querySelectorAll('.artist-card').forEach((card, index) => {
|
||||
card.addEventListener('click', () => selectArtist(results[index]));
|
||||
|
||||
// Extract colors from artist image for dynamic glow
|
||||
const artist = results[index];
|
||||
if (artist.image_url) {
|
||||
extractImageColors(artist.image_url, (colors) => {
|
||||
applyDynamicGlow(card, colors);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add mouse wheel horizontal scrolling
|
||||
|
|
@ -9421,6 +9430,16 @@ function displayArtistDiscography(discography) {
|
|||
if (albumsContainer) {
|
||||
if (discography.albums?.length > 0) {
|
||||
albumsContainer.innerHTML = discography.albums.map(album => createAlbumCard(album)).join('');
|
||||
|
||||
// Add dynamic glow effects to album cards
|
||||
albumsContainer.querySelectorAll('.album-card').forEach((card, index) => {
|
||||
const album = discography.albums[index];
|
||||
if (album.image_url) {
|
||||
extractImageColors(album.image_url, (colors) => {
|
||||
applyDynamicGlow(card, colors);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
albumsContainer.innerHTML = `
|
||||
<div style="grid-column: 1 / -1; text-align: center; padding: 40px 20px; color: rgba(255, 255, 255, 0.6);">
|
||||
|
|
@ -9436,6 +9455,16 @@ function displayArtistDiscography(discography) {
|
|||
if (singlesContainer) {
|
||||
if (discography.singles?.length > 0) {
|
||||
singlesContainer.innerHTML = discography.singles.map(single => createAlbumCard(single)).join('');
|
||||
|
||||
// Add dynamic glow effects to singles cards
|
||||
singlesContainer.querySelectorAll('.album-card').forEach((card, index) => {
|
||||
const single = discography.singles[index];
|
||||
if (single.image_url) {
|
||||
extractImageColors(single.image_url, (colors) => {
|
||||
applyDynamicGlow(card, colors);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
singlesContainer.innerHTML = `
|
||||
<div style="grid-column: 1 / -1; text-align: center; padding: 40px 20px; color: rgba(255, 255, 255, 0.6);">
|
||||
|
|
@ -9717,6 +9746,184 @@ function showSearchLoadingCards() {
|
|||
container.innerHTML = loadingCardHtml.repeat(6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract dominant colors from an image for dynamic glow effects
|
||||
*/
|
||||
async function extractImageColors(imageUrl, callback) {
|
||||
if (!imageUrl) {
|
||||
callback(['#1db954', '#1ed760']); // Fallback to Spotify green
|
||||
return;
|
||||
}
|
||||
|
||||
// Check cache first for performance
|
||||
if (artistsPageState.cache.colors[imageUrl]) {
|
||||
callback(artistsPageState.cache.colors[imageUrl]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create a canvas to analyze the image
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
|
||||
img.crossOrigin = 'anonymous';
|
||||
|
||||
img.onload = function() {
|
||||
// Resize to small dimensions for faster processing
|
||||
const size = 50;
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
|
||||
// Draw image to canvas
|
||||
ctx.drawImage(img, 0, 0, size, size);
|
||||
|
||||
try {
|
||||
// Get image data
|
||||
const imageData = ctx.getImageData(0, 0, size, size);
|
||||
const data = imageData.data;
|
||||
|
||||
// Extract colors (sample every few pixels for performance)
|
||||
const colors = [];
|
||||
for (let i = 0; i < data.length; i += 16) { // Sample every 4th pixel
|
||||
const r = data[i];
|
||||
const g = data[i + 1];
|
||||
const b = data[i + 2];
|
||||
const alpha = data[i + 3];
|
||||
|
||||
// Skip transparent or very dark pixels
|
||||
if (alpha > 128 && (r + g + b) > 150) {
|
||||
colors.push({ r, g, b });
|
||||
}
|
||||
}
|
||||
|
||||
if (colors.length === 0) {
|
||||
callback(['#1db954', '#1ed760']); // Fallback
|
||||
return;
|
||||
}
|
||||
|
||||
// Find dominant colors using a simple clustering approach
|
||||
const dominantColors = findDominantColors(colors, 2);
|
||||
|
||||
// Convert to CSS hex colors
|
||||
const hexColors = dominantColors.map(color =>
|
||||
`#${((1 << 24) + (color.r << 16) + (color.g << 8) + color.b).toString(16).slice(1)}`
|
||||
);
|
||||
|
||||
// Cache the colors for future use
|
||||
artistsPageState.cache.colors[imageUrl] = hexColors;
|
||||
|
||||
callback(hexColors);
|
||||
|
||||
} catch (e) {
|
||||
console.warn('Color extraction failed, using fallback colors:', e);
|
||||
callback(['#1db954', '#1ed760']);
|
||||
}
|
||||
};
|
||||
|
||||
img.onerror = function() {
|
||||
callback(['#1db954', '#1ed760']); // Fallback on error
|
||||
};
|
||||
|
||||
img.src = imageUrl;
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Image color extraction error:', error);
|
||||
callback(['#1db954', '#1ed760']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple color clustering to find dominant colors
|
||||
*/
|
||||
function findDominantColors(colors, numColors = 2) {
|
||||
if (colors.length === 0) return [{ r: 29, g: 185, b: 84 }];
|
||||
|
||||
// Simple k-means clustering
|
||||
let centroids = [];
|
||||
|
||||
// Initialize centroids randomly
|
||||
for (let i = 0; i < numColors; i++) {
|
||||
centroids.push(colors[Math.floor(Math.random() * colors.length)]);
|
||||
}
|
||||
|
||||
// Run a few iterations of k-means
|
||||
for (let iteration = 0; iteration < 5; iteration++) {
|
||||
const clusters = Array(numColors).fill().map(() => []);
|
||||
|
||||
// Assign each color to nearest centroid
|
||||
colors.forEach(color => {
|
||||
let minDistance = Infinity;
|
||||
let nearestCluster = 0;
|
||||
|
||||
centroids.forEach((centroid, i) => {
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(color.r - centroid.r, 2) +
|
||||
Math.pow(color.g - centroid.g, 2) +
|
||||
Math.pow(color.b - centroid.b, 2)
|
||||
);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearestCluster = i;
|
||||
}
|
||||
});
|
||||
|
||||
clusters[nearestCluster].push(color);
|
||||
});
|
||||
|
||||
// Update centroids
|
||||
centroids = clusters.map(cluster => {
|
||||
if (cluster.length === 0) return centroids[0]; // Fallback
|
||||
|
||||
const avgR = cluster.reduce((sum, c) => sum + c.r, 0) / cluster.length;
|
||||
const avgG = cluster.reduce((sum, c) => sum + c.g, 0) / cluster.length;
|
||||
const avgB = cluster.reduce((sum, c) => sum + c.b, 0) / cluster.length;
|
||||
|
||||
return { r: Math.round(avgR), g: Math.round(avgG), b: Math.round(avgB) };
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure we have vibrant colors by boosting saturation
|
||||
return centroids.map(color => {
|
||||
const max = Math.max(color.r, color.g, color.b);
|
||||
const min = Math.min(color.r, color.g, color.b);
|
||||
const saturation = max === 0 ? 0 : (max - min) / max;
|
||||
|
||||
// Boost low saturation colors
|
||||
if (saturation < 0.4) {
|
||||
const factor = 1.3;
|
||||
return {
|
||||
r: Math.min(255, Math.round(color.r * factor)),
|
||||
g: Math.min(255, Math.round(color.g * factor)),
|
||||
b: Math.min(255, Math.round(color.b * factor))
|
||||
};
|
||||
}
|
||||
|
||||
return color;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply dynamic glow effect to a card element
|
||||
*/
|
||||
function applyDynamicGlow(cardElement, colors) {
|
||||
if (!cardElement || colors.length < 2) return;
|
||||
|
||||
const color1 = colors[0];
|
||||
const color2 = colors[1];
|
||||
|
||||
// Add a small delay to make the effect feel more natural
|
||||
setTimeout(() => {
|
||||
// Create CSS custom properties for the dynamic colors
|
||||
cardElement.style.setProperty('--glow-color-1', color1);
|
||||
cardElement.style.setProperty('--glow-color-2', color2);
|
||||
cardElement.classList.add('has-dynamic-glow');
|
||||
|
||||
console.log(`🎨 Applied dynamic glow: ${color1}, ${color2}`);
|
||||
}, Math.random() * 200 + 100); // Random delay between 100-300ms
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to escape HTML
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5861,7 +5861,7 @@ body {
|
|||
gap: 20px;
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
padding: 20px;
|
||||
padding: 30px;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
/* Custom scrollbar styling */
|
||||
|
|
@ -5914,14 +5914,27 @@ body {
|
|||
}
|
||||
|
||||
.artist-card:hover {
|
||||
transform: translateY(-8px) scale(1.02);
|
||||
z-index: 10;
|
||||
|
||||
box-shadow:
|
||||
0 20px 50px rgba(0, 0, 0, 0.6),
|
||||
0 0 0 1px rgba(29, 185, 84, 0.15),
|
||||
0 0 30px rgba(29, 185, 84, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-5px) scale(1.01);
|
||||
z-index: 10;
|
||||
|
||||
box-shadow:
|
||||
0 12px 15px rgba(0, 0, 0, 0.4),
|
||||
0 0 0 1px rgba(29, 185, 84, 0.15),
|
||||
0 0 20px rgba(29, 185, 84, 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Dynamic glow effects based on image colors */
|
||||
.artist-card.has-dynamic-glow {
|
||||
position: relative;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
filter 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.artist-card.has-dynamic-glow:hover {
|
||||
filter: drop-shadow(0 0 8px var(--glow-color-1, #1db954))
|
||||
drop-shadow(0 0 16px var(--glow-color-2, #1ed760));
|
||||
border-color: var(--glow-color-1, rgba(29, 185, 84, 0.3));
|
||||
}
|
||||
|
||||
.artist-card-background {
|
||||
|
|
@ -6259,6 +6272,19 @@ body {
|
|||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Dynamic glow effects for album cards */
|
||||
.album-card.has-dynamic-glow {
|
||||
position: relative;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
filter 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.album-card.has-dynamic-glow:hover {
|
||||
filter: drop-shadow(0 0 6px var(--glow-color-1, #1db954))
|
||||
drop-shadow(0 0 12px var(--glow-color-2, #1ed760));
|
||||
border-color: var(--glow-color-1, rgba(29, 185, 84, 0.3));
|
||||
}
|
||||
|
||||
.album-card-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
|
|
|
|||
Loading…
Reference in a new issue