feat(shorts): UI/UX — rail d'actions à droite (flèches ↑/↓), panneau d'infos, glissement magnétique
CI / build-and-test (push) Canceled after 8s

- Supprime les flèches Préc./Suiv. aux bords gauche/droite (troubles
  d'affichage récurrents) : la navigation passe au rail vertical ancré au
  bord DROIT de la vidéo, centré au bureau et ancré au-dessus de la méta
  sur mobile, avec des flèches ↑/↓ plus grosses (44 px).
- Même rail : informations, j'aime, s'abonner (pastille = avatar), commentaires,
  partager, lecture auto. Toujours monté (comme le FAB), désactivé sans vidéo.
- Bouton ⓘ : panneau d'informations (titre, vues, engagement, chaîne,
  description, commentaires → Watch). Colonne à droite au bureau (la vidéo
  rétrécit, jamais masquée : --shorts-panel-w retiré du calcul de taille),
  tiroir plein écran sur mobile. Échap ferme.
- Transition de short : le doigt fait glisser le cadre (chrome solidaire),
  résistance aux extrémités, seuil de bascule 25 % de la hauteur, ressort
  sous le seuil, éjection + emboîtement au-delà (Web Animations, respecte
  prefers-reduced-motion). Aucun état de lecture modifié.
- Lecture auto : interrupteur réel (rail + menu), persisté, respecté par le
  minuteur et par la fin de vidéo YouTube.
- Tap sur un contrôle du rail = plus de play/pause parasite (garde
  isChromeTarget dans onTouchStart/onTouchEnd).
- FAB menu conservé (haut droit, déplaçable) ; son touchmove ne déplace plus
  le cadre en même temps.
This commit is contained in:
2026-10-02 13:37:50 -04:00
parent 6b32ede302
commit 99e01c0679
4 changed files with 446 additions and 49 deletions
+113 -9
View File
@@ -497,7 +497,7 @@ body {
max-width: 100%;
height: min(
calc(var(--app-vh) - var(--header-offset) - var(--shorts-chrome)),
calc((100vw - 2rem) * 16 / 9)
calc((100vw - 2rem - var(--shorts-panel-w, 0px)) * 16 / 9)
);
min-height: 0;
}
@@ -515,6 +515,11 @@ body {
.shorts-frame {
--shorts-chrome: 5.5rem;
}
/* Le rail est ancré au-dessus de la méta (bas) : il ne doit jamais la
chevaucher — sur un écran court il défile en interne au lieu de rogner. */
.shorts-rail {
max-height: calc(100% - 11rem);
}
}
/* Embed PAYSAGE dans le cadre vertical : la boîte de l'iframe suit le16:9
@@ -572,19 +577,118 @@ 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 : rail d'actions (bord DROIT de la vidéo) --------------------- */
/* Colonne d'icônes façon Shorts : informations, flèches ↑/↓, j'aime,
s'abonner, commentaires, partager, lecture auto. TOUJOURS montée (c'est la
navigation utilisable dans tous les états, avec le FAB). Ancrage bas sur
mobile (pouce, au-dessus de la méta) puis centré verticalement au bureau
(classes du template). */
.shorts-rail {
max-height: calc(100% - 1.5rem);
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.shorts-rail::-webkit-scrollbar { display: none; }
.shorts-rail-btn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
flex: none;
width: 2.5rem; /* 40px : cible tactile mobile */
height: 2.5rem;
border-radius: 9999px;
color: #fff;
background: rgba(0, 0, 0, .55);
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .5);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
transition: opacity .25s ease, background .15s ease, transform .15s ease;
}
.shorts-rail-btn:hover:not(:disabled) { background: rgba(0, 0, 0, .75); transform: scale(1.06); }
.shorts-rail-btn:disabled { opacity: .4; }
.shorts-rail-btn svg { width: 1.15rem; height: 1.15rem; }
/* Flèches ↑/↓ : plus grosses (c'est LA cible de défilement). */
.shorts-rail-btn--nav { width: 2.75rem; height: 2.75rem; }
.shorts-rail-btn--nav svg { width: 1.5rem; height: 1.5rem; }
/* Bouton actif (j'aime, abonné, lecture auto) : plein, contraste maximal. */
.shorts-rail-btn--on {
background: rgba(255, 255, 255, .92);
color: #0f172a;
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .9);
}
.shorts-rail-btn--on:hover:not(:disabled) { background: #fff; }
/* Abonnement : la pastille EST l'avatar de la chaîne. */
.shorts-rail-btn--sub { overflow: visible; }
.shorts-rail-badge {
position: absolute;
right: -2px;
bottom: -2px;
display: flex;
align-items: center;
justify-content: center;
width: 1.05rem;
height: 1.05rem;
border-radius: 9999px;
background: #ef4444;
color: #fff;
font-size: .7rem;
font-weight: 800;
line-height: 1;
box-shadow: 0 1px 3px rgba(0, 0, 0, .5);
}
.shorts-rail-btn--on .shorts-rail-badge { background: #16a34a; }
@media (min-width: 768px) {
/* Bureau : rail plus compact, la vidéo reste le sujet. */
.shorts-rail-btn {
width: 2.25rem;
height: 2.25rem;
box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .45);
}
.shorts-rail-btn svg { width: 1.05rem; height: 1.05rem; }
.shorts-rail-btn--nav { width: 2.5rem; height: 2.5rem; }
.shorts-rail-btn--nav svg { width: 1.35rem; height: 1.35rem; }
}
/* --- Shorts : panneau d'informations (bouton ⓘ) ---------------------------- */
.shorts-stage { --shorts-panel-w: 0px; }
@media (min-width: 768px) {
/* Le cadre est dimensionné par la hauteur du viewport : on retranche aussi
la largeur du panneau pour que la vidéo RÉTRÉCISSE au lieu d'être rognée
(exigence : « sans masquer la vidéo »). */
.shorts-stage.shorts-panel-open { --shorts-panel-w: 356px; }
}
.shorts-info-action {
display: inline-flex;
align-items: center;
gap: .4rem;
border-radius: 9999px;
padding: .3rem .6rem;
font-size: .72rem;
font-weight: 600;
color: rgba(255, 255, 255, .75);
background: rgba(255, 255, 255, .07);
transition: background .15s ease, color .15s ease;
}
.shorts-info-action:hover:not(:disabled) { background: rgba(255, 255, 255, .16); color: #fff; }
.shorts-info-action:disabled { opacity: .45; }
.shorts-info-action--on { color: #fff; background: rgba(255, 255, 255, .2); }
/* Contrôles flottants (rail + 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 bouton actif 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-rail-btn,
.shorts-frame app-player-menu .pm-fab {
transition: opacity 0.25s ease;
}
.shorts-chrome-hidden .shorts-frame .shorts-ctrl {
.shorts-chrome-hidden .shorts-frame .shorts-rail-btn {
opacity: 0.18;
}
.shorts-chrome-hidden .shorts-frame .shorts-ctrl:not(:disabled):hover {
.shorts-chrome-hidden .shorts-frame .shorts-rail-btn:not(:disabled):hover {
opacity: 1;
}
.shorts-chrome-hidden .shorts-frame app-player-menu .pm-fab {