Merge pull request 'push-xtrnoorvumqm' (#62) from push-xtrnoorvumqm into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 28m25s
All checks were successful
Build and Push Docker Image / build (push) Successful in 28m25s
Reviewed-on: #62
This commit was merged in pull request #62.
This commit is contained in:
63
Blackboard/Done/large_cover.md
Normal file
63
Blackboard/Done/large_cover.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Tâche : Amélioration de l'overlay de couverture d'album
|
||||
|
||||
**Crate concernée** : pmoapp/webapp
|
||||
|
||||
**Statut** : Terminée
|
||||
|
||||
---
|
||||
|
||||
## Demande initiale
|
||||
|
||||
### Point 1 : Métadonnées et progress bar
|
||||
Quand on clique sur l'image de couverture de l'album, elle s'agrandit pour prendre tout l'espace. Par défaut les métadonnées sont cachées et si on clique sur l'écran, il y a un effet toggle. Il fallait que par défaut elles soient visibles, avec une progress bar draggable dans une petite boîte au fond gris transparent avec un effet glass metal à la macOS.
|
||||
|
||||
### Point 2 : Ergonomie mobile
|
||||
Le bouton de fermeture en haut à droite n'était pas ergonomique sur mobile où les doigts sont plutôt en bas de l'écran.
|
||||
|
||||
---
|
||||
|
||||
## Synthèse des modifications
|
||||
|
||||
### Fichier modifié
|
||||
- `pmoapp/webapp/src/components/pmocontrol/CurrentTrack.vue`
|
||||
|
||||
### Point 1 : Résolu
|
||||
- Métadonnées visibles par défaut à l'ouverture
|
||||
- Progress bar interactive ajoutée dans le panneau des métadonnées (souris + tactile)
|
||||
- Style glassmorphism : fond transparent 35%, blur 30px, bordure subtile, ombre
|
||||
- Panneau réduit à 70% de largeur pour un look plus élégant
|
||||
|
||||
### Point 2 : Résolu
|
||||
- **Swipe down** pour fermer l'overlay sur tactile (seuil 100px)
|
||||
- Feedback visuel pendant le geste (translation + fade)
|
||||
- Bouton X conservé pour desktop
|
||||
- Blocage du pull-to-refresh Android (`overscroll-behavior: contain`)
|
||||
|
||||
### Corrections additionnelles
|
||||
- Support tactile de la progress bar principale (hors overlay)
|
||||
- Utilisation de `100dvh` au lieu de `100vh` pour compatibilité avec les barres de navigation mobiles (Brave, Safari, Chrome)
|
||||
- Corrections TypeScript pour les événements tactiles
|
||||
|
||||
---
|
||||
|
||||
## Éléments techniques clés
|
||||
|
||||
### Swipe down
|
||||
```typescript
|
||||
const swipeStartY = ref(0);
|
||||
const swipeCurrentY = ref(0);
|
||||
const isSwiping = ref(false);
|
||||
const swipeThreshold = 100;
|
||||
```
|
||||
Listeners ajoutés sur `document` pour capturer le mouvement même sur les éléments enfants.
|
||||
|
||||
### CSS mobile
|
||||
```css
|
||||
.cover-overlay {
|
||||
overscroll-behavior: contain;
|
||||
touch-action: none;
|
||||
}
|
||||
.cover-overlay-content {
|
||||
height: calc(100dvh - 32px);
|
||||
}
|
||||
```
|
||||
89
Blackboard/Report/large_cover.md
Normal file
89
Blackboard/Report/large_cover.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Rapport : Amélioration de l'overlay de couverture d'album
|
||||
|
||||
## Résumé
|
||||
|
||||
Amélioration de l'interface de l'overlay affichant la couverture d'album en grand dans le Control Point PMOMusic, avec focus sur l'ergonomie mobile.
|
||||
|
||||
## Fichier modifié
|
||||
|
||||
- `pmoapp/webapp/src/components/pmocontrol/CurrentTrack.vue`
|
||||
|
||||
## Modifications effectuées
|
||||
|
||||
### Point 1 : Métadonnées visibles par défaut + Progress bar
|
||||
|
||||
**Problème initial** : Les métadonnées étaient cachées par défaut à l'ouverture de l'overlay.
|
||||
|
||||
**Solutions implémentées** :
|
||||
|
||||
1. **Métadonnées visibles par défaut** : `showMetadata.value = true` dans `openCoverOverlay()`
|
||||
|
||||
2. **Ajout de la progress bar** dans le panneau des métadonnées :
|
||||
- Barre de progression interactive avec thumb draggable
|
||||
- Affichage temps écoulé / durée totale
|
||||
- Support souris et tactile complet
|
||||
- Style glassmorphism (fond transparent 35%, blur, bordure subtile)
|
||||
|
||||
3. **Panneau plus discret** :
|
||||
- Transparence augmentée (35% au lieu de 60%)
|
||||
- Largeur réduite (70% de l'écran avec `left: 15%; right: 15%`)
|
||||
|
||||
### Point 2 : Fermeture ergonomique sur mobile
|
||||
|
||||
**Problème initial** : Bouton X en haut à droite difficile d'accès au pouce.
|
||||
|
||||
**Solutions implémentées** :
|
||||
|
||||
1. **Swipe down pour fermer** :
|
||||
- Geste naturel vers le bas pour fermer l'overlay
|
||||
- Feedback visuel (translation + diminution opacité)
|
||||
- Seuil de 100px pour déclencher la fermeture
|
||||
- Listeners sur `document` pour capturer le mouvement même sur les éléments enfants
|
||||
|
||||
2. **Bouton X conservé** pour desktop/souris
|
||||
|
||||
3. **Blocage du pull-to-refresh Android** :
|
||||
- `overscroll-behavior: contain`
|
||||
- `touch-action: none` sur l'overlay
|
||||
|
||||
### Corrections supplémentaires
|
||||
|
||||
1. **Support tactile progress bar principale** : Ajout de `@touchstart` sur la progress bar hors overlay
|
||||
|
||||
2. **Compatibilité viewport mobile** : Remplacement de `100vh` par `100dvh` (dynamic viewport height) pour éviter que la barre du navigateur masque le contenu
|
||||
|
||||
3. **Corrections TypeScript** : Vérifications `if (!touch) return` pour les événements tactiles
|
||||
|
||||
## Détails techniques
|
||||
|
||||
### Nouvelles variables réactives
|
||||
|
||||
```typescript
|
||||
const swipeStartY = ref(0);
|
||||
const swipeCurrentY = ref(0);
|
||||
const isSwiping = ref(false);
|
||||
const swipeThreshold = 100;
|
||||
```
|
||||
|
||||
### Nouvelles fonctions
|
||||
|
||||
- `handleOverlayTouchStart()` : Initialise le swipe et ajoute les listeners document
|
||||
- `handleSwipeTouchMove()` : Suit le mouvement du doigt
|
||||
- `handleSwipeTouchEnd()` : Ferme si seuil atteint, nettoie les listeners
|
||||
- `handleOverlayProgressBarTouchStart()` : Gestion tactile du seek (réutilisée par les deux progress bars)
|
||||
- `swipeOffset` / `swipeOpacity` : Computed pour le feedback visuel
|
||||
|
||||
### Nouveaux styles CSS
|
||||
|
||||
- `.overlay-metadata-text` : Conteneur du texte
|
||||
- `.overlay-progress-section` : Section progress bar
|
||||
- `.overlay-progress-bar` / `-fill` / `-thumb` : Style glassmorphism
|
||||
- `.overlay-time-display` : Affichage temps
|
||||
- `.cover-overlay-content.swiping` : État pendant le swipe
|
||||
- `overscroll-behavior: contain` + `touch-action: none` : Blocage pull-to-refresh
|
||||
- `100dvh` : Viewport dynamique pour mobile
|
||||
|
||||
### Responsive
|
||||
|
||||
- Mobile (< 768px) : Progress bar 10px, thumb 28px
|
||||
- Mode kiosque (800x600) : Progress bar 6px
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4,7 +4,7 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "PMOMusic"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
dependencies = [
|
||||
"axum 0.8.7",
|
||||
"console-subscriber",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "PMOMusic"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -24,7 +24,7 @@ const showMetadata = ref(false);
|
||||
function openCoverOverlay() {
|
||||
if (hasCover.value) {
|
||||
showCoverOverlay.value = true;
|
||||
showMetadata.value = false; // Reset metadata visibility when opening
|
||||
showMetadata.value = true; // Afficher les métadonnées par défaut
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,148 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
isSeeking.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fonctions pour la progress bar de l'overlay (réutilisent la même logique)
|
||||
function handleOverlayProgressBarClick(event: MouseEvent) {
|
||||
handleProgressBarClick(event);
|
||||
}
|
||||
|
||||
function handleOverlayProgressBarMouseDown(event: MouseEvent) {
|
||||
handleProgressBarMouseDown(event);
|
||||
}
|
||||
|
||||
// Gestion tactile pour la progress bar de l'overlay
|
||||
function handleOverlayProgressBarTouchStart(event: TouchEvent) {
|
||||
const duration = state.value?.duration_ms;
|
||||
if (!duration || duration === 0 || isSeeking.value) return;
|
||||
|
||||
const progressBar = event.currentTarget as HTMLElement;
|
||||
const touch = event.touches[0];
|
||||
if (!touch) return;
|
||||
|
||||
isDragging.value = true;
|
||||
dragProgress.value = calculateProgressFromX(touch.clientX, progressBar);
|
||||
|
||||
const handleTouchMove = (e: TouchEvent) => {
|
||||
if (!isDragging.value) return;
|
||||
const moveTouch = e.touches[0];
|
||||
if (!moveTouch) return;
|
||||
dragProgress.value = calculateProgressFromX(
|
||||
moveTouch.clientX,
|
||||
progressBar,
|
||||
);
|
||||
};
|
||||
|
||||
const handleTouchEnd = async (e: TouchEvent) => {
|
||||
if (!isDragging.value) return;
|
||||
|
||||
const endTouch = e.changedTouches[0];
|
||||
if (!endTouch) return;
|
||||
|
||||
const finalPercent = calculateProgressFromX(
|
||||
endTouch.clientX,
|
||||
progressBar,
|
||||
);
|
||||
const newPositionMs = (finalPercent / 100) * duration;
|
||||
const newPositionSeconds = Math.floor(newPositionMs / 1000);
|
||||
|
||||
dragProgress.value = finalPercent;
|
||||
seekTargetMs.value = newPositionMs;
|
||||
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"}`,
|
||||
);
|
||||
seekTargetMs.value = null;
|
||||
dragProgress.value = 0;
|
||||
} finally {
|
||||
isSeeking.value = false;
|
||||
}
|
||||
|
||||
document.removeEventListener("touchmove", handleTouchMove);
|
||||
document.removeEventListener("touchend", handleTouchEnd);
|
||||
};
|
||||
|
||||
document.addEventListener("touchmove", handleTouchMove, { passive: true });
|
||||
document.addEventListener("touchend", handleTouchEnd);
|
||||
}
|
||||
|
||||
// Gestion du swipe down pour fermer l'overlay
|
||||
const swipeStartY = ref(0);
|
||||
const swipeCurrentY = ref(0);
|
||||
const isSwiping = ref(false);
|
||||
const swipeThreshold = 100; // Distance minimale pour fermer
|
||||
|
||||
function handleOverlayTouchStart(event: TouchEvent) {
|
||||
const touch = event.touches[0];
|
||||
if (!touch) return;
|
||||
swipeStartY.value = touch.clientY;
|
||||
swipeCurrentY.value = touch.clientY;
|
||||
isSwiping.value = true;
|
||||
|
||||
// Ajouter les listeners sur le document pour capturer tous les mouvements
|
||||
document.addEventListener("touchmove", handleSwipeTouchMove, {
|
||||
passive: true,
|
||||
});
|
||||
document.addEventListener("touchend", handleSwipeTouchEnd);
|
||||
document.addEventListener("touchcancel", handleSwipeTouchEnd);
|
||||
}
|
||||
|
||||
function handleSwipeTouchMove(event: TouchEvent) {
|
||||
if (!isSwiping.value) return;
|
||||
const touch = event.touches[0];
|
||||
if (!touch) return;
|
||||
swipeCurrentY.value = touch.clientY;
|
||||
}
|
||||
|
||||
function handleSwipeTouchEnd() {
|
||||
if (!isSwiping.value) return;
|
||||
|
||||
const swipeDistance = swipeCurrentY.value - swipeStartY.value;
|
||||
|
||||
// Si swipe vers le bas suffisant, fermer l'overlay
|
||||
if (swipeDistance > swipeThreshold) {
|
||||
closeCoverOverlay();
|
||||
}
|
||||
|
||||
isSwiping.value = false;
|
||||
swipeStartY.value = 0;
|
||||
swipeCurrentY.value = 0;
|
||||
|
||||
// Retirer les listeners
|
||||
document.removeEventListener("touchmove", handleSwipeTouchMove);
|
||||
document.removeEventListener("touchend", handleSwipeTouchEnd);
|
||||
document.removeEventListener("touchcancel", handleSwipeTouchEnd);
|
||||
}
|
||||
|
||||
// Ancienne fonction conservée pour compatibilité template (non utilisée)
|
||||
function handleOverlayTouchMove(event: TouchEvent) {
|
||||
handleSwipeTouchMove(event);
|
||||
}
|
||||
|
||||
function handleOverlayTouchEnd() {
|
||||
handleSwipeTouchEnd();
|
||||
}
|
||||
|
||||
// Calcul du décalage visuel pendant le swipe
|
||||
const swipeOffset = computed(() => {
|
||||
if (!isSwiping.value) return 0;
|
||||
const offset = swipeCurrentY.value - swipeStartY.value;
|
||||
// Ne permettre que le swipe vers le bas (positif)
|
||||
return Math.max(0, offset);
|
||||
});
|
||||
|
||||
// Opacité qui diminue pendant le swipe
|
||||
const swipeOpacity = computed(() => {
|
||||
if (!isSwiping.value) return 1;
|
||||
const offset = swipeOffset.value;
|
||||
// Réduire l'opacité progressivement
|
||||
return Math.max(0.3, 1 - offset / 300);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -222,6 +364,7 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
class="progress-bar"
|
||||
@click="handleProgressBarClick"
|
||||
@mousedown="handleProgressBarMouseDown"
|
||||
@touchstart="handleOverlayProgressBarTouchStart"
|
||||
:class="{ seeking: isSeeking, dragging: isDragging }"
|
||||
>
|
||||
<div
|
||||
@@ -247,7 +390,15 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
>
|
||||
<div
|
||||
class="cover-overlay-content"
|
||||
:class="{ swiping: isSwiping }"
|
||||
:style="{
|
||||
transform: `translateY(${swipeOffset}px)`,
|
||||
opacity: swipeOpacity,
|
||||
}"
|
||||
@click.stop="handleOverlayContentClick"
|
||||
@touchstart="handleOverlayTouchStart"
|
||||
@touchmove="handleOverlayTouchMove"
|
||||
@touchend="handleOverlayTouchEnd"
|
||||
>
|
||||
<button
|
||||
class="cover-overlay-close"
|
||||
@@ -267,12 +418,47 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
v-if="metadata && showMetadata"
|
||||
class="cover-overlay-metadata"
|
||||
>
|
||||
<div class="overlay-metadata-text">
|
||||
<h2>{{ metadata.title }}</h2>
|
||||
<p class="artist">{{ metadata.artist }}</p>
|
||||
<p v-if="metadata.album" class="album">
|
||||
{{ metadata.album }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="overlay-progress-section">
|
||||
<div
|
||||
class="overlay-progress-bar"
|
||||
@click.stop="
|
||||
handleOverlayProgressBarClick
|
||||
"
|
||||
@mousedown.stop="
|
||||
handleOverlayProgressBarMouseDown
|
||||
"
|
||||
@touchstart.stop="
|
||||
handleOverlayProgressBarTouchStart
|
||||
"
|
||||
:class="{
|
||||
seeking: isSeeking,
|
||||
dragging: isDragging,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="overlay-progress-bar-fill"
|
||||
:style="{
|
||||
width: `${progressPercent}%`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="overlay-progress-bar-thumb"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-time-display">
|
||||
<span>{{ currentTime }}</span>
|
||||
<span>{{ totalTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
@@ -529,12 +715,15 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
backdrop-filter: blur(20px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(150%);
|
||||
padding: 16px;
|
||||
/* Empêcher le pull-to-refresh Android de se déclencher */
|
||||
overscroll-behavior: contain;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.cover-overlay-content {
|
||||
position: relative;
|
||||
width: calc(100vw - 32px);
|
||||
height: calc(100vh - 32px);
|
||||
height: calc(100dvh - 32px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -556,7 +745,7 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
}
|
||||
|
||||
.cover-overlay-metadata {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +792,7 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
|
||||
.cover-overlay-image {
|
||||
width: calc(100vw - 64px);
|
||||
height: calc(100vh - 64px);
|
||||
height: calc(100dvh - 64px);
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.6),
|
||||
@@ -614,16 +803,16 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
.cover-overlay-metadata {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
left: 15%;
|
||||
right: 15%;
|
||||
text-align: center;
|
||||
padding: var(--spacing-lg);
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
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);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.cover-overlay-metadata h2 {
|
||||
@@ -697,13 +886,13 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
|
||||
.cover-overlay-content {
|
||||
width: calc(100vw - 24px);
|
||||
height: calc(100vh - 24px);
|
||||
height: calc(100dvh - 24px);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.cover-overlay-image {
|
||||
width: calc(100vw - 48px);
|
||||
height: calc(100vh - 48px);
|
||||
height: calc(100dvh - 48px);
|
||||
}
|
||||
|
||||
.cover-overlay-metadata {
|
||||
@@ -729,12 +918,12 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
|
||||
.cover-overlay-content {
|
||||
width: calc(100vw - 24px);
|
||||
height: calc(100vh - 24px);
|
||||
height: calc(100dvh - 24px);
|
||||
}
|
||||
|
||||
.cover-overlay-image {
|
||||
width: calc(100vw - 48px);
|
||||
height: calc(100vh - 48px);
|
||||
height: calc(100dvh - 48px);
|
||||
}
|
||||
|
||||
.cover-overlay-metadata {
|
||||
@@ -770,4 +959,159 @@ async function handleProgressBarClick(event: MouseEvent) {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Swipe down pour fermer */
|
||||
.cover-overlay-content {
|
||||
transition:
|
||||
transform 0.1s ease-out,
|
||||
opacity 0.1s ease-out;
|
||||
}
|
||||
|
||||
.cover-overlay-content:not(.swiping) {
|
||||
transition:
|
||||
transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
|
||||
opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* Progress bar dans l'overlay - style glassmorphism macOS */
|
||||
.overlay-metadata-text {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.overlay-progress-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.overlay-progress-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
box-shadow:
|
||||
inset 0 1px 3px rgba(0, 0, 0, 0.4),
|
||||
0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.overlay-progress-bar:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.overlay-progress-bar.dragging {
|
||||
cursor: grabbing;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.overlay-progress-bar.seeking {
|
||||
cursor: wait;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.overlay-progress-bar-fill {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #059669 0%, #10b981 50%, #34d399 100%);
|
||||
border-radius: 4px;
|
||||
transition: width 0.1s linear;
|
||||
z-index: 1;
|
||||
box-shadow: 0 0 12px rgba(16, 185, 129, 0.6);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.overlay-progress-bar.dragging .overlay-progress-bar-fill {
|
||||
transition: width 0s;
|
||||
box-shadow: 0 0 16px rgba(16, 185, 129, 0.9);
|
||||
}
|
||||
|
||||
.overlay-progress-bar-thumb {
|
||||
position: absolute;
|
||||
right: -12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 2px solid rgba(16, 185, 129, 0.8);
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0, 0, 0, 0.5),
|
||||
0 0 12px rgba(16, 185, 129, 0.4),
|
||||
inset 0 1px 2px rgba(255, 255, 255, 0.5);
|
||||
transition: all 0.2s ease;
|
||||
z-index: 2;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.overlay-progress-bar:hover .overlay-progress-bar-thumb {
|
||||
transform: translateY(-50%) scale(1.15);
|
||||
box-shadow:
|
||||
0 3px 12px rgba(0, 0, 0, 0.6),
|
||||
0 0 16px rgba(16, 185, 129, 0.5),
|
||||
inset 0 1px 2px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.overlay-progress-bar.dragging .overlay-progress-bar-thumb {
|
||||
transform: translateY(-50%) scale(1.25);
|
||||
cursor: grabbing;
|
||||
box-shadow:
|
||||
0 4px 16px rgba(0, 0, 0, 0.7),
|
||||
0 0 20px rgba(16, 185, 129, 0.6),
|
||||
inset 0 1px 2px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.overlay-time-display {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: var(--text-sm);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Responsive progress bar overlay */
|
||||
@media (max-width: 768px) {
|
||||
.overlay-progress-bar {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.overlay-progress-bar-thumb {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
right: -14px;
|
||||
}
|
||||
|
||||
.overlay-time-display {
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mode kiosque - progress bar overlay */
|
||||
@media (max-height: 700px) and (orientation: landscape) {
|
||||
.overlay-progress-section {
|
||||
padding-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.overlay-progress-bar {
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.overlay-progress-bar-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
right: -10px;
|
||||
}
|
||||
|
||||
.overlay-time-display {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.3.8
|
||||
0.3.9
|
||||
|
||||
Reference in New Issue
Block a user