From 99e01c06797059c3cf56bb58e37940dffb8e4b89 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Fri, 2 Oct 2026 13:37:50 -0400 Subject: [PATCH] =?UTF-8?q?feat(shorts):=20UI/UX=20=E2=80=94=20rail=20d'ac?= =?UTF-8?q?tions=20=C3=A0=20droite=20(fl=C3=A8ches=20=E2=86=91/=E2=86=93),?= =?UTF-8?q?=20panneau=20d'infos,=20glissement=20magn=C3=A9tique?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- index.css | 122 +++++++++- .../player-menu/player-menu.component.ts | 3 + .../shorts/watch-short.component.html | 209 +++++++++++++++--- .../shorts/watch-short.component.ts | 161 +++++++++++++- 4 files changed, 446 insertions(+), 49 deletions(-) diff --git a/index.css b/index.css index 10f55a7..ce1502f 100644 --- a/index.css +++ b/index.css @@ -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 { diff --git a/src/app/shared/components/player-menu/player-menu.component.ts b/src/app/shared/components/player-menu/player-menu.component.ts index 1560fa1..5abafe8 100644 --- a/src/app/shared/components/player-menu/player-menu.component.ts +++ b/src/app/shared/components/player-menu/player-menu.component.ts @@ -26,6 +26,9 @@ import { CommonModule } from '@angular/common'; '(wheel)': '$event.stopPropagation()', '(touchstart)': '$event.stopPropagation()', '(touchend)': '$event.stopPropagation()', + // /shorts suit aussi le doigt en `touchmove` : sans ça, glisser le FAB + // déplacerait AUSSI le cadre (deux drags pour un geste). + '(touchmove)': '$event.stopPropagation()', }, template: `