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:
2026-09-27 02:12:54 +02:00
parent 17916b411a
commit 89e65ca331
33 changed files with 1100 additions and 282 deletions
@@ -7,12 +7,13 @@ import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
import { act, renderHook } from "@testing-library/react";
import { useControlsVisibility } from "./plexPlayerHooks";
const setup = () => {
const video = { paused: false } as HTMLVideoElement;
const setup = (menuOpen = false) => {
const video = document.createElement("video");
Object.defineProperty(video, "paused", { configurable: true, value: false });
return renderHook(() =>
useControlsVisibility({
barAutoHideRef: { current: true },
menuOpenRef: { current: false },
menuOpenRef: { current: menuOpen },
videoRef: { current: video },
}),
);
@@ -44,4 +45,19 @@ describe("useControlsVisibility", () => {
act(() => vi.advanceTimersByTime(100));
expect(result.current.uiVisible).toBe(false);
});
// C3: on touch nothing moves a mouse, so a tap toggles the bar and playback arms the timer.
it("toggle hides a shown bar at once and shows a hidden one", () => {
const { result } = setup();
act(() => result.current.toggle());
expect(result.current.uiVisible).toBe(false);
act(() => result.current.toggle());
expect(result.current.uiVisible).toBe(true);
});
it("a tap does not hide the bar under an open menu", () => {
const { result } = setup(true);
act(() => result.current.toggle());
expect(result.current.uiVisible).toBe(true);
});
});