Ajout d'un padding de 10px à gauche et à droite dans la liste des pistes pour améliorer l'espacement et l'alignement visuel.
774 lines
20 KiB
Vue
774 lines
20 KiB
Vue
<script setup lang="ts">
|
|
import { computed, toRef, ref } from "vue";
|
|
import { useRenderer } from "@/composables/useRenderers";
|
|
import { useUIStore } from "@/stores/ui";
|
|
import { api } from "@/services/pmocontrol/api";
|
|
import { Music, X } from "lucide-vue-next";
|
|
|
|
const props = defineProps<{
|
|
rendererId: string;
|
|
}>();
|
|
|
|
const { state } = useRenderer(toRef(props, "rendererId"));
|
|
const uiStore = useUIStore();
|
|
const metadata = computed(() => state.value?.current_track);
|
|
const isSeeking = ref(false);
|
|
const isDragging = ref(false);
|
|
const dragProgress = ref(0);
|
|
const seekTargetMs = ref<number | null>(null);
|
|
|
|
// État pour l'overlay de la cover
|
|
const showCoverOverlay = ref(false);
|
|
const showMetadata = ref(false);
|
|
|
|
function openCoverOverlay() {
|
|
if (hasCover.value) {
|
|
showCoverOverlay.value = true;
|
|
showMetadata.value = false; // Reset metadata visibility when opening
|
|
}
|
|
}
|
|
|
|
function closeCoverOverlay() {
|
|
showCoverOverlay.value = false;
|
|
showMetadata.value = false;
|
|
}
|
|
|
|
// Détecter le clic sur la partie basse de l'image
|
|
function handleOverlayContentClick(event: MouseEvent) {
|
|
const target = event.currentTarget as HTMLElement;
|
|
const rect = target.getBoundingClientRect();
|
|
const clickY = event.clientY - rect.top;
|
|
const height = rect.height;
|
|
|
|
// Si le clic est dans le tiers inférieur, toggle les métadonnées
|
|
if (clickY > height * 0.66) {
|
|
showMetadata.value = !showMetadata.value;
|
|
}
|
|
}
|
|
|
|
// Calcul du pourcentage de progression
|
|
const progressPercent = computed(() => {
|
|
// Si on est en train de drag, utiliser la position du drag
|
|
if (isDragging.value) {
|
|
return dragProgress.value;
|
|
}
|
|
|
|
// Si on a une cible de seek active
|
|
if (seekTargetMs.value !== null) {
|
|
const currentPosition = state.value?.position_ms || 0;
|
|
|
|
// Si la position actuelle est proche de la cible (tolérance de 2 secondes)
|
|
if (Math.abs(currentPosition - seekTargetMs.value) < 2000) {
|
|
// On peut libérer le contrôle
|
|
seekTargetMs.value = null;
|
|
dragProgress.value = 0;
|
|
} else {
|
|
// Sinon, continuer à afficher la position cible
|
|
return dragProgress.value;
|
|
}
|
|
}
|
|
|
|
const position = state.value?.position_ms;
|
|
const duration = state.value?.duration_ms;
|
|
if (position && duration && duration > 0) {
|
|
return (position / duration) * 100;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
// Formater la durée en MM:SS
|
|
function formatTime(ms: number | null | undefined): string {
|
|
if (!ms) return "--:--";
|
|
const totalSeconds = Math.floor(ms / 1000);
|
|
const minutes = Math.floor(totalSeconds / 60);
|
|
const seconds = totalSeconds % 60;
|
|
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
}
|
|
|
|
const currentTime = computed(() => formatTime(state.value?.position_ms));
|
|
const totalTime = computed(() => formatTime(state.value?.duration_ms));
|
|
|
|
const hasCover = computed(() => !!metadata.value?.album_art_uri);
|
|
|
|
function handleImageError(event: Event) {
|
|
const img = event.target as HTMLImageElement;
|
|
img.style.display = "none";
|
|
}
|
|
|
|
// Calculer le pourcentage à partir d'une position X dans la barre
|
|
function calculateProgressFromX(
|
|
clientX: number,
|
|
progressBar: HTMLElement,
|
|
): number {
|
|
const rect = progressBar.getBoundingClientRect();
|
|
const x = Math.max(0, Math.min(clientX - rect.left, rect.width));
|
|
return (x / rect.width) * 100;
|
|
}
|
|
|
|
// Gestion du drag sur la progress bar
|
|
function handleProgressBarMouseDown(event: MouseEvent) {
|
|
const duration = state.value?.duration_ms;
|
|
if (!duration || duration === 0 || isSeeking.value) return;
|
|
|
|
const progressBar = event.currentTarget as HTMLElement;
|
|
isDragging.value = true;
|
|
dragProgress.value = calculateProgressFromX(event.clientX, progressBar);
|
|
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
if (!isDragging.value) return;
|
|
dragProgress.value = calculateProgressFromX(e.clientX, progressBar);
|
|
};
|
|
|
|
const handleMouseUp = async (e: MouseEvent) => {
|
|
if (!isDragging.value) return;
|
|
|
|
const finalPercent = calculateProgressFromX(e.clientX, progressBar);
|
|
const newPositionMs = (finalPercent / 100) * duration;
|
|
const newPositionSeconds = Math.floor(newPositionMs / 1000);
|
|
|
|
// Garder dragProgress à jour et définir la cible
|
|
dragProgress.value = finalPercent;
|
|
seekTargetMs.value = newPositionMs;
|
|
|
|
// D'abord arrêter le drag pour éviter les mouvements pendant le seek
|
|
isDragging.value = false;
|
|
|
|
try {
|
|
isSeeking.value = true;
|
|
await api.seekTo(props.rendererId, newPositionSeconds);
|
|
} catch (error) {
|
|
uiStore.notifyError(
|
|
`Impossible de seek: ${error instanceof Error ? error.message : "Erreur inconnue"}`,
|
|
);
|
|
// En cas d'erreur, réinitialiser
|
|
seekTargetMs.value = null;
|
|
dragProgress.value = 0;
|
|
} finally {
|
|
isSeeking.value = false;
|
|
}
|
|
|
|
document.removeEventListener("mousemove", handleMouseMove);
|
|
document.removeEventListener("mouseup", handleMouseUp);
|
|
};
|
|
|
|
document.addEventListener("mousemove", handleMouseMove);
|
|
document.addEventListener("mouseup", handleMouseUp);
|
|
}
|
|
|
|
// Gestion du seek sur la progress bar (clic simple)
|
|
async function handleProgressBarClick(event: MouseEvent) {
|
|
// Ne rien faire si on vient de drag (géré par mouseup)
|
|
if (isDragging.value) return;
|
|
|
|
const duration = state.value?.duration_ms;
|
|
if (!duration || duration === 0 || isSeeking.value) return;
|
|
|
|
const progressBar = event.currentTarget as HTMLElement;
|
|
const percent = calculateProgressFromX(event.clientX, progressBar);
|
|
const newPositionMs = (percent / 100) * duration;
|
|
const newPositionSeconds = Math.floor(newPositionMs / 1000);
|
|
|
|
// Définir la cible et la position visuelle
|
|
dragProgress.value = percent;
|
|
seekTargetMs.value = newPositionMs;
|
|
|
|
try {
|
|
isSeeking.value = true;
|
|
await api.seekTo(props.rendererId, newPositionSeconds);
|
|
} catch (error) {
|
|
uiStore.notifyError(
|
|
`Impossible de seek: ${error instanceof Error ? error.message : "Erreur inconnue"}`,
|
|
);
|
|
// En cas d'erreur, réinitialiser
|
|
seekTargetMs.value = null;
|
|
dragProgress.value = 0;
|
|
} finally {
|
|
isSeeking.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="current-track">
|
|
<!-- Cover Art -->
|
|
<div
|
|
class="cover-container"
|
|
:class="{ clickable: hasCover }"
|
|
@click="openCoverOverlay"
|
|
>
|
|
<img
|
|
v-if="hasCover"
|
|
:src="metadata?.album_art_uri!"
|
|
:alt="metadata?.album || 'Album cover'"
|
|
class="cover-image"
|
|
loading="lazy"
|
|
@error="handleImageError"
|
|
/>
|
|
<div v-else class="cover-placeholder">
|
|
<Music :size="64" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metadata -->
|
|
<div class="metadata">
|
|
<h2 class="title">{{ metadata?.title || "Aucun titre" }}</h2>
|
|
<p class="artist">{{ metadata?.artist || "Artiste inconnu" }}</p>
|
|
<p class="album" v-if="metadata?.album">{{ metadata.album }}</p>
|
|
</div>
|
|
|
|
<!-- Progress Bar -->
|
|
<div class="progress-section">
|
|
<div
|
|
class="progress-bar"
|
|
@click="handleProgressBarClick"
|
|
@mousedown="handleProgressBarMouseDown"
|
|
:class="{ seeking: isSeeking, dragging: isDragging }"
|
|
>
|
|
<div
|
|
class="progress-bar-fill"
|
|
:style="{ width: `${progressPercent}%` }"
|
|
>
|
|
<div class="progress-bar-thumb"></div>
|
|
</div>
|
|
</div>
|
|
<div class="time-display">
|
|
<span>{{ currentTime }}</span>
|
|
<span>{{ totalTime }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Overlay cover en grand avec effet glassmorphism -->
|
|
<Teleport to="body">
|
|
<Transition name="cover-overlay">
|
|
<div
|
|
v-if="showCoverOverlay"
|
|
class="cover-overlay"
|
|
@click="closeCoverOverlay"
|
|
>
|
|
<div
|
|
class="cover-overlay-content"
|
|
@click.stop="handleOverlayContentClick"
|
|
>
|
|
<button
|
|
class="cover-overlay-close"
|
|
@click="closeCoverOverlay"
|
|
title="Fermer"
|
|
>
|
|
<X :size="24" />
|
|
</button>
|
|
<img
|
|
v-if="hasCover"
|
|
:src="metadata?.album_art_uri!"
|
|
:alt="metadata?.album || 'Album cover'"
|
|
class="cover-overlay-image"
|
|
/>
|
|
<Transition name="metadata-fade">
|
|
<div
|
|
v-if="metadata && showMetadata"
|
|
class="cover-overlay-metadata"
|
|
>
|
|
<h2>{{ metadata.title }}</h2>
|
|
<p class="artist">{{ metadata.artist }}</p>
|
|
<p v-if="metadata.album" class="album">
|
|
{{ metadata.album }}
|
|
</p>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.current-track {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-lg);
|
|
}
|
|
|
|
.cover-container {
|
|
width: 100%;
|
|
aspect-ratio: 1;
|
|
max-width: 300px;
|
|
margin: 0 auto;
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
background-color: var(--color-bg-secondary);
|
|
box-shadow: var(--shadow-lg);
|
|
transition:
|
|
transform 0.3s ease,
|
|
box-shadow 0.3s ease;
|
|
}
|
|
|
|
.cover-container.clickable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cover-container.clickable:hover {
|
|
transform: scale(1.02);
|
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.cover-image {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.cover-placeholder {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--color-text-tertiary);
|
|
}
|
|
|
|
.metadata {
|
|
text-align: center;
|
|
}
|
|
|
|
.title {
|
|
font-size: var(--text-2xl);
|
|
font-weight: 700;
|
|
color: var(--color-text);
|
|
margin: 0 0 var(--spacing-sm);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.artist {
|
|
font-size: var(--text-lg);
|
|
color: var(--color-text-secondary);
|
|
margin: 0 0 var(--spacing-xs);
|
|
}
|
|
|
|
.album {
|
|
font-size: var(--text-base);
|
|
color: var(--color-text-tertiary);
|
|
margin: 0;
|
|
}
|
|
|
|
.progress-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-xs);
|
|
padding: 0 10px;
|
|
}
|
|
|
|
.progress-bar {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 6px;
|
|
background: linear-gradient(
|
|
to bottom,
|
|
rgba(255, 255, 255, 0.15) 0%,
|
|
rgba(255, 255, 255, 0.25) 50%,
|
|
rgba(255, 255, 255, 0.15) 100%
|
|
);
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
transition: background 0.2s ease;
|
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
overflow: visible;
|
|
}
|
|
|
|
.progress-bar:hover {
|
|
background: linear-gradient(
|
|
to bottom,
|
|
rgba(255, 255, 255, 0.2) 0%,
|
|
rgba(255, 255, 255, 0.35) 50%,
|
|
rgba(255, 255, 255, 0.2) 100%
|
|
);
|
|
}
|
|
|
|
.progress-bar.dragging {
|
|
cursor: grabbing;
|
|
background: linear-gradient(
|
|
to bottom,
|
|
rgba(255, 255, 255, 0.2) 0%,
|
|
rgba(255, 255, 255, 0.35) 50%,
|
|
rgba(255, 255, 255, 0.2) 100%
|
|
);
|
|
}
|
|
|
|
.progress-bar.seeking {
|
|
cursor: wait;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.progress-bar-fill {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #059669 0%, #10b981 50%, #34d399 100%);
|
|
border-radius: 3px;
|
|
transition: width 0.1s linear;
|
|
z-index: 1;
|
|
box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);
|
|
overflow: visible;
|
|
}
|
|
|
|
.progress-bar.dragging .progress-bar-fill {
|
|
transition: width 0s;
|
|
box-shadow: 0 0 12px rgba(16, 185, 129, 0.8);
|
|
}
|
|
|
|
.progress-bar-thumb {
|
|
position: absolute;
|
|
right: -10px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 20px;
|
|
height: 20px;
|
|
background: white;
|
|
border: 3px solid rgba(0, 0, 0, 0.8);
|
|
border-radius: 50%;
|
|
box-shadow:
|
|
0 2px 8px rgba(0, 0, 0, 0.6),
|
|
inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
transition: all 0.2s ease;
|
|
z-index: 2;
|
|
cursor: grab;
|
|
}
|
|
|
|
.progress-bar:hover .progress-bar-thumb {
|
|
transform: translateY(-50%) scale(1.2);
|
|
box-shadow:
|
|
0 3px 12px rgba(0, 0, 0, 0.7),
|
|
inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.progress-bar.dragging .progress-bar-thumb {
|
|
transform: translateY(-50%) scale(1.3);
|
|
cursor: grabbing;
|
|
box-shadow:
|
|
0 4px 16px rgba(0, 0, 0, 0.8),
|
|
inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.time-display {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: var(--text-sm);
|
|
color: var(--color-text-secondary);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (min-width: 768px) {
|
|
.cover-container {
|
|
max-width: 250px;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.cover-container {
|
|
max-width: 300px;
|
|
}
|
|
}
|
|
|
|
/* Mode kiosque - compactage pour petites hauteurs (800x600) */
|
|
@media (max-height: 700px) and (orientation: landscape) {
|
|
.current-track {
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.cover-container {
|
|
max-width: 160px !important;
|
|
max-height: 160px;
|
|
}
|
|
|
|
.cover-placeholder {
|
|
font-size: 40px;
|
|
}
|
|
|
|
.metadata {
|
|
margin-top: -8px;
|
|
}
|
|
|
|
.title {
|
|
font-size: var(--text-lg);
|
|
margin: 0 0 4px;
|
|
}
|
|
|
|
.artist {
|
|
font-size: var(--text-sm);
|
|
margin: 0 0 2px;
|
|
}
|
|
|
|
.album {
|
|
font-size: var(--text-xs);
|
|
}
|
|
|
|
.progress-section {
|
|
gap: 4px;
|
|
}
|
|
|
|
.time-display {
|
|
font-size: 11px;
|
|
}
|
|
}
|
|
|
|
/* Overlay cover en grand - Liquid metal style */
|
|
.cover-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 1000;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
backdrop-filter: blur(20px) saturate(150%);
|
|
-webkit-backdrop-filter: blur(20px) saturate(150%);
|
|
padding: 16px;
|
|
}
|
|
|
|
.cover-overlay-content {
|
|
position: relative;
|
|
width: calc(100vw - 32px);
|
|
height: calc(100vh - 32px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
backdrop-filter: blur(40px) saturate(180%);
|
|
-webkit-backdrop-filter: blur(40px) saturate(180%);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 24px;
|
|
box-shadow:
|
|
0 20px 60px rgba(0, 0, 0, 0.5),
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
|
inset 0 -1px 0 rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
.cover-overlay-content {
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-color: rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
.cover-overlay-metadata {
|
|
background: rgba(0, 0, 0, 0.7);
|
|
}
|
|
}
|
|
|
|
.cover-overlay-close {
|
|
position: absolute;
|
|
top: var(--spacing-md);
|
|
right: var(--spacing-md);
|
|
width: 48px;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
color: var(--color-text);
|
|
transition: all 0.3s ease;
|
|
z-index: 10;
|
|
}
|
|
|
|
.cover-overlay-close:hover {
|
|
background: rgba(255, 255, 255, 0.3);
|
|
transform: scale(1.1) rotate(90deg);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.cover-overlay-close:active {
|
|
transform: scale(1) rotate(90deg);
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
.cover-overlay-close {
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.cover-overlay-close:hover {
|
|
background: rgba(0, 0, 0, 0.5);
|
|
}
|
|
}
|
|
|
|
.cover-overlay-image {
|
|
width: calc(100vw - 64px);
|
|
height: calc(100vh - 64px);
|
|
border-radius: 16px;
|
|
box-shadow:
|
|
0 30px 80px rgba(0, 0, 0, 0.6),
|
|
0 0 2px rgba(255, 255, 255, 0.2);
|
|
object-fit: contain;
|
|
}
|
|
|
|
.cover-overlay-metadata {
|
|
position: absolute;
|
|
bottom: 24px;
|
|
left: 24px;
|
|
right: 24px;
|
|
text-align: center;
|
|
padding: var(--spacing-lg);
|
|
background: rgba(0, 0, 0, 0.6);
|
|
backdrop-filter: blur(30px) saturate(180%);
|
|
-webkit-backdrop-filter: blur(30px) saturate(180%);
|
|
border-radius: 20px;
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.cover-overlay-metadata h2 {
|
|
font-size: clamp(1.5rem, 4vw, 2.5rem);
|
|
font-weight: 700;
|
|
color: white;
|
|
margin: 0;
|
|
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.8);
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.cover-overlay-metadata .artist {
|
|
font-size: clamp(1rem, 2.5vw, 1.5rem);
|
|
font-weight: 500;
|
|
color: rgba(255, 255, 255, 0.9);
|
|
margin: 8px 0 0 0;
|
|
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
|
|
padding-top: 8px;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.cover-overlay-metadata .album {
|
|
font-size: clamp(0.875rem, 2vw, 1.125rem);
|
|
font-weight: 400;
|
|
font-style: italic;
|
|
color: rgba(255, 255, 255, 0.75);
|
|
margin: 6px 0 0 0;
|
|
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
/* Animations de transition */
|
|
.cover-overlay-enter-active,
|
|
.cover-overlay-leave-active {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.cover-overlay-enter-active .cover-overlay-content,
|
|
.cover-overlay-leave-active .cover-overlay-content {
|
|
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
.cover-overlay-enter-from,
|
|
.cover-overlay-leave-to {
|
|
opacity: 0;
|
|
backdrop-filter: blur(0px);
|
|
-webkit-backdrop-filter: blur(0px);
|
|
}
|
|
|
|
.cover-overlay-enter-from .cover-overlay-content,
|
|
.cover-overlay-leave-to .cover-overlay-content {
|
|
opacity: 0;
|
|
transform: scale(0.9);
|
|
}
|
|
|
|
.cover-overlay-enter-to,
|
|
.cover-overlay-leave-from {
|
|
opacity: 1;
|
|
}
|
|
|
|
.cover-overlay-enter-to .cover-overlay-content,
|
|
.cover-overlay-leave-from .cover-overlay-content {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
/* Responsive overlay */
|
|
@media (max-width: 768px) {
|
|
.cover-overlay {
|
|
padding: 12px;
|
|
}
|
|
|
|
.cover-overlay-content {
|
|
width: calc(100vw - 24px);
|
|
height: calc(100vh - 24px);
|
|
border-radius: 16px;
|
|
}
|
|
|
|
.cover-overlay-image {
|
|
width: calc(100vw - 48px);
|
|
height: calc(100vh - 48px);
|
|
}
|
|
|
|
.cover-overlay-metadata {
|
|
bottom: 16px;
|
|
left: 16px;
|
|
right: 16px;
|
|
padding: var(--spacing-md);
|
|
}
|
|
|
|
.cover-overlay-close {
|
|
width: 40px;
|
|
height: 40px;
|
|
top: 12px;
|
|
right: 12px;
|
|
}
|
|
}
|
|
|
|
/* Mode kiosque - overlay adapté */
|
|
@media (max-height: 700px) and (orientation: landscape) {
|
|
.cover-overlay {
|
|
padding: 12px;
|
|
}
|
|
|
|
.cover-overlay-content {
|
|
width: calc(100vw - 24px);
|
|
height: calc(100vh - 24px);
|
|
}
|
|
|
|
.cover-overlay-image {
|
|
width: calc(100vw - 48px);
|
|
height: calc(100vh - 48px);
|
|
}
|
|
|
|
.cover-overlay-metadata {
|
|
bottom: 12px;
|
|
left: 12px;
|
|
right: 12px;
|
|
padding: var(--spacing-sm);
|
|
}
|
|
|
|
.cover-overlay-metadata .artist {
|
|
padding-top: 4px;
|
|
}
|
|
|
|
.cover-overlay-metadata .album {
|
|
display: none; /* Masquer l'album en mode kiosque pour plus d'espace */
|
|
}
|
|
}
|
|
|
|
/* Transition pour les métadonnées */
|
|
.metadata-fade-enter-active,
|
|
.metadata-fade-leave-active {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.metadata-fade-enter-from,
|
|
.metadata-fade-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
|
|
.metadata-fade-enter-to,
|
|
.metadata-fade-leave-from {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
</style>
|