feat: redesign QualityBadge with Qobuz-style info display
- Show icon + bit depth + sample rate (e.g., Hi-Res 24-bit / 96 kHz) - Color-coded tiers: MAX (gold), Hi-Res (blue), CD (gray), MP3 (dim) - Support compact mode for NowPlayingBar - Custom Hi-Res audio icon (sound bars)
This commit is contained in:
1 parent
722d378303
commit
764ac9f00e
3 files changed
+132
-44
No files matched your search
@@ -190,7 +190,7 @@
|
||||
<h2 class="artist">{artist}</h2>
|
||||
<h3 class="album">{album}</h3>
|
||||
<div class="quality-info">
|
||||
<QualityBadge {quality} {bitDepth} {samplingRate} size="md" />
|
||||
<QualityBadge {quality} {bitDepth} {samplingRate} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -162,10 +162,7 @@
|
||||
<div class="track-title">{trackTitle}</div>
|
||||
<div class="track-artist">{artist}</div>
|
||||
<div class="track-quality">
|
||||
<QualityBadge {quality} {bitDepth} {samplingRate} />
|
||||
{#if quality}
|
||||
<span class="quality-text">{quality}</span>
|
||||
{/if}
|
||||
<QualityBadge {quality} {bitDepth} {samplingRate} compact />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -1,92 +1,183 @@
|
||||
<script lang="ts">
|
||||
import { Disc } from 'lucide-svelte';
|
||||
import { Disc, Music } from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
quality?: string;
|
||||
bitDepth?: number;
|
||||
samplingRate?: number;
|
||||
size?: 'sm' | 'md';
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
quality = '',
|
||||
bitDepth,
|
||||
samplingRate,
|
||||
size = 'sm'
|
||||
compact = false
|
||||
}: Props = $props();
|
||||
|
||||
// Determine quality tier
|
||||
const tier = $derived.by(() => {
|
||||
if (bitDepth && bitDepth >= 24 && samplingRate && samplingRate > 96) {
|
||||
return 'max'; // Hi-Res+ (24-bit/>96kHz)
|
||||
return 'max';
|
||||
}
|
||||
if (bitDepth && bitDepth >= 24) {
|
||||
return 'hires'; // Hi-Res (24-bit)
|
||||
return 'hires';
|
||||
}
|
||||
if (quality.toLowerCase().includes('flac') || quality.toLowerCase().includes('16-bit') || quality.toLowerCase().includes('lossless')) {
|
||||
return 'cd'; // CD Quality
|
||||
}
|
||||
if (quality.toLowerCase().includes('mp3')) {
|
||||
if (quality.toLowerCase().includes('mp3') || quality.toLowerCase().includes('320')) {
|
||||
return 'lossy';
|
||||
}
|
||||
// Default based on sampling rate
|
||||
if (bitDepth === 16 || quality.toLowerCase().includes('cd') || quality.toLowerCase().includes('flac') || quality.toLowerCase().includes('lossless')) {
|
||||
return 'cd';
|
||||
}
|
||||
if (samplingRate && samplingRate >= 44.1) {
|
||||
if (bitDepth && bitDepth >= 24) return 'hires';
|
||||
return 'cd';
|
||||
}
|
||||
return 'unknown';
|
||||
});
|
||||
|
||||
const sizeClass = $derived(size === 'md' ? 'badge-md' : 'badge-sm');
|
||||
// Format the display text
|
||||
const displayText = $derived.by(() => {
|
||||
if (tier === 'max' || tier === 'hires') {
|
||||
const depth = bitDepth || 24;
|
||||
const rate = samplingRate ? `${samplingRate} kHz` : '';
|
||||
return compact ? `${depth}/${samplingRate || ''}` : `${depth}-bit${rate ? ` / ${rate}` : ''}`;
|
||||
}
|
||||
if (tier === 'cd') {
|
||||
const depth = bitDepth || 16;
|
||||
const rate = samplingRate || 44.1;
|
||||
return compact ? `${depth}/${rate}` : `${depth}-bit / ${rate} kHz`;
|
||||
}
|
||||
if (tier === 'lossy') {
|
||||
return compact ? '320k' : '320 kbps';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const tierLabel = $derived.by(() => {
|
||||
if (tier === 'max') return 'Hi-Res';
|
||||
if (tier === 'hires') return 'Hi-Res';
|
||||
if (tier === 'cd') return 'CD';
|
||||
if (tier === 'lossy') return 'MP3';
|
||||
return '';
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if tier === 'max'}
|
||||
<span class="badge badge-max {sizeClass}">MAX</span>
|
||||
{:else if tier === 'hires'}
|
||||
<span class="badge badge-hires {sizeClass}">HR</span>
|
||||
{:else if tier === 'cd'}
|
||||
<span class="badge badge-cd {sizeClass}">
|
||||
<Disc size={size === 'md' ? 12 : 10} />
|
||||
</span>
|
||||
{#if tier !== 'unknown'}
|
||||
<div class="quality-badge tier-{tier}" class:compact>
|
||||
<!-- Icon -->
|
||||
<div class="badge-icon">
|
||||
{#if tier === 'max' || tier === 'hires'}
|
||||
<!-- Hi-Res Audio Icon (simplified) -->
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" class="hires-icon">
|
||||
<rect x="2" y="6" width="4" height="12" rx="1" />
|
||||
<rect x="8" y="3" width="4" height="18" rx="1" />
|
||||
<rect x="14" y="8" width="4" height="8" rx="1" />
|
||||
<rect x="20" y="5" width="2" height="14" rx="1" />
|
||||
</svg>
|
||||
{:else if tier === 'cd'}
|
||||
<Disc size={14} />
|
||||
{:else if tier === 'lossy'}
|
||||
<Music size={14} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Text -->
|
||||
<div class="badge-text">
|
||||
{#if !compact}
|
||||
<span class="tier-label">{tierLabel}</span>
|
||||
{/if}
|
||||
<span class="quality-info">{displayText}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.badge {
|
||||
.quality-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-family: var(--font-sans, system-ui);
|
||||
}
|
||||
|
||||
.quality-badge.compact {
|
||||
padding: 2px 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.badge-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hires-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.compact .hires-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.compact .badge-text {
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tier-label {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
border-radius: 3px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.badge-sm {
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
.compact .tier-label {
|
||||
font-size: 9px;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.badge-md {
|
||||
height: 20px;
|
||||
padding: 0 6px;
|
||||
.quality-info {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.compact .quality-info {
|
||||
font-size: 10px;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.badge-max {
|
||||
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||
color: #1a1a1a;
|
||||
/* Tier colors */
|
||||
.tier-max {
|
||||
background: linear-gradient(135deg, rgba(255, 170, 0, 0.15) 0%, rgba(255, 136, 0, 0.15) 100%);
|
||||
border: 1px solid rgba(255, 170, 0, 0.3);
|
||||
color: #ffaa00;
|
||||
}
|
||||
|
||||
.badge-hires {
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
|
||||
color: white;
|
||||
.tier-hires {
|
||||
background: rgba(59, 130, 246, 0.12);
|
||||
border: 1px solid rgba(59, 130, 246, 0.25);
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.badge-cd {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: var(--text-muted);
|
||||
.tier-cd {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.tier-lossy {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user