Scroll content inside the shell so the player shares the ambient backdrop
CI/CD / BackEnd (push) Successful in 1m17s
CI/CD / WebPlayer (push) Successful in 40s
CI/CD / Deploy (push) Skipped

The player dock used to sit fixed over the scrolling page, so it needed an
opaque background. The shell is now a fixed-height frame where only the
content column scrolls, letting the dock stay transparent like the sidebar
and queue. Scroll reset/restore on navigation moves to restoreScroll(), and
the stable scrollbar gutter moves from <html> to the content scroller.
This commit is contained in:
2026-09-23 23:52:27 +02:00
parent 0f28e19c1d
commit cfdc61a4b8
10 changed files with 61 additions and 42 deletions
+19 -15
View File
@@ -19,12 +19,15 @@
import { ACCENT_HUE } from '$lib/theme/color';
import { buildThemeTokens, applyThemeTokens, tokensToCss } from '$lib/theme/tokens';
import { themeMode } from '$lib/theme/mode.svelte';
import { trackNavigation } from '$lib/state/navigation.svelte';
import { trackNavigation, restoreScroll } from '$lib/state/navigation.svelte';
import { page } from '$app/state';
let { children, data } = $props();
let scroller = $state<HTMLElement>();
trackNavigation();
restoreScroll(() => scroller);
$effect(() => {
if (data.likedTracks?.length) liked.hydrate(data.likedTracks);
@@ -35,7 +38,6 @@
});
const isAuthPage = $derived(page.url.pathname === '/auth');
const hasTrack = $derived(player.currentId !== null);
const initialThemeCss = `:root{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'dark'))}}:root[data-theme='light']{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'light'))}}`;
@@ -101,28 +103,30 @@
{@render children()}
</div>
{:else}
<div class="relative flex min-h-screen text-fg antialiased">
<div class="relative flex h-dvh overflow-hidden text-fg antialiased">
<div class="app-backdrop"></div>
<div class="app-vignette"></div>
<Sidebar user={data.user} playlists={data.sidebarPlaylists ?? []} />
<div class="flex min-w-0 flex-1">
<div class="flex min-w-0 flex-1 flex-col">
<MobileHeader user={data.user} accountUrl={data.accountUrl} />
<TopBar user={data.user} accountUrl={data.accountUrl} />
<main class="flex-1 app-main" data-has-track={hasTrack ? '' : undefined}>
{#key page.url.pathname}
<div class="animate-fade">{@render children()}</div>
{/key}
</main>
<div class="flex min-w-0 flex-1 flex-col">
<div class="flex min-h-0 flex-1">
<div bind:this={scroller} class="flex min-w-0 flex-1 app-scroll flex-col">
<MobileHeader user={data.user} accountUrl={data.accountUrl} />
<TopBar user={data.user} accountUrl={data.accountUrl} />
<main class="flex-1">
{#key page.url.pathname}
<div class="animate-fade">{@render children()}</div>
{/key}
</main>
</div>
<Queue />
</div>
<Queue />
<PlayerDock user={data.user} />
</div>
<PlayerDock user={data.user} />
<Modal
open={createPlaylistModal.open}
onClose={() => createPlaylistModal.close()}
+6 -13
View File
@@ -98,10 +98,6 @@
}
}
@utility scrollbar-stable {
scrollbar-gutter: stable;
}
@utility text-display-1 {
@apply font-display text-3xl font-medium tracking-tight sm:text-4xl;
}
@@ -131,7 +127,6 @@
transition-duration: var(--default-transition-duration);
}
@utility glass-bar {
background-color: var(--mf-bg);
border-top: 1px solid var(--mf-hairline);
}
@utility grid-cards {
@@ -224,14 +219,12 @@
margin-bottom: --spacing(14);
}
}
@utility app-main {
--mf-dock-h: 0px;
padding-bottom: calc(var(--mf-nav-h) + var(--mf-safe-b) + var(--mf-dock-h));
transition: padding var(--mf-motion-slow) var(--mf-ease);
&[data-has-track] {
--mf-dock-h: var(--mf-player-h);
}
@utility app-scroll {
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
scrollbar-width: thin;
scrollbar-color: var(--mf-scrollbar) transparent;
}
@utility genre-tile {
background: linear-gradient(