diff --git a/frontend/src/design-lab/DesignLab.tsx b/frontend/src/design-lab/DesignLab.tsx index 082fe312..8420481a 100644 --- a/frontend/src/design-lab/DesignLab.tsx +++ b/frontend/src/design-lab/DesignLab.tsx @@ -6,7 +6,7 @@ import { BatchB } from "./batchB"; import { BatchC, C5 } from "./batchC"; // The open decisions, in the order they are asked. The decided ones render below them as reference. -const OPEN = [{ id: "c5", code: "C5", title: "Player row", render: C5 }] as const; +const OPEN = [{ id: "c5", code: "C5", title: "Player controls", render: C5 }] as const; // The design lab (E28): the living style guide. Dev-only — see main.tsx and devOnlyGuard.ts. It sits // OUTSIDE , so it owns the theme attributes on itself, through the same data-theme / @@ -116,7 +116,7 @@ export default function DesignLab() {
-

Open · the player row on a narrow screen

+

Open · the Plex player controls

Each decision shows its variants with the app's real CSS. The framed one is the recommendation. Resize the window to tablet size to see them there too. diff --git a/frontend/src/design-lab/batchC.tsx b/frontend/src/design-lab/batchC.tsx index 5189e732..d220d9a1 100644 --- a/frontend/src/design-lab/batchC.tsx +++ b/frontend/src/design-lab/batchC.tsx @@ -9,13 +9,13 @@ import { Maximize, MoreHorizontal, Pause, + Play, Pin, Rewind, RotateCcw, Settings, SkipBack, SkipForward, - Square, Subtitles, Volume2, X, @@ -27,8 +27,8 @@ import { FADE_ABOVE_PANEL, FADE_BELOW_PANEL, H, LAYER, STRONG } from "./labToken // E28 S2 · Batch C — behaviour on touch and on a narrow window. C1–C4 are DECIDED (owner, 2026-09-24: // all as proposed) and stay as references. C5 is open: the live player pass (E28 S2d) found the Plex -// control row running off-screen below 1024, which C3's 44px buttons make worse, so how the row -// fits is asked on its own. +// control row running off-screen below 1024; the owner answered with a reworked control set, and +// two of its points are asked back. const ico = "w-4 h-4"; @@ -210,31 +210,45 @@ export function BatchC() { ); } -// ── C5 · the Plex control row on a narrow screen ───────────────────────────────────────────────── -// Every control of today's row, in the order it sits; `keep` = stays in the row under variant A. -const CONTROLS: { icon: typeof Pause; label: string; keep: boolean }[] = [ - { icon: Rewind, label: "Back 10 s", keep: true }, - { icon: Pause, label: "Play / pause", keep: true }, - { icon: FastForward, label: "Forward 10 s", keep: true }, - { icon: RotateCcw, label: "Restart from the beginning", keep: false }, - { icon: Square, label: "Stop", keep: false }, - { icon: SkipBack, label: "Previous episode", keep: true }, - { icon: SkipForward, label: "Next episode", keep: true }, - { icon: Volume2, label: "Mute", keep: true }, - { icon: Subtitles, label: "Audio & subtitles", keep: true }, - { icon: Settings, label: "Settings", keep: false }, - { icon: Activity, label: "Stats for nerds", keep: false }, - { icon: Info, label: "Media info", keep: false }, - { icon: Keyboard, label: "Keyboard shortcuts", keep: false }, - { icon: Download, label: "Download original file", keep: false }, +// ── C5 · the Plex player's controls, reworked (the owner's model, 2026-09-24) ───────────────────── +// The owner's direction: Stop, Restart and ±10 s leave the row; Back restarts or steps to the previous +// episode; Next exists only when there is a next item; restarting moves onto the card. What is left +// fits a portrait tablet in one row, with a fixed "⋯" for the rare tools. + +const ROW: { icon: typeof Pause; label: string }[] = [ + { icon: Pause, label: "Play / pause" }, + { icon: SkipBack, label: "Back: restart, then the previous episode" }, + { icon: SkipForward, label: "Next episode (only when there is one)" }, + { icon: Volume2, label: "Mute" }, +]; +const MORE: { icon: typeof Pause; label: string }[] = [ + { icon: Settings, label: "Player settings" }, + { icon: Info, label: "Media info" }, + { icon: Activity, label: "Stats for nerds" }, + { icon: Keyboard, label: "Keyboard shortcuts" }, + { icon: Download, label: "Download original file" }, ]; const ctl = "grid h-11 w-11 shrink-0 place-items-center rounded-full"; -function Frame({ children }: { children: ReactNode }) { - // Stands in for a portrait tablet: the frame is the screen width, narrower than the full row. +function PlayerRow({ touchZones }: { touchZones: boolean }) { return (

+ {touchZones && ( + // Double-tap zones: the left and right thirds of the picture skip ∓10 s on touch. +
+ + + double-tap −10 s + + + + + double-tap +10 s + + +
+ )}
@@ -242,56 +256,99 @@ function Frame({ children }: { children: ReactNode }) {
- {children} +
+ {ROW.map((c) => ( + + + + ))} + 12:04 / 42:21 + + + + + + + + + +
+
+ {MORE.map((c) => ( + + + {c.label} + + ))} +
); } -function OverflowRow() { +function MediaCard({ state }: { state: "new" | "started" | "touch" }) { + const pill = "inline-flex h-8 items-center gap-1.5 rounded-full px-3 text-xs font-medium"; return ( - -
- {CONTROLS.filter((c) => c.keep).map((c) => ( - - +
+
+ {state !== "new" && ( + + - ))} - 12:04 / 42:21 - - - - - - -
-
- {CONTROLS.filter((c) => !c.keep).map((c) => ( - - - {c.label} + )} + {state === "new" && ( + + + + - ))} + )} + {state === "started" && ( + + + Resume 14:02 + + + From the start + + + )}
- +
+
+
Darkman III
+
+ {state === "new" ? "1996 · not started" : "1996 · 14 min in"} +
+
+ {state === "touch" && ( + + + + )} +
+
); } -function WrappedRow() { +function Cards() { return ( - -
- {CONTROLS.map((c) => ( - - - - ))} - - - - 12:04 / 42:21 -
- +
+ + + + + + + + + +
); } @@ -300,24 +357,25 @@ export function C5() { - + + - + );