feat(shorts): flèches en ligne avec le menu + fondu des contrôles au repos
CI / build-and-test (push) Successful in 14m54s

Demandes (page /shorts) : retrouver les flèches Préc./Suiv. là où se
trouve le menu, et des contrôles presque invisibles au repos pour
laisser la vidéo respirer (ton pro).

- Flèches déplacées du milieu des côtés vers la rangée HAUTE du cadre,
  centrées : même niveau que le FAB menu, sans chevaucher la flèche
  retour mobile (gauche) ni le FAB (droite). Le toast « Lien copié »
  passe en z-30 pour rester lisible sous la rangée.
- Fondu au repos : nouvelles règles index.css — flèches (.shorts-ctrl)
  et FAB à 0.18 d'opacité quand le chrome est au repos (2,5 s sans
  souris), pleins au moindre mouvement (chromeVisible) ou au survol
  direct (:hover), jamais atténués quand le panneau est ouvert
  (pm-fab-open). Sur tactile, le chrome ne se masque jamais (pas de
  mousemove : comportement voulu, mobile inchangé).
- Le minuteur d'immobilité est armé au chargement (pokeChrome sous
  matchMedia('(hover: hover)')) : sans un premier mousemove,
  chromeVisible restait true et le fondu ne s'armait jamais.
- mouseenter sur l'iframe : en entrant sur la vidéo, le document
  parent ne reçoit pas les mouvements internes à l'iframe — l'entrée
  réveille les contrôles.

Recette Playwright desktop+mobile, 14/14 : position haute centrée,
absence de chevauchements, repos 0.18 (flèches + FAB), retour plein
après mouvement réel, mobile plein en permanence. Build + test:section
verts avant commit.
This commit is contained in:
2026-10-02 11:28:03 -04:00
parent 3ab61821b2
commit e44b545e01
3 changed files with 46 additions and 13 deletions
+22
View File
@@ -572,6 +572,28 @@ body {
opacity: 0;
pointer-events: none;
}
/* Contrôles flottants (rangée de flèches + FAB menu) : presque fondu au repos
pour laisser la vidéo respirer (ton pro), pleins dès le moindre mouvement
de souris (chromeVisible) ou survol direct. Le panneau ouvert et le FAB
ouvert ne sont jamais atténués. Le tactile ne déclenche presque pas
mousemove : sur mobile tout reste donc plein (comportement voulu). */
.shorts-frame .shorts-ctrl,
.shorts-frame app-player-menu .pm-fab {
transition: opacity 0.25s ease;
}
.shorts-chrome-hidden .shorts-frame .shorts-ctrl {
opacity: 0.18;
}
.shorts-chrome-hidden .shorts-frame .shorts-ctrl:not(:disabled):hover {
opacity: 1;
}
.shorts-chrome-hidden .shorts-frame app-player-menu .pm-fab {
opacity: 0.18;
}
.shorts-chrome-hidden .shorts-frame app-player-menu .pm-fab:hover,
.shorts-chrome-hidden .shorts-frame app-player-menu .pm-fab.pm-fab-open {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.shorts-chrome {
transition: none;
@@ -24,6 +24,7 @@
<div #shortsFrame class="shorts-frame relative shrink-0 overflow-hidden bg-black rounded-none ring-0 shadow-none sm:rounded-2xl sm:ring-1 sm:ring-white/10 sm:shadow-2xl">
@if (embedUrl(); as url) {
<iframe [src]="url" [title]="current()?.title || 'Short video'" class="absolute inset-0 w-full h-full" [class.embed-wide]="wideEmbed()" frameborder="0" playsinline
(mouseenter)="pokeChrome()"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture; web-share"
allowfullscreen referrerpolicy="origin"></iframe>
} @else if (embedWaiting()) {
@@ -205,22 +206,27 @@
}
</div>
<!-- Navigation précédent / suivant : SUR la vidéo (hors auto-hide du
chrome, hors geste tactile — stopPropagation dans les handlers). -->
<button type="button" (click)="prev(); $event.stopPropagation()" [disabled]="!canPrev()"
class="absolute left-1.5 top-1/2 z-20 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/70 disabled:opacity-40"
aria-label="Short précédent">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M15.5 4.5 8 12l7.5 7.5z"/></svg>
</button>
<button type="button" (click)="next(); $event.stopPropagation()" [disabled]="!canNext()"
class="absolute right-1.5 top-1/2 z-20 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/70 disabled:opacity-40"
aria-label="Short suivant">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8.5 4.5 16 12l-7.5 7.5z"/></svg>
</button>
<!-- Navigation précédent / suivant : MÊME LIGNE que le menu (haut
du cadre, centré pour ne pas chevaucher la flèche retour mobile
ni le FAB) — on les retrouve là où se trouve le menu.
Classe shorts-ctrl : fondu au repos, pleins au mouvement/
survol (index.css). stopPropagation dans les handlers. -->
<div class="absolute left-1/2 top-1.5 z-20 flex -translate-x-1/2 gap-2">
<button type="button" (click)="prev(); $event.stopPropagation()" [disabled]="!canPrev()"
class="shorts-ctrl flex h-10 w-10 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/70 disabled:opacity-40"
aria-label="Short précédent">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M15.5 4.5 8 12l7.5 7.5z"/></svg>
</button>
<button type="button" (click)="next(); $event.stopPropagation()" [disabled]="!canNext()"
class="shorts-ctrl flex h-10 w-10 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/15 backdrop-blur-md transition hover:bg-black/70 disabled:opacity-40"
aria-label="Short suivant">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8.5 4.5 16 12l-7.5 7.5z"/></svg>
</button>
</div>
}
@if (shareCopied()) {
<div class="absolute top-2 left-1/2 -translate-x-1/2 px-3 py-1.5 rounded-full bg-black/70 text-white text-xs" role="status">Lien copié ✓</div>
<div class="absolute top-2 left-1/2 -translate-x-1/2 z-30 px-3 py-1.5 rounded-full bg-black/70 text-white text-xs" role="status">Lien copié ✓</div>
}
</div>
</div>
@@ -714,6 +714,11 @@ export class WatchShortComponent implements OnDestroy {
});
try { window.addEventListener('message', this.handleShortMessage); } catch {}
try { document.addEventListener('fullscreenchange', this.handleFullscreenChange); } catch {}
// Arme le minuteur d'immobilité dès le chargement : sans un premier
// mousemove, chromeVisible resterait à true et les contrôles flottants
// (flèches + FAB) ne feraient jamais leur fondu « au repos ». Desktop
// seulement (hover) : sur tactile le chrome reste visible en permanence.
try { if (matchMedia('(hover: hover)').matches) this.pokeChrome(); } catch {}
}
ngOnDestroy(): void {