Merge pull request #15 from JulienMaille/cover-color
Cover color/background and full screen cover option
This commit is contained in:
commit
3db7a04971
7 changed files with 455 additions and 20 deletions
35
index.html
35
index.html
|
|
@ -35,6 +35,25 @@
|
|||
<div id="queue-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fullscreen-cover-overlay" style="display: none;">
|
||||
<div class="fullscreen-cover-content">
|
||||
<button id="close-fullscreen-cover-btn" title="Close">×</button>
|
||||
<img id="fullscreen-cover-image" src="" alt="Album Cover">
|
||||
<div class="fullscreen-track-info">
|
||||
<h2 id="fullscreen-track-title"></h2>
|
||||
<h3 id="fullscreen-track-artist"></h3>
|
||||
<div id="fullscreen-next-track" style="display: none;">
|
||||
<span class="label">Up Next: </span>
|
||||
<span class="value"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fullscreen-controls">
|
||||
<!-- Controls will be cloned or managed here if needed, or we just rely on main controls -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="queue-track-menu" id="queue-track-menu">
|
||||
<ul>
|
||||
<li data-action="remove">Remove from Queue</li>
|
||||
|
|
@ -88,6 +107,7 @@
|
|||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div id="page-background"></div>
|
||||
<header class="main-header">
|
||||
<button class="hamburger-menu" id="hamburger-btn" title="Open navigation">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
|
@ -185,7 +205,7 @@
|
|||
</svg>
|
||||
<span>Play</span>
|
||||
</button>
|
||||
<button id="download-playlist-btn" class="btn-secondary">
|
||||
<button id="download-playlist-btn" class="btn-primary">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
|
|
@ -298,7 +318,8 @@
|
|||
<span class="description">Choose what shows when you click the album art</span>
|
||||
</div>
|
||||
<select id="now-playing-mode">
|
||||
<option value="cover">Album Cover</option>
|
||||
<option value="album">Show Album</option>
|
||||
<option value="cover">Enlarged Cover</option>
|
||||
<option value="lyrics">Lyrics Panel</option>
|
||||
<option value="karaoke">Karaoke Mode</option>
|
||||
</select>
|
||||
|
|
@ -313,6 +334,16 @@
|
|||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="info">
|
||||
<span class="label">Album Cover Background</span>
|
||||
<span class="description">Use the album cover as a blurred background on album pages</span>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="album-background-toggle">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="info">
|
||||
<span class="label">Gapless Playback</span>
|
||||
|
|
|
|||
31
js/app.js
31
js/app.js
|
|
@ -251,9 +251,30 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
// Clear sync when hiding
|
||||
clearLyricsPanelSync(audioPlayer, lyricsPanel);
|
||||
}
|
||||
} else if (mode === 'cover') {
|
||||
const overlay = document.getElementById('fullscreen-cover-overlay');
|
||||
if (overlay && overlay.style.display === 'flex') {
|
||||
ui.closeFullscreenCover();
|
||||
} else {
|
||||
const nextTrack = player.getNextTrack();
|
||||
ui.showFullscreenCover(player.currentTrack, nextTrack);
|
||||
}
|
||||
} else {
|
||||
// Default to 'album' mode - navigate to album
|
||||
if (player.currentTrack.album?.id) {
|
||||
window.location.hash = `#album/${player.currentTrack.album.id}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('close-fullscreen-cover-btn')?.addEventListener('click', () => {
|
||||
ui.closeFullscreenCover();
|
||||
});
|
||||
|
||||
document.getElementById('fullscreen-cover-image')?.addEventListener('click', () => {
|
||||
ui.closeFullscreenCover();
|
||||
});
|
||||
|
||||
document.getElementById('close-lyrics-btn')?.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
lyricsPanel.classList.add('hidden');
|
||||
|
|
@ -278,6 +299,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
audioPlayer.addEventListener('play', async () => {
|
||||
if (!player.currentTrack) return;
|
||||
|
||||
// Update UI with current track info for theme
|
||||
ui.setCurrentTrack(player.currentTrack);
|
||||
|
||||
const currentTrackId = player.currentTrack.id;
|
||||
if (currentTrackId === previousTrackId) return;
|
||||
previousTrackId = currentTrackId;
|
||||
|
|
@ -301,6 +325,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update Fullscreen/Enlarged Cover if it's open
|
||||
const fullscreenOverlay = document.getElementById('fullscreen-cover-overlay');
|
||||
if (fullscreenOverlay && getComputedStyle(fullscreenOverlay).display !== 'none') {
|
||||
const nextTrack = player.getNextTrack();
|
||||
ui.showFullscreenCover(player.currentTrack, nextTrack);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', async (e) => {
|
||||
|
|
|
|||
13
js/player.js
13
js/player.js
|
|
@ -366,6 +366,19 @@ export class Player {
|
|||
return this.shuffleActive ? this.shuffledQueue : this.queue;
|
||||
}
|
||||
|
||||
getNextTrack() {
|
||||
const currentQueue = this.getCurrentQueue();
|
||||
if (this.currentQueueIndex === -1 || currentQueue.length === 0) return null;
|
||||
|
||||
const nextIndex = this.currentQueueIndex + 1;
|
||||
if (nextIndex < currentQueue.length) {
|
||||
return currentQueue[nextIndex];
|
||||
} else if (this.repeatMode === REPEAT_MODE.ALL) {
|
||||
return currentQueue[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
updatePlayingTrackIndicator() {
|
||||
const currentTrack = this.getCurrentQueue()[this.currentQueueIndex];
|
||||
document.querySelectorAll('.track-item').forEach(item => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//js/settings
|
||||
import { themeManager, lastFMStorage, nowPlayingSettings, lyricsSettings } from './storage.js';
|
||||
import { themeManager, lastFMStorage, nowPlayingSettings, lyricsSettings, backgroundSettings } from './storage.js';
|
||||
|
||||
export function initializeSettings(scrobbler, player, api, ui) {
|
||||
const lastfmConnectBtn = document.getElementById('lastfm-connect-btn');
|
||||
|
|
@ -185,6 +185,15 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
|||
});
|
||||
}
|
||||
|
||||
// Album Background Toggle
|
||||
const albumBackgroundToggle = document.getElementById('album-background-toggle');
|
||||
if (albumBackgroundToggle) {
|
||||
albumBackgroundToggle.checked = backgroundSettings.isEnabled();
|
||||
albumBackgroundToggle.addEventListener('change', (e) => {
|
||||
backgroundSettings.setEnabled(e.target.checked);
|
||||
});
|
||||
}
|
||||
|
||||
// Filename template setting
|
||||
const filenameTemplate = document.getElementById('filename-template');
|
||||
if (filenameTemplate) {
|
||||
|
|
|
|||
|
|
@ -305,9 +305,9 @@ export const nowPlayingSettings = {
|
|||
|
||||
getMode() {
|
||||
try {
|
||||
return localStorage.getItem(this.STORAGE_KEY) || 'cover';
|
||||
return localStorage.getItem(this.STORAGE_KEY) || 'album';
|
||||
} catch (e) {
|
||||
return 'cover';
|
||||
return 'album';
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -332,6 +332,23 @@ export const lyricsSettings = {
|
|||
}
|
||||
};
|
||||
|
||||
export const backgroundSettings = {
|
||||
STORAGE_KEY: 'album-background-enabled',
|
||||
|
||||
isEnabled() {
|
||||
try {
|
||||
// Default to true if not set
|
||||
return localStorage.getItem(this.STORAGE_KEY) !== 'false';
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
setEnabled(enabled) {
|
||||
localStorage.setItem(this.STORAGE_KEY, enabled ? 'true' : 'false');
|
||||
}
|
||||
};
|
||||
|
||||
export const queueManager = {
|
||||
STORAGE_KEY: 'monochrome-queue',
|
||||
|
||||
|
|
|
|||
147
js/ui.js
147
js/ui.js
|
|
@ -1,10 +1,37 @@
|
|||
//js/ui.js
|
||||
import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists, getTrackTitle, calculateTotalDuration, formatDuration } from './utils.js';
|
||||
import { recentActivityManager } from './storage.js';
|
||||
import { recentActivityManager, backgroundSettings } from './storage.js';
|
||||
|
||||
export class UIRenderer {
|
||||
constructor(api) {
|
||||
this.api = api;
|
||||
this.currentTrack = null;
|
||||
}
|
||||
|
||||
setCurrentTrack(track) {
|
||||
this.currentTrack = track;
|
||||
this.updateGlobalTheme();
|
||||
}
|
||||
|
||||
updateGlobalTheme() {
|
||||
// If the album background setting is disabled, we don't do global coloring
|
||||
// except possibly for the album page which handles its own check.
|
||||
// But here we are handling the "not on album page" case or general updates.
|
||||
|
||||
// Check if we are currently viewing an album page
|
||||
const isAlbumPage = document.getElementById('page-album').classList.contains('active');
|
||||
|
||||
if (isAlbumPage) {
|
||||
// The album page render logic handles its own coloring.
|
||||
// We shouldn't override it here.
|
||||
return;
|
||||
}
|
||||
|
||||
if (backgroundSettings.isEnabled() && this.currentTrack?.album?.vibrantColor) {
|
||||
this.setVibrantColor(this.currentTrack.album.vibrantColor);
|
||||
} else {
|
||||
this.resetVibrantColor();
|
||||
}
|
||||
}
|
||||
|
||||
createExplicitBadge() {
|
||||
|
|
@ -184,6 +211,107 @@ export class UIRenderer {
|
|||
});
|
||||
}
|
||||
|
||||
setPageBackground(imageUrl) {
|
||||
const bgElement = document.getElementById('page-background');
|
||||
if (backgroundSettings.isEnabled() && imageUrl) {
|
||||
bgElement.style.backgroundImage = `url('${imageUrl}')`;
|
||||
bgElement.classList.add('active');
|
||||
document.body.classList.add('has-page-background');
|
||||
} else {
|
||||
bgElement.classList.remove('active');
|
||||
document.body.classList.remove('has-page-background');
|
||||
// Delay clearing the image to allow transition
|
||||
setTimeout(() => {
|
||||
if (!bgElement.classList.contains('active')) {
|
||||
bgElement.style.backgroundImage = '';
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
setVibrantColor(color) {
|
||||
if (!color) return;
|
||||
|
||||
const root = document.documentElement;
|
||||
|
||||
// Calculate contrast text color
|
||||
const hex = color.replace('#', '');
|
||||
const r = parseInt(hex.substr(0, 2), 16);
|
||||
const g = parseInt(hex.substr(2, 2), 16);
|
||||
const b = parseInt(hex.substr(4, 2), 16);
|
||||
const brightness = ((r * 299) + (g * 587) + (b * 114)) / 1000;
|
||||
const foreground = brightness > 128 ? '#000000' : '#ffffff';
|
||||
|
||||
// Set global CSS variables
|
||||
root.style.setProperty('--primary', color);
|
||||
root.style.setProperty('--primary-foreground', foreground);
|
||||
root.style.setProperty('--highlight', color);
|
||||
root.style.setProperty('--highlight-rgb', `${r}, ${g}, ${b}`);
|
||||
root.style.setProperty('--active-highlight', color);
|
||||
root.style.setProperty('--ring', color);
|
||||
|
||||
// Calculate a safe hover color (darken if too light)
|
||||
let hoverColor;
|
||||
if (brightness > 200) {
|
||||
const dr = Math.floor(r * 0.85);
|
||||
const dg = Math.floor(g * 0.85);
|
||||
const db = Math.floor(b * 0.85);
|
||||
hoverColor = `rgba(${dr}, ${dg}, ${db}, 0.25)`;
|
||||
} else {
|
||||
hoverColor = `rgba(${r}, ${g}, ${b}, 0.15)`;
|
||||
}
|
||||
root.style.setProperty('--track-hover-bg', hoverColor);
|
||||
}
|
||||
|
||||
resetVibrantColor() {
|
||||
const root = document.documentElement;
|
||||
root.style.removeProperty('--primary');
|
||||
root.style.removeProperty('--primary-foreground');
|
||||
root.style.removeProperty('--highlight');
|
||||
root.style.removeProperty('--highlight-rgb');
|
||||
root.style.removeProperty('--active-highlight');
|
||||
root.style.removeProperty('--ring');
|
||||
root.style.removeProperty('--track-hover-bg');
|
||||
}
|
||||
|
||||
showFullscreenCover(track, nextTrack) {
|
||||
if (!track) return;
|
||||
|
||||
const overlay = document.getElementById('fullscreen-cover-overlay');
|
||||
const image = document.getElementById('fullscreen-cover-image');
|
||||
const title = document.getElementById('fullscreen-track-title');
|
||||
const artist = document.getElementById('fullscreen-track-artist');
|
||||
const nextTrackEl = document.getElementById('fullscreen-next-track');
|
||||
|
||||
const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280');
|
||||
|
||||
image.src = coverUrl;
|
||||
title.textContent = track.title;
|
||||
artist.textContent = track.artist?.name || 'Unknown Artist';
|
||||
|
||||
if (nextTrack) {
|
||||
nextTrackEl.style.display = 'flex';
|
||||
nextTrackEl.querySelector('.value').textContent = `${nextTrack.title} • ${nextTrack.artist?.name || 'Unknown'}`;
|
||||
|
||||
// Replay animation
|
||||
nextTrackEl.classList.remove('animate-in');
|
||||
void nextTrackEl.offsetWidth; // Trigger reflow
|
||||
nextTrackEl.classList.add('animate-in');
|
||||
} else {
|
||||
nextTrackEl.style.display = 'none';
|
||||
nextTrackEl.classList.remove('animate-in');
|
||||
}
|
||||
|
||||
// Set the background image via CSS variable for the pseudo-element to use
|
||||
overlay.style.setProperty('--bg-image', `url('${coverUrl}')`);
|
||||
|
||||
overlay.style.display = 'flex';
|
||||
}
|
||||
|
||||
closeFullscreenCover() {
|
||||
document.getElementById('fullscreen-cover-overlay').style.display = 'none';
|
||||
}
|
||||
|
||||
showPage(pageId) {
|
||||
document.querySelectorAll('.page').forEach(page => {
|
||||
page.classList.toggle('active', page.id === `page-${pageId}`);
|
||||
|
|
@ -195,6 +323,12 @@ export class UIRenderer {
|
|||
|
||||
document.querySelector('.main-content').scrollTop = 0;
|
||||
|
||||
// Clear background and color if not on album page
|
||||
if (pageId !== 'album') {
|
||||
this.setPageBackground(null);
|
||||
this.updateGlobalTheme();
|
||||
}
|
||||
|
||||
if (pageId === 'settings') {
|
||||
this.renderApiSettings();
|
||||
}
|
||||
|
|
@ -331,9 +465,18 @@ export class UIRenderer {
|
|||
try {
|
||||
const { album, tracks } = await this.api.getAlbum(albumId);
|
||||
|
||||
imageEl.src = this.api.getCoverUrl(album.cover, '1280');
|
||||
const coverUrl = this.api.getCoverUrl(album.cover, '1280');
|
||||
imageEl.src = coverUrl;
|
||||
imageEl.style.backgroundColor = '';
|
||||
|
||||
// Set background and vibrant color
|
||||
this.setPageBackground(coverUrl);
|
||||
if (backgroundSettings.isEnabled() && album.vibrantColor) {
|
||||
this.setVibrantColor(album.vibrantColor);
|
||||
} else {
|
||||
this.resetVibrantColor();
|
||||
}
|
||||
|
||||
const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : '';
|
||||
titleEl.innerHTML = `${album.title} ${explicitBadge}`;
|
||||
|
||||
|
|
|
|||
217
styles.css
217
styles.css
|
|
@ -12,6 +12,7 @@
|
|||
--shadow-md: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||||
--shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
--shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.8);
|
||||
--cover-filter: blur(30px) brightness(0.3);
|
||||
}
|
||||
|
||||
:root[data-theme="monochrome"] {
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
--highlight-rgb: 255, 255, 255;
|
||||
--active-highlight: var(--highlight);
|
||||
--explicit-badge: #fafafa;
|
||||
--cover-filter: blur(30px) brightness(0.3);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
|
|
@ -133,6 +135,7 @@
|
|||
--active-highlight: var(--highlight);
|
||||
--explicit-badge: #ef4444;
|
||||
--explicit-badge-foreground: #ffffff;
|
||||
--cover-filter: blur(30px) brightness(1.6) opacity(0.85);
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
|
|
@ -206,6 +209,48 @@ kbd {
|
|||
overflow-y: auto;
|
||||
padding: var(--spacing-xl);
|
||||
scroll-behavior: smooth;
|
||||
position: relative; /* Context for background */
|
||||
}
|
||||
|
||||
/* Ensure content sits on top of background */
|
||||
.main-header,
|
||||
.page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#page-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
min-height: 400px;
|
||||
z-index: 0;
|
||||
background-size: cover;
|
||||
background-position: center 20%;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
|
||||
/* Fade out at the bottom */
|
||||
mask-image: linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0.8) 40%, rgba(0,0,0,0) 100%);
|
||||
-webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0.8) 40%, rgba(0,0,0,0) 100%);
|
||||
|
||||
/* Blur effect */
|
||||
filter: blur(50px) saturate(1.4) brightness(0.5);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#page-background.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Light mode adjustments */
|
||||
:root[data-theme="light"] #page-background {
|
||||
filter: blur(50px) saturate(1.5) brightness(1.1) opacity(0.5);
|
||||
mask-image: linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%);
|
||||
-webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%);
|
||||
}
|
||||
|
||||
.now-playing-bar {
|
||||
|
|
@ -217,6 +262,8 @@ kbd {
|
|||
grid-template-columns: 1fr 2fr 1fr;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
position: relative;
|
||||
z-index: 2100;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
|
|
@ -329,6 +376,14 @@ kbd {
|
|||
border-color: var(--ring);
|
||||
}
|
||||
|
||||
body.has-page-background .search-bar input {
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
body.has-page-background .track-item:hover {
|
||||
background-color: var(--track-hover-bg, var(--secondary));
|
||||
}
|
||||
|
||||
.page {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -1174,12 +1229,157 @@ input:checked + .slider::before {
|
|||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(4px);
|
||||
z-index: 1000;
|
||||
z-index: 3000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
animation: fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#fullscreen-cover-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: fadeIn 0.3s ease;
|
||||
overflow: hidden;
|
||||
background-color: var(--background);
|
||||
/* Use a CSS variable for the image so we can set it in JS */
|
||||
--bg-image: none;
|
||||
padding-bottom: 90px; /* Account for desktop player bar */
|
||||
}
|
||||
|
||||
#fullscreen-cover-overlay::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -20px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
filter: var(--cover-filter);
|
||||
z-index: -1;
|
||||
background-image: var(--bg-image);
|
||||
}
|
||||
|
||||
.fullscreen-cover-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* Remove fixed padding to allow flex centering to work within the overlay's padded box */
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#close-fullscreen-cover-btn {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background-color: var(--background);
|
||||
border: none;
|
||||
color: var(--foreground);
|
||||
font-size: 2rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.2s;
|
||||
z-index: 10;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#close-fullscreen-cover-btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#fullscreen-cover-image {
|
||||
max-width: 80vw;
|
||||
max-height: 60vh;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 20px 50px rgba(0,0,0,0.5);
|
||||
object-fit: contain;
|
||||
margin-bottom: 2rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.fullscreen-track-info {
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
max-width: 90%;
|
||||
background: color-mix(in srgb, var(--card), transparent 25%);
|
||||
padding: 1.5rem 2rem;
|
||||
border-radius: var(--radius);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid color-mix(in srgb, var(--border), transparent 50%);
|
||||
}
|
||||
|
||||
#fullscreen-track-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--foreground);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
#fullscreen-track-artist {
|
||||
font-size: 1.25rem;
|
||||
color: var(--muted-foreground);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#fullscreen-next-track {
|
||||
margin-top: 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted-foreground);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
opacity: 0; /* Initially hidden for animation */
|
||||
}
|
||||
|
||||
#fullscreen-next-track.animate-in {
|
||||
animation: fadeIn 0.5s ease 0.2s forwards; /* Added forwards to keep opacity 1 */
|
||||
}
|
||||
|
||||
#fullscreen-next-track .label {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#fullscreen-next-track .value {
|
||||
font-weight: 500;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#fullscreen-cover-overlay {
|
||||
padding-bottom: 160px; /* Account for taller mobile player bar */
|
||||
}
|
||||
|
||||
#fullscreen-cover-image {
|
||||
max-height: 45vh; /* Reduce height on mobile to fit text */
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
#fullscreen-track-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#fullscreen-track-artist {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
#queue-modal {
|
||||
background-color: var(--card);
|
||||
width: 90%;
|
||||
|
|
@ -2385,12 +2585,13 @@ input:checked + .slider::before {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: var(--shadow-xl);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.lyrics-panel:not(.hidden) {
|
||||
transform: translateX(0);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
.lyrics-header {
|
||||
|
|
@ -2712,16 +2913,6 @@ input:checked + .slider::before {
|
|||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
#play-playlist-btn {
|
||||
background-color: var(--primary);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
#download-playlist-btn {
|
||||
background-color: var(--secondary);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
#play-playlist-btn:hover,
|
||||
#download-playlist-btn:hover {
|
||||
transform: scale(1.05);
|
||||
|
|
|
|||
Loading…
Reference in a new issue