feat(immersive): create unified ImmersivePlayer component
New component architecture: - ImmersivePlayer.svelte: Main container with tab system - ImmersiveHeader.svelte: Tabs (lyrics/credits/suggestions/visualizer) - ImmersiveControls.svelte: Bottom bar with all playback controls - ImmersiveArtwork.svelte: Album art with floating/vinyl variants - ImmersiveBackground.svelte: Optimized blur background (from Phase 1) Panel components: - LyricsPanel: Wrapper for existing LyricsLines - CreditsPanel: Placeholder (Phase 3) - SuggestionsPanel: Placeholder (Phase 4) - VisualizerPanel: Placeholder (Phase 5) Shared components: - ProgressBar: Reusable seek bar with keyboard support - VolumeSlider: Reusable volume control Features: - Tab-based navigation (L/C/S/V keyboard shortcuts) - Auto-hide UI after 4 seconds of inactivity - Full keyboard control (Space, Arrows, Escape) - Responsive layout (desktop/tablet/mobile) - Accessibility: ARIA roles, focus management
This commit is contained in:
1 parent
513614466e
commit
7c9f1820f8
11 files changed
+1730
No files matched your search
@@ -0,0 +1,131 @@
|
||||
<script lang="ts">
|
||||
import LyricsLines from '../../lyrics/LyricsLines.svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
interface LyricsLine {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
lines: LyricsLine[];
|
||||
activeIndex?: number;
|
||||
activeProgress?: number;
|
||||
isSynced?: boolean;
|
||||
isLoading?: boolean;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
let {
|
||||
lines = [],
|
||||
activeIndex = -1,
|
||||
activeProgress = 0,
|
||||
isSynced = false,
|
||||
isLoading = false,
|
||||
error = null
|
||||
}: Props = $props();
|
||||
|
||||
const hasLyrics = $derived(lines.length > 0);
|
||||
</script>
|
||||
|
||||
<div class="lyrics-panel">
|
||||
{#if isLoading}
|
||||
<div class="lyrics-state">
|
||||
<div class="spinner"></div>
|
||||
<span>{$t('player.fetchingLyrics')}</span>
|
||||
</div>
|
||||
{:else if error}
|
||||
<div class="lyrics-state">
|
||||
<span class="error-text">{error}</span>
|
||||
</div>
|
||||
{:else if hasLyrics}
|
||||
<div class="lyrics-container">
|
||||
<LyricsLines
|
||||
{lines}
|
||||
{activeIndex}
|
||||
{activeProgress}
|
||||
{isSynced}
|
||||
center={false}
|
||||
compact={false}
|
||||
immersive={true}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="lyrics-state">
|
||||
<span class="no-lyrics">{$t('player.noLyrics') || 'No lyrics available'}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.lyrics-panel {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.lyrics-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
black 8%,
|
||||
black 85%,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
black 8%,
|
||||
black 85%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.lyrics-container :global(.lyrics-lines) {
|
||||
--text-primary: var(--alpha-95, rgba(255, 255, 255, 0.95));
|
||||
--text-secondary: var(--alpha-50, rgba(255, 255, 255, 0.5));
|
||||
--text-muted: var(--alpha-25, rgba(255, 255, 255, 0.25));
|
||||
--bg-tertiary: var(--alpha-8, rgba(255, 255, 255, 0.08));
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.lyrics-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
color: var(--alpha-50, rgba(255, 255, 255, 0.5));
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: var(--alpha-40, rgba(255, 255, 255, 0.4));
|
||||
}
|
||||
|
||||
.no-lyrics {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--alpha-20, rgba(255, 255, 255, 0.2));
|
||||
border-top-color: var(--alpha-80, rgba(255, 255, 255, 0.8));
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user