feat(design): players on touch and the C5 control set, restart on the card (E28 S3f2)
Plex player: a finger's tap on the picture only shows or hides the controls, the hide timer starts with playback, a double-tap on a side third skips 10 s, the seek bar is a 2.75rem touch-none hit area with its head shown on touch. The row is play/pause, Back by position, Next only with a next item, mute, time, tracks, a fixed ⋯ (settings, media info, stats, shortcuts, download) and fullscreen; Stop, Restart and the ±10 s buttons are gone. A started card shows the video card's Continue + Restart pair (one shared MediaResumeButtons, beside the picture's button), and its menu lists the restart; the player opens that item at 0 (fromStart). YouTube modal: the description opens from an ⓘ, the queue arrows sit inside the screen.
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ExternalLink,
|
||||
Info,
|
||||
Maximize,
|
||||
Minimize,
|
||||
Minimize2,
|
||||
@@ -403,25 +404,23 @@ export default function PlayerModal({
|
||||
}
|
||||
};
|
||||
|
||||
// Lazy description (fetched only when the title is hovered). Anchored ABOVE the title so it grows
|
||||
// upward over the player rather than downward off-screen / behind the OS taskbar — `flip` still
|
||||
// rescues it when there is no room up there either, which the old fixed `bottom` could not. The
|
||||
// hover-intent delay (only pop it if the pointer lingers, so a quick pass doesn't flash the card)
|
||||
// and the travel grace are now the platform's, along with the safePolygon bridge that replaces the
|
||||
// pair of hand-managed timers.
|
||||
// Lazy description (fetched only when it is opened). It opens from an ⓘ beside the title, by click
|
||||
// or tap (C3): it used to open on hovering the title, which a finger never does — measured live,
|
||||
// a tablet could not read a description at all. Anchored ABOVE, so it grows upward over the player
|
||||
// rather than off-screen; `flip` still rescues it when there is no room up there.
|
||||
const [showDesc, setShowDesc] = useState(false);
|
||||
const descPop = useAnchoredPopover({
|
||||
open: showDesc,
|
||||
onOpenChange: setShowDesc,
|
||||
mode: "hover",
|
||||
mode: "dialog",
|
||||
role: "dialog",
|
||||
placement: "top-start",
|
||||
offset: 8,
|
||||
hoverDelay: { open: 400, close: 150 },
|
||||
});
|
||||
const detail = useQuery({
|
||||
queryKey: qk.videoDetail(currentVideoId),
|
||||
queryFn: () => api.videoDetail(currentVideoId),
|
||||
// On hover (for the description) and eagerly when navigated, so we can link the
|
||||
// On opening the description, and eagerly when navigated, so we can link the
|
||||
// linked video's channel. Both share the one cached result.
|
||||
enabled: showDesc || navigated,
|
||||
staleTime: 5 * 60_000,
|
||||
@@ -574,10 +573,13 @@ export default function PlayerModal({
|
||||
aria-labelledby={titleId}
|
||||
>
|
||||
{/* The card, flanked by full-height glassy strips that step through the feed's order (or a
|
||||
playlist). Each strip sits just outside the card, spans its full height, and fades out at
|
||||
the ends. stopPropagation so a click steps instead of closing. Hidden on narrow screens
|
||||
where there's no room beside the card. */}
|
||||
<div className="relative flex w-full max-w-4xl max-h-full">
|
||||
playlist). Each strip sits in a gutter of the card's row, spans its full height, and fades
|
||||
out at the ends; the gutter keeps it on the screen (C3: outside the card, a 1024 window cut
|
||||
it and a portrait tablet lost it). stopPropagation so a click steps instead of closing.
|
||||
Hidden on narrow screens where there's no room beside the card. */}
|
||||
<div
|
||||
className={`relative flex w-full max-h-full ${hasQueue ? "max-w-[63rem] sm:px-14" : "max-w-4xl"}`}
|
||||
>
|
||||
{hasQueue && (
|
||||
<Pressable
|
||||
onClick={(e) => {
|
||||
@@ -587,7 +589,7 @@ export default function PlayerModal({
|
||||
disabled={index === 0}
|
||||
aria-label={t("player.previous")}
|
||||
title={`${t("player.previous")} · Shift+←`}
|
||||
className="absolute right-full top-0 bottom-0 mr-2 hidden w-12 place-items-center rounded-2xl bg-white/5 text-white/40 backdrop-blur-xs transition hover:bg-white/15 hover:text-white disabled:pointer-events-none disabled:opacity-0 sm:grid"
|
||||
className="absolute left-0 top-0 bottom-0 hidden w-12 place-items-center rounded-2xl bg-white/5 text-white/40 backdrop-blur-xs transition hover:bg-white/15 hover:text-white disabled:pointer-events-none disabled:opacity-0 sm:grid"
|
||||
>
|
||||
<ChevronLeft className="h-8 w-8" />
|
||||
</Pressable>
|
||||
@@ -778,18 +780,20 @@ export default function PlayerModal({
|
||||
)}
|
||||
|
||||
<div className="p-4 sm:p-5">
|
||||
{/* Title row — title (with hover description) on the left, Close on the right. */}
|
||||
{/* Title row — the title and its ⓘ (the description) on the left, Close on the right. */}
|
||||
<div className="flex items-start gap-3">
|
||||
<h2 id={titleId} className="min-w-0 flex-1 text-lg font-semibold leading-snug">
|
||||
{/* Hover target is the text itself (inline), not the whole row. */}
|
||||
<span
|
||||
ref={descPop.refs.setReference}
|
||||
{...descPop.getReferenceProps()}
|
||||
className="cursor-default"
|
||||
>
|
||||
{navigated ? (liveData?.title ?? t("player.loading")) : active.title}
|
||||
</span>
|
||||
{navigated ? (liveData?.title ?? t("player.loading")) : active.title}
|
||||
</h2>
|
||||
<IconButton
|
||||
ref={descPop.refs.setReference}
|
||||
{...descPop.getReferenceProps()}
|
||||
variant="ghost"
|
||||
aria-expanded={showDesc}
|
||||
label={t("player.description")}
|
||||
>
|
||||
<Info />
|
||||
</IconButton>
|
||||
{navigated && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -999,7 +1003,7 @@ export default function PlayerModal({
|
||||
disabled={index >= queue!.length - 1}
|
||||
aria-label={t("player.next")}
|
||||
title={`${t("player.next")} · Shift+→`}
|
||||
className="absolute left-full top-0 bottom-0 ml-2 hidden w-12 place-items-center rounded-2xl bg-white/5 text-white/40 backdrop-blur-xs transition hover:bg-white/15 hover:text-white disabled:pointer-events-none disabled:opacity-0 sm:grid"
|
||||
className="absolute right-0 top-0 bottom-0 hidden w-12 place-items-center rounded-2xl bg-white/5 text-white/40 backdrop-blur-xs transition hover:bg-white/15 hover:text-white disabled:pointer-events-none disabled:opacity-0 sm:grid"
|
||||
>
|
||||
<ChevronRight className="h-8 w-8" />
|
||||
</Pressable>
|
||||
|
||||
Reference in New Issue
Block a user