diff --git a/index.css b/index.css
index b208ecb..c71d584 100644
--- a/index.css
+++ b/index.css
@@ -502,8 +502,11 @@ body {
min-height: 0;
}
@media (min-width: 768px) {
+ /* Bureau : pas de barre du bas, les flèches sont sur les côtés — on ne
+ réserve que la scène (py-3) + la charge « Chargement… ». La vidéo gagne
+ ~80 px de hauteur (et autant de largeur, 9:16 oblige). */
.shorts-frame {
- --shorts-chrome: 9rem;
+ --shorts-chrome: 4rem;
}
}
@media (max-width: 767px) {
@@ -581,6 +584,8 @@ body {
relief (ombre + highlight interne) et agrandissement au survol. */
.pm-fab {
display: inline-flex;
+ touch-action: none; /* glisser le FAB ne défile pas la page */
+ cursor: grab;
align-items: center;
justify-content: center;
width: 2.25rem; /* 36px */
@@ -601,7 +606,7 @@ body {
border-color: rgba(255, 255, 255, .32);
transform: scale(1.06);
}
-.pm-fab:active { transform: scale(.94); }
+.pm-fab:active { transform: scale(.94); cursor: grabbing; }
.pm-fab-open {
background: linear-gradient(180deg, rgba(51, 65, 85, .95), rgba(2, 6, 23, .95));
border-color: rgba(255, 255, 255, .32);
@@ -730,6 +735,25 @@ body {
.shorts-frame app-player-menu { left: auto; right: 0.5rem; }
.shorts-frame app-player-menu .pm-panel { left: auto; right: 0; transform-origin: top right; }
+/* FAB glissé ailleurs que son coin d'origine : le panneau s'ouvre TOUJOURS
+ vers l'intérieur du conteneur (sinon `overflow: hidden` le rogne) et vers
+ le haut quand le FAB est en bas. Les classes sont posées par
+ PlayerMenuComponent.applyAnchor(). */
+app-player-menu.pm-opens-left .pm-panel { left: auto; right: 0; transform-origin: top right; }
+app-player-menu.pm-opens-right .pm-panel { left: 0; right: auto; transform-origin: top left; }
+app-player-menu.pm-opens-up .pm-panel { top: auto; bottom: calc(100% + 0.5rem); transform-origin: bottom left; }
+app-player-menu.pm-opens-left.pm-opens-up .pm-panel { transform-origin: bottom right; }
+
+/* Bureau : menu plus compact pour laisser la place à la vidéo (shorts). */
+@media (min-width: 768px) {
+ .shorts-frame app-player-menu .pm-fab { width: 1.875rem; height: 1.875rem; }
+ .shorts-frame app-player-menu .pm-fab svg { width: 0.875rem; height: 0.875rem; }
+ .shorts-frame app-player-menu .pm-panel { width: 13rem; max-height: min(60vh, 380px); }
+ .shorts-frame app-player-menu .pm-item { padding: 0.375rem 0.5rem; gap: 0.5rem; font-size: 0.75rem; }
+ .shorts-frame app-player-menu .pm-section-label { padding: 0.375rem 0.5rem 0.125rem; font-size: 0.625rem; }
+ .shorts-frame app-player-menu .pm-panel-header { padding: 0.3125rem 0.3125rem 0.3125rem 0.625rem; }
+}
+
/* --- Drawer / overlay plumbing -------------------------------------------- */
/* Applied to
while a full-screen drawer or modal owns the viewport.
Note: deliberately NOT `touch-action: none` — touch-action is intersected up
diff --git a/src/app/search/adapters/pt.ts b/src/app/search/adapters/pt.ts
index cfc083b..3ec61cd 100644
--- a/src/app/search/adapters/pt.ts
+++ b/src/app/search/adapters/pt.ts
@@ -45,6 +45,10 @@ export class PtAdapter implements ProviderAdapter {
// Le serveur envoie `publishedAt` (ISO) quand l'instance l'expose.
publishedAt: typeof it.publishedAt === 'string' ? it.publishedAt : undefined,
channelExternalId,
+ // URL canonnique : instance d'origine (vidéos fédérées). Sans elle,
+ // /shorts reconstruit l'embed sur l'instance ACTIVE → « Sorry this
+ // video does not exist ».
+ url: typeof it.url === 'string' ? it.url : undefined,
// Signaux de type pour le raffinement client des filtres.
type: typeof it.type === 'string' ? it.type : undefined,
isShort: it.isShort === true ? true : undefined,
diff --git a/src/app/search/shorts-catalog.spec.ts b/src/app/search/shorts-catalog.spec.ts
index bbf99b9..c7752d0 100644
--- a/src/app/search/shorts-catalog.spec.ts
+++ b/src/app/search/shorts-catalog.spec.ts
@@ -50,6 +50,15 @@ assertEqual(
assertEqual(v?.views, 0, 'vues inconnues → 0 (pas de devinette)');
assertEqual(v?.duration, 0, 'durée inconnue → 0');
}
+{
+ // PeerTube fédéré : l'URL porte l'instance d'origine. Sans elle, /shorts
+ // reconstruit l'embed sur l'instance ACTIVE → « Sorry this video does not exist ».
+ const v: any = catalogShortToVideo({
+ id: 'p1', provider: 'peertube', title: 'P', thumbnailUrl: '', channelName: 'C',
+ url: 'https://makertube.net/videos/watch/p1',
+ } as any);
+ assertEqual(v?.url, 'https://makertube.net/videos/watch/p1', 'url fournisseur conservée (host PeerTube)');
+}
{
const v: any = catalogShortToVideo({
id: 'y1', provider: 'youtube', title: 'Y', thumbnailUrl: '', channelName: 'C',
diff --git a/src/app/search/shorts-catalog.ts b/src/app/search/shorts-catalog.ts
index 215dbbb..7e979a7 100644
--- a/src/app/search/shorts-catalog.ts
+++ b/src/app/search/shorts-catalog.ts
@@ -56,7 +56,9 @@ export function catalogShortToVideo(item: VideoItem | null | undefined): Video |
const embeddableRaw = (item as { embeddable?: unknown }).embeddable;
const allowEmbed = typeof embeddableRaw === 'boolean' ? embeddableRaw : undefined;
return {
- url: provider === 'odysee' && slug ? `https://odysee.com/${slug}` : '',
+ // Odysee : URL normalisée depuis son slug ; ailleurs on conserve l'URL
+ // du fournisseur (PeerTube fédéré en a besoin pour choisir le bon host).
+ url: provider === 'odysee' && slug ? `https://odysee.com/${slug}` : strOrEmpty(item.url),
type: item.isLive === true ? 'live' : (item.isShort === true ? 'short' : 'video'),
title: strOrEmpty(item.title),
thumbnail: strOrEmpty(item.thumbnailUrl),
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 10ef1f1..1560fa1 100644
--- a/src/app/shared/components/player-menu/player-menu.component.ts
+++ b/src/app/shared/components/player-menu/player-menu.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core';
+import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
/**
@@ -30,10 +30,14 @@ import { CommonModule } from '@angular/common';
template: `