feat(immersive): use full artwork background like Cider
Replace blur approach with full resolution artwork: - Artwork scaled 115% to hide edges - Strong vignette overlay fading to black at edges - Gradient overlays for text readability (header, controls, lyrics) - Slight desaturation for better contrast - Simpler and more visually impactful
This commit is contained in:
1 parent
eb0166b709
commit
484ef87ee7
2 files changed
+49
-76
No files matched your search
@@ -1,82 +1,42 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { generateBlurredBackground, extractDominantColors, createGradientFromColors } from '$lib/utils/imageBlur';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
artwork: string;
|
artwork: string;
|
||||||
mode?: 'blur' | 'gradient' | 'solid';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let { artwork, mode = 'blur' }: Props = $props();
|
let { artwork }: Props = $props();
|
||||||
|
|
||||||
let backgroundUrl = $state('');
|
|
||||||
let useCanvasBlur = $state(false);
|
|
||||||
let isLoading = $state(true);
|
let isLoading = $state(true);
|
||||||
let currentArtwork = $state('');
|
let currentArtwork = $state('');
|
||||||
|
|
||||||
// Generate background when artwork changes
|
// Track artwork changes for fade transition
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!artwork || artwork === currentArtwork) return;
|
if (!artwork || artwork === currentArtwork) return;
|
||||||
|
|
||||||
currentArtwork = artwork;
|
currentArtwork = artwork;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
useCanvasBlur = false;
|
|
||||||
|
|
||||||
if (mode === 'blur') {
|
|
||||||
// Try canvas blur first (most efficient)
|
|
||||||
generateBlurredBackground(artwork)
|
|
||||||
.then((dataUrl) => {
|
|
||||||
backgroundUrl = dataUrl;
|
|
||||||
useCanvasBlur = true;
|
|
||||||
isLoading = false;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// CORS error - fallback to CSS blur (works but uses more GPU)
|
|
||||||
console.warn('[ImmersiveBackground] Canvas blur failed (CORS), using CSS blur fallback');
|
|
||||||
backgroundUrl = artwork;
|
|
||||||
useCanvasBlur = false;
|
|
||||||
isLoading = false;
|
|
||||||
});
|
|
||||||
} else if (mode === 'gradient') {
|
|
||||||
fallbackToGradient();
|
|
||||||
} else {
|
|
||||||
backgroundUrl = '';
|
|
||||||
isLoading = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fallbackToGradient() {
|
function handleImageLoad() {
|
||||||
try {
|
|
||||||
const colors = await extractDominantColors(artwork);
|
|
||||||
backgroundUrl = createGradientFromColors(colors);
|
|
||||||
useCanvasBlur = true; // Gradient doesn't need CSS blur
|
|
||||||
} catch {
|
|
||||||
backgroundUrl = 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)';
|
|
||||||
useCanvasBlur = true;
|
|
||||||
}
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="immersive-background" class:loading={isLoading}>
|
<div class="immersive-background" class:loading={isLoading}>
|
||||||
<!-- Background: either pre-blurred canvas or CSS blur fallback -->
|
<!-- Full artwork as background -->
|
||||||
{#if useCanvasBlur}
|
{#if artwork}
|
||||||
<!-- Pre-computed blur (efficient) -->
|
|
||||||
<div
|
|
||||||
class="background-image canvas-blur"
|
|
||||||
style="background-image: url({backgroundUrl});"
|
|
||||||
></div>
|
|
||||||
{:else if backgroundUrl}
|
|
||||||
<!-- CSS blur fallback (works with CORS-restricted images) -->
|
|
||||||
<img
|
<img
|
||||||
src={backgroundUrl}
|
src={artwork}
|
||||||
alt=""
|
alt=""
|
||||||
class="background-image css-blur"
|
class="background-image"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
onload={handleImageLoad}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Overlay for consistent darkness and vignette -->
|
<!-- Vignette overlay - strong fade to black at edges -->
|
||||||
<div class="background-overlay"></div>
|
<div class="vignette-overlay"></div>
|
||||||
|
|
||||||
|
<!-- Gradient overlay for text readability -->
|
||||||
|
<div class="gradient-overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -85,42 +45,55 @@
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
background-color: #0a0a0b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background-image {
|
.background-image {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -40px; /* Overflow to avoid blur edge artifacts */
|
/* Scale larger than viewport to hide edges */
|
||||||
transition: opacity 300ms ease-out;
|
top: 50%;
|
||||||
}
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) scale(1.15);
|
||||||
/* Canvas pre-blurred version (efficient - no GPU filter) */
|
min-width: 115%;
|
||||||
.background-image.canvas-blur {
|
min-height: 115%;
|
||||||
background-size: cover;
|
width: auto;
|
||||||
background-position: center;
|
height: auto;
|
||||||
background-repeat: no-repeat;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CSS blur fallback for CORS-restricted images */
|
|
||||||
.background-image.css-blur {
|
|
||||||
width: calc(100% + 80px);
|
|
||||||
height: calc(100% + 80px);
|
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
filter: blur(60px) saturate(1.2) brightness(0.5);
|
object-position: center;
|
||||||
transform: scale(1.2);
|
transition: opacity 400ms ease-out;
|
||||||
|
/* Slight desaturation for better text contrast */
|
||||||
|
filter: saturate(0.85) brightness(0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading .background-image {
|
.loading .background-image {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background-overlay {
|
/* Strong vignette - fades to black at edges */
|
||||||
|
.vignette-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse 80% 80% at 50% 45%,
|
||||||
|
transparent 0%,
|
||||||
|
transparent 30%,
|
||||||
|
rgba(0, 0, 0, 0.4) 60%,
|
||||||
|
rgba(0, 0, 0, 0.85) 100%
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Vignette effect + ensure readability */
|
/* Gradient for text areas - darker where text appears */
|
||||||
|
.gradient-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
background:
|
background:
|
||||||
radial-gradient(ellipse at center, transparent 0%, rgba(0, 0, 0, 0.3) 100%),
|
/* Top gradient for header */
|
||||||
linear-gradient(to bottom, rgba(0, 0, 0, 0.2) 0%, transparent 30%, transparent 70%, rgba(0, 0, 0, 0.4) 100%);
|
linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, transparent 15%),
|
||||||
|
/* Bottom gradient for controls */
|
||||||
|
linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, transparent 25%),
|
||||||
|
/* Right side gradient for lyrics panel */
|
||||||
|
linear-gradient(to left, rgba(0, 0, 0, 0.4) 0%, transparent 40%);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -214,7 +214,7 @@
|
|||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<!-- Background -->
|
<!-- Background -->
|
||||||
<ImmersiveBackground {artwork} mode="blur" />
|
<ImmersiveBackground {artwork} />
|
||||||
|
|
||||||
<!-- Header with tabs -->
|
<!-- Header with tabs -->
|
||||||
<ImmersiveHeader
|
<ImmersiveHeader
|
||||||
|
|||||||
Reference in new issue
Block a user