Redesign Artists page: rich hero section, full-bleed cards, multi-source genres
Artists page hero section: - Large portrait artist photo (400x480px, rounded rectangle) - Blurred saturated background from artist image - 2.6em bold name with text shadow - Real service logo badges (Spotify, MusicBrainz, Deezer, iTunes, Last.fm, Genius, Tidal, Qobuz) — matching library page - Genre pills merged from metadata cache + Last.fm tags - Last.fm bio with read more/show less toggle - Last.fm listener count + playcount stats (large bold numbers) - Backend enriches discography response with artist_info from metadata cache + library (all service IDs, Last.fm data, genres) Album/Single/EP cards: - Full-bleed cover art filling entire card with gradient overlay - Album name + year overlaid at bottom over dark gradient - Image zoom on hover, accent glow for dynamic-glow cards - Responsive grid (220px desktop, 170px tablet, 140px mobile) Similar artist cards: - Full-bleed image cards matching library artist card style - Gradient overlay with name at bottom, aspect-ratio 0.8 - Grid-controlled sizing via existing responsive breakpoints Genre explorer (multi-source): - Queries all allowed sources (iTunes+Deezer always, Spotify when authed) via _get_genre_allowed_sources() helper - Deezer genre support: genre_id mapping from search results, one-time backfill from stored raw_json, album-to-artist propagation - Genre deep dive deduplicates artists across sources - Source dots on artists/tracks in deep dive modal - Artist clicks route through source-specific client - Album endpoint falls back across sources when IDs don't match - Genre explorer cached 24hr in-memory, positioned at top of Discover page below hero slider All changes mobile responsive with proper breakpoints.
This commit is contained in:
parent
e08462a002
commit
d248c36da1
5 changed files with 761 additions and 220 deletions
100
web_server.py
100
web_server.py
|
|
@ -9238,10 +9238,108 @@ def get_artist_discography(artist_id):
|
|||
for single in singles_list:
|
||||
print(f"🎵 Single/EP: {single['name']} ({single['album_type']}) - {single['release_date']}")
|
||||
|
||||
# Gather artist enrichment info from cache + library
|
||||
artist_info = {}
|
||||
try:
|
||||
cache = get_metadata_cache()
|
||||
# Try metadata cache for genres, image, followers
|
||||
cached = cache.get_entity(active_source or 'spotify', 'artist', artist_id)
|
||||
if not cached and active_source != 'spotify':
|
||||
cached = cache.get_entity('spotify', 'artist', artist_id)
|
||||
if not cached:
|
||||
# Try by name across all sources
|
||||
for src in ['spotify', 'itunes', 'deezer']:
|
||||
if artist_name:
|
||||
db_tmp = get_database()
|
||||
conn_tmp = db_tmp._get_connection()
|
||||
try:
|
||||
cur = conn_tmp.cursor()
|
||||
cur.execute("""
|
||||
SELECT genres, image_url, followers, popularity, external_urls
|
||||
FROM metadata_cache_entities
|
||||
WHERE entity_type = 'artist' AND name COLLATE NOCASE = ? AND source = ?
|
||||
LIMIT 1
|
||||
""", (artist_name, src))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
cached = dict(row)
|
||||
break
|
||||
finally:
|
||||
conn_tmp.close()
|
||||
if cached:
|
||||
try:
|
||||
artist_info['genres'] = json.loads(cached.get('genres', '[]')) if isinstance(cached.get('genres'), str) else (cached.get('genres') or [])
|
||||
except Exception:
|
||||
artist_info['genres'] = []
|
||||
artist_info['image_url'] = cached.get('image_url')
|
||||
artist_info['followers'] = cached.get('followers')
|
||||
artist_info['popularity'] = cached.get('popularity')
|
||||
try:
|
||||
artist_info['external_urls'] = json.loads(cached.get('external_urls', '{}')) if isinstance(cached.get('external_urls'), str) else (cached.get('external_urls') or {})
|
||||
except Exception:
|
||||
artist_info['external_urls'] = {}
|
||||
|
||||
# Try library for full enrichment (Last.fm bio, stats, service IDs)
|
||||
if artist_name:
|
||||
db_lib = get_database()
|
||||
conn_lib = db_lib._get_connection()
|
||||
try:
|
||||
cur_lib = conn_lib.cursor()
|
||||
cur_lib.execute("""
|
||||
SELECT id, summary, genres, thumb_url,
|
||||
spotify_artist_id, musicbrainz_id, deezer_id, itunes_artist_id,
|
||||
audiodb_id, tidal_id, qobuz_id, genius_id, soul_id,
|
||||
lastfm_bio, lastfm_listeners, lastfm_playcount, lastfm_tags,
|
||||
lastfm_url, genius_url, style, mood, label
|
||||
FROM artists WHERE name COLLATE NOCASE = ? LIMIT 1
|
||||
""", (artist_name,))
|
||||
lib_row = cur_lib.fetchone()
|
||||
if lib_row:
|
||||
lib = dict(lib_row)
|
||||
artist_info['library_id'] = lib['id']
|
||||
# Image fallback
|
||||
if not artist_info.get('image_url') and lib['thumb_url']:
|
||||
artist_info['image_url'] = fix_artist_image_url(lib['thumb_url'])
|
||||
# Genres fallback
|
||||
if not artist_info.get('genres') and lib['genres']:
|
||||
try:
|
||||
artist_info['genres'] = json.loads(lib['genres'])
|
||||
except Exception:
|
||||
pass
|
||||
# Last.fm enrichment
|
||||
if lib.get('lastfm_bio'):
|
||||
artist_info['lastfm_bio'] = lib['lastfm_bio']
|
||||
if lib.get('lastfm_listeners'):
|
||||
artist_info['lastfm_listeners'] = lib['lastfm_listeners']
|
||||
if lib.get('lastfm_playcount'):
|
||||
artist_info['lastfm_playcount'] = lib['lastfm_playcount']
|
||||
if lib.get('lastfm_tags'):
|
||||
try:
|
||||
artist_info['lastfm_tags'] = json.loads(lib['lastfm_tags']) if isinstance(lib['lastfm_tags'], str) else lib['lastfm_tags']
|
||||
except Exception:
|
||||
pass
|
||||
if lib.get('lastfm_url'):
|
||||
artist_info['lastfm_url'] = lib['lastfm_url']
|
||||
if lib.get('genius_url'):
|
||||
artist_info['genius_url'] = lib['genius_url']
|
||||
# Service IDs for badges
|
||||
for key in ['spotify_artist_id', 'musicbrainz_id', 'deezer_id', 'itunes_artist_id',
|
||||
'audiodb_id', 'tidal_id', 'qobuz_id', 'genius_id', 'soul_id']:
|
||||
if lib.get(key):
|
||||
artist_info[key] = lib[key]
|
||||
# Bio fallback from summary
|
||||
if not artist_info.get('lastfm_bio') and lib.get('summary'):
|
||||
artist_info['bio'] = lib['summary']
|
||||
finally:
|
||||
conn_lib.close()
|
||||
except Exception as e:
|
||||
logger.debug(f"Artist info enrichment failed (non-fatal): {e}")
|
||||
|
||||
return jsonify({
|
||||
"albums": album_list,
|
||||
"singles": singles_list,
|
||||
"source": active_source or "spotify"
|
||||
"source": active_source or "spotify",
|
||||
"artist_info": artist_info,
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -2283,33 +2283,41 @@
|
|||
|
||||
<!-- Artist Detail State -->
|
||||
<div class="artist-detail-state hidden" id="artist-detail-state">
|
||||
<div class="artist-detail-header">
|
||||
<div class="artist-detail-header-left">
|
||||
<button class="artist-detail-back-button" id="artist-detail-back-button">
|
||||
<span class="back-icon">←</span>
|
||||
<span>Back to Results</span>
|
||||
<!-- Hero Section -->
|
||||
<div class="artists-hero-section" id="artists-hero-section">
|
||||
<div class="artists-hero-bg" id="artists-hero-bg"></div>
|
||||
<div class="artists-hero-overlay"></div>
|
||||
<div class="artists-hero-content">
|
||||
<button class="artists-hero-back" id="artist-detail-back-button">
|
||||
<span>←</span> Back
|
||||
</button>
|
||||
|
||||
<div class="artist-detail-info">
|
||||
<div class="artist-detail-image" id="search-artist-detail-image"></div>
|
||||
<div class="artist-detail-text">
|
||||
<h2 class="artist-detail-name" id="search-artist-detail-name">Artist Name</h2>
|
||||
<p class="artist-detail-genres" id="search-artist-detail-genres">Genres</p>
|
||||
<div class="artists-hero-main">
|
||||
<div class="artists-hero-image" id="artists-hero-image"></div>
|
||||
<div class="artists-hero-info">
|
||||
<h1 class="artists-hero-name" id="artists-hero-name">Artist Name</h1>
|
||||
<div class="artists-hero-badges" id="artists-hero-badges"></div>
|
||||
<div class="artists-hero-genres" id="artists-hero-genres"></div>
|
||||
<div class="artists-hero-bio" id="artists-hero-bio"></div>
|
||||
<div class="artists-hero-stats" id="artists-hero-stats"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="artist-detail-watchlist-group">
|
||||
<button class="artist-detail-watchlist-btn" id="artist-detail-watchlist-btn">
|
||||
<span class="watchlist-icon">👁️</span>
|
||||
<span class="watchlist-text">Add to Watchlist</span>
|
||||
</button>
|
||||
<button class="artist-detail-watchlist-settings-btn hidden" id="artist-detail-watchlist-settings-btn" title="Watchlist Settings">
|
||||
⚙
|
||||
</button>
|
||||
<div class="artists-hero-actions">
|
||||
<button class="artist-detail-watchlist-btn" id="artist-detail-watchlist-btn">
|
||||
<span class="watchlist-icon">👁️</span>
|
||||
<span class="watchlist-text">Add to Watchlist</span>
|
||||
</button>
|
||||
<button class="artist-detail-watchlist-settings-btn hidden" id="artist-detail-watchlist-settings-btn" title="Watchlist Settings">
|
||||
⚙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Keep old hidden elements for backward compatibility with JS refs -->
|
||||
<div id="search-artist-detail-image" style="display:none"></div>
|
||||
<div id="search-artist-detail-name" style="display:none"></div>
|
||||
<div id="search-artist-detail-genres" style="display:none"></div>
|
||||
|
||||
<div class="artist-detail-content">
|
||||
<div class="artist-detail-tabs">
|
||||
<button class="artist-tab active" data-tab="albums" id="albums-tab">
|
||||
|
|
|
|||
|
|
@ -912,7 +912,68 @@
|
|||
|
||||
.album-cards-container,
|
||||
.singles-cards-container {
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.album-card-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.album-card-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Artists page hero — mobile */
|
||||
.artists-hero-section {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.artists-hero-main {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.artists-hero-image {
|
||||
width: 140px;
|
||||
height: 175px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.artists-hero-name {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.artists-hero-genres {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artists-hero-badges {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artists-hero-content {
|
||||
padding: 16px 14px 20px;
|
||||
}
|
||||
|
||||
.artists-hero-actions {
|
||||
position: static;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.artists-hero-bio {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.artists-hero-stats {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artists-hero-stat strong {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* --- Phase 5B: Library --- */
|
||||
|
|
|
|||
|
|
@ -31909,10 +31909,19 @@ async function loadArtistDiscography(artistId, artistName = null, sourceOverride
|
|||
...artistsPageState.selectedArtist,
|
||||
...data.artist
|
||||
};
|
||||
// Refresh header to show MusicBrainz link
|
||||
updateArtistDetailHeader(artistsPageState.selectedArtist);
|
||||
}
|
||||
|
||||
// Merge artist_info enrichment from discography response
|
||||
if (data.artist_info) {
|
||||
artistsPageState.selectedArtist = {
|
||||
...artistsPageState.selectedArtist,
|
||||
artist_info: data.artist_info,
|
||||
};
|
||||
}
|
||||
|
||||
// Refresh header with all available data
|
||||
updateArtistDetailHeader(artistsPageState.selectedArtist);
|
||||
|
||||
console.log(`✅ Loaded ${discography.albums.length} albums and ${discography.singles.length} singles`);
|
||||
|
||||
// Cache the results (use source-prefixed key if source override active)
|
||||
|
|
@ -32887,63 +32896,132 @@ function retryLastSearch() {
|
|||
* Update artist detail header with artist info
|
||||
*/
|
||||
function updateArtistDetailHeader(artist) {
|
||||
const imageElement = document.getElementById('search-artist-detail-image');
|
||||
const nameElement = document.getElementById('search-artist-detail-name');
|
||||
const genresElement = document.getElementById('search-artist-detail-genres');
|
||||
const _esc = (s) => (s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const info = artist.artist_info || {};
|
||||
const imageUrl = artist.image_url || info.image_url || '';
|
||||
|
||||
if (imageElement) {
|
||||
if (artist.image_url) {
|
||||
imageElement.style.backgroundImage = `url('${artist.image_url}')`;
|
||||
// Background blur
|
||||
const heroBg = document.getElementById('artists-hero-bg');
|
||||
if (heroBg) {
|
||||
heroBg.style.backgroundImage = imageUrl ? `url('${imageUrl}')` : 'none';
|
||||
}
|
||||
|
||||
// Artist image
|
||||
const heroImage = document.getElementById('artists-hero-image');
|
||||
if (heroImage) {
|
||||
if (imageUrl) {
|
||||
heroImage.style.backgroundImage = `url('${imageUrl}')`;
|
||||
heroImage.innerHTML = '';
|
||||
} else {
|
||||
// Lazy load image if missing (common for iTunes artists)
|
||||
console.log(`🖼️ Lazy loading detail image for ${artist.name} (${artist.id})`);
|
||||
heroImage.style.backgroundImage = 'none';
|
||||
heroImage.innerHTML = '<span class="artists-hero-image-fallback">🎤</span>';
|
||||
// Lazy load
|
||||
fetch(`/api/artist/${artist.id}/image`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success && data.image_url) {
|
||||
console.log(`✅ Loaded detail image for ${artist.name}`);
|
||||
imageElement.style.backgroundImage = `url('${data.image_url}')`;
|
||||
// Update the artist object in memory too
|
||||
artist.image_url = data.image_url;
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (d.success && d.image_url) {
|
||||
heroImage.style.backgroundImage = `url('${d.image_url}')`;
|
||||
heroImage.innerHTML = '';
|
||||
if (heroBg) heroBg.style.backgroundImage = `url('${d.image_url}')`;
|
||||
artist.image_url = d.image_url;
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('❌ Failed to load detail image:', err));
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
if (nameElement) {
|
||||
nameElement.textContent = artist.name;
|
||||
// Name
|
||||
const heroName = document.getElementById('artists-hero-name');
|
||||
if (heroName) heroName.textContent = artist.name || 'Unknown Artist';
|
||||
|
||||
// Add MusicBrainz link if available
|
||||
if (artist.musicbrainz_id) {
|
||||
// Remove existing MB link if any
|
||||
const existingMb = nameElement.parentNode.querySelector('.mb-link-btn');
|
||||
if (existingMb) existingMb.remove();
|
||||
// Badges (service links — real logos matching library page)
|
||||
const badgesEl = document.getElementById('artists-hero-badges');
|
||||
if (badgesEl) {
|
||||
const _hb = (logo, fallback, title, url) => {
|
||||
const attr = url ? `data-url="${_esc(url)}" onclick="window.open(this.dataset.url,'_blank')"` : '';
|
||||
const inner = logo
|
||||
? `<img src="${logo}" alt="${fallback}" onerror="this.parentNode.textContent='${fallback}'">`
|
||||
: `<span style="font-size:9px;font-weight:700;">${fallback}</span>`;
|
||||
return `<div class="artists-hero-badge" title="${title}" ${attr}>${inner}</div>`;
|
||||
};
|
||||
const badges = [];
|
||||
if (info.spotify_artist_id) badges.push(_hb(SPOTIFY_LOGO_URL, 'SP', 'Spotify', `https://open.spotify.com/artist/${info.spotify_artist_id}`));
|
||||
if (info.musicbrainz_id || artist.musicbrainz_id) badges.push(_hb(MUSICBRAINZ_LOGO_URL, 'MB', 'MusicBrainz', `https://musicbrainz.org/artist/${info.musicbrainz_id || artist.musicbrainz_id}`));
|
||||
if (info.deezer_id) badges.push(_hb(DEEZER_LOGO_URL, 'Dz', 'Deezer', `https://www.deezer.com/artist/${info.deezer_id}`));
|
||||
if (info.itunes_artist_id) badges.push(_hb(ITUNES_LOGO_URL, 'IT', 'Apple Music', `https://music.apple.com/artist/${info.itunes_artist_id}`));
|
||||
if (info.lastfm_url) badges.push(_hb(LASTFM_LOGO_URL, 'LFM', 'Last.fm', info.lastfm_url));
|
||||
if (info.genius_url) badges.push(_hb(GENIUS_LOGO_URL, 'GEN', 'Genius', info.genius_url));
|
||||
if (info.tidal_id) badges.push(_hb(TIDAL_LOGO_URL, 'TD', 'Tidal', `https://tidal.com/browse/artist/${info.tidal_id}`));
|
||||
if (info.qobuz_id) badges.push(_hb(QOBUZ_LOGO_URL, 'Qz', 'Qobuz', `https://www.qobuz.com/artist/${info.qobuz_id}`));
|
||||
badgesEl.innerHTML = badges.join('');
|
||||
}
|
||||
|
||||
const mbLink = document.createElement('a');
|
||||
mbLink.className = 'mb-link-btn';
|
||||
mbLink.href = `https://musicbrainz.org/artist/${artist.musicbrainz_id}`;
|
||||
mbLink.target = '_blank';
|
||||
mbLink.title = 'View on MusicBrainz';
|
||||
mbLink.innerHTML = `
|
||||
<img src="https://custom-icon-badges.demolab.com/badge/-MusicBrainz-353535?style=flat&logo=musicbrainz&logoColor=white" style="width: auto; height: 16px; margin: 0;">
|
||||
`;
|
||||
// Simplified button style to just be the badge/icon for cleaner look header
|
||||
mbLink.style.padding = '0';
|
||||
mbLink.style.border = 'none';
|
||||
mbLink.style.background = 'transparent';
|
||||
mbLink.style.marginLeft = '12px';
|
||||
mbLink.style.verticalAlign = 'middle';
|
||||
|
||||
nameElement.appendChild(mbLink);
|
||||
// Genres (pill tags — merge with Last.fm tags, deduplicated)
|
||||
const genresEl = document.getElementById('artists-hero-genres');
|
||||
if (genresEl) {
|
||||
let genres = info.genres || artist.genres || [];
|
||||
// Merge Last.fm tags
|
||||
const lfmTags = info.lastfm_tags || [];
|
||||
if (Array.isArray(lfmTags) && lfmTags.length > 0) {
|
||||
const existing = new Set(genres.map(g => g.toLowerCase()));
|
||||
const newTags = lfmTags.filter(t => !existing.has(t.toLowerCase()));
|
||||
genres = [...genres, ...newTags];
|
||||
}
|
||||
if (genres.length > 0) {
|
||||
genresEl.innerHTML = genres.slice(0, 8).map(g =>
|
||||
`<span class="artists-hero-genre-pill">${_esc(g)}</span>`
|
||||
).join('');
|
||||
} else {
|
||||
genresEl.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (genresElement) {
|
||||
const genres = artist.genres?.slice(0, 4).join(' • ') || 'Various genres';
|
||||
genresElement.textContent = genres;
|
||||
// Bio (Last.fm bio or summary fallback — matching library page pattern)
|
||||
const bioEl = document.getElementById('artists-hero-bio');
|
||||
if (bioEl) {
|
||||
const bio = info.lastfm_bio || info.bio || '';
|
||||
if (bio) {
|
||||
// Strip HTML tags and "Read more on Last.fm" links
|
||||
let cleanBio = bio.replace(/<a\b[^>]*>.*?<\/a>/gi, '').replace(/<[^>]+>/g, '').trim();
|
||||
if (cleanBio) {
|
||||
bioEl.innerHTML = `<span class="artists-hero-bio-text">${_esc(cleanBio)}</span>
|
||||
<span class="artists-hero-bio-toggle" onclick="this.parentElement.classList.toggle('expanded');this.textContent=this.parentElement.classList.contains('expanded')?'Show less':'Read more'">Read more</span>`;
|
||||
bioEl.style.display = '';
|
||||
} else {
|
||||
bioEl.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
bioEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Stats (Last.fm listeners + playcount, with followers fallback)
|
||||
const statsEl = document.getElementById('artists-hero-stats');
|
||||
if (statsEl) {
|
||||
const _fmtNum = (n) => {
|
||||
if (!n || n <= 0) return '0';
|
||||
if (n >= 1000000) return (n / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
|
||||
if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
|
||||
return n.toLocaleString();
|
||||
};
|
||||
let stats = '';
|
||||
if (info.lastfm_listeners) {
|
||||
stats += `<span class="artists-hero-stat"><strong>${_fmtNum(info.lastfm_listeners)}</strong> listeners</span>`;
|
||||
}
|
||||
if (info.lastfm_playcount) {
|
||||
stats += `<span class="artists-hero-stat"><strong>${_fmtNum(info.lastfm_playcount)}</strong> plays</span>`;
|
||||
}
|
||||
if (!stats && info.followers) {
|
||||
stats += `<span class="artists-hero-stat"><strong>${_fmtNum(info.followers)}</strong> followers</span>`;
|
||||
}
|
||||
statsEl.innerHTML = stats;
|
||||
}
|
||||
|
||||
// Also update old hidden elements for any JS that references them
|
||||
const oldImage = document.getElementById('search-artist-detail-image');
|
||||
if (oldImage && imageUrl) oldImage.style.backgroundImage = `url('${imageUrl}')`;
|
||||
const oldName = document.getElementById('search-artist-detail-name');
|
||||
if (oldName) oldName.textContent = artist.name;
|
||||
|
||||
// Initialize watchlist button
|
||||
initializeArtistDetailWatchlistButton(artist);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16890,6 +16890,298 @@ body {
|
|||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ========================================= */
|
||||
/* ARTISTS PAGE - HERO SECTION */
|
||||
/* ========================================= */
|
||||
|
||||
.artists-hero-section {
|
||||
position: relative;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 28px;
|
||||
min-height: 300px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.artists-hero-bg {
|
||||
position: absolute;
|
||||
inset: -20px;
|
||||
background-size: cover;
|
||||
background-position: center top;
|
||||
filter: blur(50px) brightness(0.35) saturate(1.4);
|
||||
transform: scale(1.3);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.artists-hero-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(8, 8, 8, 0.5) 0%,
|
||||
rgba(12, 12, 12, 0.75) 50%,
|
||||
rgba(12, 12, 12, 0.92) 100%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.artists-hero-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 32px 36px 36px;
|
||||
}
|
||||
|
||||
.artists-hero-back {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
padding: 6px 14px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
margin-bottom: 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.artists-hero-back:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.artists-hero-main {
|
||||
display: flex;
|
||||
gap: 36px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.artists-hero-image {
|
||||
width: 400px;
|
||||
height: 480px;
|
||||
border-radius: 14px;
|
||||
background-size: cover;
|
||||
background-position: center top;
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
flex-shrink: 0;
|
||||
box-shadow:
|
||||
0 16px 48px rgba(0, 0, 0, 0.6),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.04);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.artists-hero-section:hover .artists-hero-image {
|
||||
border-color: rgba(var(--accent-rgb), 0.2);
|
||||
box-shadow:
|
||||
0 16px 48px rgba(0, 0, 0, 0.6),
|
||||
0 0 30px rgba(var(--accent-rgb), 0.1);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.artists-hero-image-fallback {
|
||||
font-size: 64px;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.artists-hero-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.artists-hero-name {
|
||||
font-size: 2.6em;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -1px;
|
||||
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.artists-hero-badges {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.artists-hero-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 10px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.artists-hero-badge img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.artists-hero-badge:hover {
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
transform: translateY(-2px) scale(1.08);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.artists-hero-genres {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.artists-hero-genre-pill {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 20px;
|
||||
padding: 5px 14px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
letter-spacing: 0.01em;
|
||||
backdrop-filter: blur(4px);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.artists-hero-genre-pill:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.artists-hero-bio {
|
||||
max-width: 600px;
|
||||
max-height: 3.9em;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.artists-hero-bio.expanded {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.artists-hero-bio-text {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
margin: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.artists-hero-bio-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(var(--accent-rgb), 0.9);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-left: 4px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.artists-hero-bio-toggle:hover {
|
||||
color: rgb(var(--accent-rgb));
|
||||
}
|
||||
|
||||
.artists-hero-stats {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.artists-hero-stat {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.artists-hero-stat strong {
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.artists-hero-actions {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
right: 36px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Mobile responsive */
|
||||
@media (max-width: 768px) {
|
||||
.artists-hero-section {
|
||||
border-radius: 14px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.artists-hero-main {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.artists-hero-image {
|
||||
width: 160px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.artists-hero-name {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
.artists-hero-genres {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artists-hero-badges {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artists-hero-stats {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.artists-hero-content {
|
||||
padding: 20px 16px 24px;
|
||||
}
|
||||
|
||||
.artists-hero-actions {
|
||||
position: static;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.artists-hero-stat strong {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -17737,122 +18029,147 @@ body {
|
|||
.album-cards-container,
|
||||
.singles-cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 24px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Album Card Styles */
|
||||
/* Album Card — full-bleed image with info overlay, matching similar artist cards */
|
||||
.album-card {
|
||||
position: relative;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(26, 26, 26, 0.95) 0%,
|
||||
rgba(18, 18, 18, 0.98) 100%);
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(18, 18, 18, 1);
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.4),
|
||||
0 0 0 1px rgba(var(--accent-rgb), 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.album-card:hover {
|
||||
transform: translateY(-6px) scale(1.03);
|
||||
|
||||
box-shadow:
|
||||
0 16px 40px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(var(--accent-rgb), 0.15),
|
||||
0 0 20px rgba(var(--accent-rgb), 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-5px) scale(1.02);
|
||||
border-color: rgba(var(--accent-rgb), 0.25);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5),
|
||||
0 0 24px rgba(var(--accent-rgb), 0.12);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.album-card.has-dynamic-glow:hover {
|
||||
filter: drop-shadow(0 0 6px var(--glow-color-1, rgb(var(--accent-rgb)))) drop-shadow(0 0 12px var(--glow-color-2, rgb(var(--accent-light-rgb))));
|
||||
border-color: var(--glow-color-1, rgba(var(--accent-rgb), 0.3));
|
||||
filter: none;
|
||||
border-color: color-mix(in srgb, var(--glow-color-1) 30%, transparent);
|
||||
box-shadow:
|
||||
0 12px 40px rgba(0, 0, 0, 0.5),
|
||||
0 0 20px color-mix(in srgb, var(--glow-color-1) 15%, transparent);
|
||||
}
|
||||
|
||||
/* Gradient overlay for text readability */
|
||||
.album-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(0deg,
|
||||
rgba(0, 0, 0, 0.88) 0%,
|
||||
rgba(0, 0, 0, 0.35) 40%,
|
||||
transparent 65%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.album-card:hover::after {
|
||||
background: linear-gradient(0deg,
|
||||
rgba(0, 0, 0, 0.92) 0%,
|
||||
rgba(0, 0, 0, 0.3) 45%,
|
||||
transparent 70%);
|
||||
}
|
||||
|
||||
.album-card-image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Fallback gradient if no image */
|
||||
background-image: linear-gradient(135deg,
|
||||
rgba(var(--accent-rgb), 0.2) 0%,
|
||||
rgba(24, 156, 71, 0.1) 100%);
|
||||
.album-card:hover .album-card-image {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.album-card-image::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg,
|
||||
transparent 0%,
|
||||
transparent 60%,
|
||||
rgba(0, 0, 0, 0.3) 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.album-card:hover .album-card-image::after {
|
||||
opacity: 1;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Info pinned at bottom over gradient */
|
||||
.album-card-content {
|
||||
padding: 20px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 14px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.album-card-name {
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.3;
|
||||
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
|
||||
/* Text overflow handling */
|
||||
color: #fff;
|
||||
margin-bottom: 3px;
|
||||
line-height: 1.25;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
letter-spacing: -0.2px;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.album-card-year {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
font-family: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
margin-bottom: 0;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.album-card-type {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
display: none; /* Type is in the completion overlay or implied by the tab */
|
||||
}
|
||||
|
||||
background: rgba(var(--accent-rgb), 0.15);
|
||||
color: rgba(var(--accent-rgb), 0.9);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(var(--accent-rgb), 0.2);
|
||||
font-family: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
@media (max-width: 900px) {
|
||||
.album-cards-container,
|
||||
.singles-cards-container {
|
||||
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.album-cards-container,
|
||||
.singles-cards-container {
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.album-card {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.album-card-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.album-card-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Completion Status Overlay */
|
||||
|
|
@ -24635,85 +24952,66 @@ body {
|
|||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Artist Bubble Card */
|
||||
/* Similar Artist Card — full-bleed image, library-card style */
|
||||
.similar-artist-bubble {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 20px 16px;
|
||||
border-radius: 16px;
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
/* Subtle background */
|
||||
background: linear-gradient(135deg,
|
||||
rgba(26, 26, 26, 0.6) 0%,
|
||||
rgba(18, 18, 18, 0.7) 100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: rgba(18, 18, 18, 1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
|
||||
/* Elegant shadow */
|
||||
box-shadow:
|
||||
0 4px 12px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
aspect-ratio: 0.8;
|
||||
|
||||
/* Progressive load animation */
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
transform: translateY(16px) scale(0.97);
|
||||
animation: fadeInUp 0.35s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
/* Keyframe animation for bubbles */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
from { opacity: 0; transform: translateY(16px) scale(0.97); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
.similar-artist-bubble:hover {
|
||||
transform: translateY(-6px) scale(1.02);
|
||||
background: linear-gradient(135deg,
|
||||
rgba(32, 32, 32, 0.8) 0%,
|
||||
rgba(24, 24, 24, 0.9) 100%);
|
||||
border-color: rgba(var(--accent-rgb), 0.3);
|
||||
|
||||
box-shadow:
|
||||
0 8px 20px rgba(0, 0, 0, 0.4),
|
||||
0 0 20px rgba(var(--accent-rgb), 0.15),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
transform: translateY(-5px) scale(1.02);
|
||||
border-color: rgba(var(--accent-rgb), 0.25);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5),
|
||||
0 0 24px rgba(var(--accent-rgb), 0.12);
|
||||
}
|
||||
|
||||
/* Circular Image Container */
|
||||
/* Gradient overlay for text readability */
|
||||
.similar-artist-bubble::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(0deg,
|
||||
rgba(0, 0, 0, 0.85) 0%,
|
||||
rgba(0, 0, 0, 0.3) 40%,
|
||||
transparent 65%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Image fills the card */
|
||||
.similar-artist-bubble-image {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 0 auto;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
|
||||
/* Premium border */
|
||||
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
0 4px 15px rgba(0, 0, 0, 0.4),
|
||||
inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.similar-artist-bubble:hover .similar-artist-bubble-image {
|
||||
border-color: rgba(var(--accent-rgb), 0.5);
|
||||
box-shadow:
|
||||
0 6px 20px rgba(0, 0, 0, 0.5),
|
||||
0 0 25px rgba(var(--accent-rgb), 0.2),
|
||||
inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
transform: scale(1.05);
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.similar-artist-bubble-image img {
|
||||
|
|
@ -24723,27 +25021,32 @@ body {
|
|||
display: block;
|
||||
}
|
||||
|
||||
/* Fallback for images that fail to load */
|
||||
.similar-artist-bubble-image-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(var(--accent-rgb), 0.2) 0%,
|
||||
rgba(var(--accent-light-rgb), 0.15) 100%);
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
|
||||
font-size: 40px;
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Artist Name */
|
||||
/* Name pinned at bottom over gradient */
|
||||
.similar-artist-bubble-name {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
line-height: 1.3;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 12px;
|
||||
z-index: 2;
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: 1.25;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
|
||||
width: auto;
|
||||
|
||||
/* Truncate long names */
|
||||
display: -webkit-box;
|
||||
|
|
@ -24761,6 +25064,7 @@ body {
|
|||
|
||||
/* Genres (Optional - hidden by default for cleaner look) */
|
||||
.similar-artist-bubble-genres {
|
||||
display: none; /* Hidden in card layout — name + image is enough */
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
|
|
@ -24808,17 +25112,9 @@ body {
|
|||
gap: 16px;
|
||||
}
|
||||
|
||||
.similar-artist-bubble {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
.similar-artist-bubble-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.similar-artist-bubble-name {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue