diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 0b08b8c6..4fa263bd 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -113,11 +113,14 @@ style guide. because the picture does: the broadcast red `--live` under `--live-fg` (never `--danger`: "live" is not an error), `--rating-media` on a poster's scrim, and `--media-chrome` for the always-dark player's menus (owner decision F16). On that chrome `--media-mark` is a marked place (the playhead, the crop frame, a - Plex marker) and the focus ring, and `--media-danger` an error, as a light theme's accent and danger are - dark on the chrome: `media-focus` re-points `--focus` on the player (the Plex player, the YouTube stage) - and on every surface painted `--media-chrome` (its menus, panels and bars), and a form there - (`media-scope`) re-points `--danger` too. `theme.contract.test.ts` measures the red on the chrome and - holds every class string that paints it to name one of the two. Black and white are the media scrims and their text. Five schemes × dark + Plex marker), the focus ring and what is chosen, and `--media-danger` an error, as a light theme's + accent and danger are dark on the chrome: `media-marks` re-points `--focus` and `--chosen` on the + player (the Plex player, the YouTube stage) and on every surface painted `--media-chrome` (its menus, + panels and bars), and a form there (`media-scope`) re-points `--danger` too. A chosen item there is + white like the rest, marked by an amber tick (a track) or underline (a tab, `ui/mediaChrome`), never + the accent as text; a choice filled with the accent keeps it, its own text holds on it. + `theme.contract.test.ts` measures the red on the chrome and holds every class string that paints it + to name one of the two. Black and white are the media scrims and their text. Five schemes × dark and light (B2): **Midnight** (blue), **Forest** (cyan), **Slate** (fuchsia), **Matrix** (moss) each earn their place with their own hue family; **Fjord** (Midnight's navy with a calm steel-blue accent) is the one kept exception, the calm twin of the vivid Midnight. A new scheme still needs its own hue family. Normal text ≥ 4.5:1 and a control's edge ≥ 3:1 in **every** scheme and mode. @@ -767,4 +770,4 @@ of a full-width panel and nothing on the right. | 2026-10-09 | A Plex show card plays | Its picture plays the show's next episode everywhere (an episode with a resume point → Continue + Restart, else the first unwatched, else S1·E1), resolved on the click; the preview names it once up; title and ⓘ open the show (option A + hover ask) | The picture opens the show page (as before); every card carrying `next` | A show card on Discover had no Play while Home's had one; carrying `next` costs a grid page up to 180 queries for the few cards anyone plays | | 2026-10-10 | Clip mode in the players (E38) | Scissors in the control row + `C`, `[` `]`; the panel in the player with the watched video as preview; overview + zoomed strip on `ui/RangeTrack` (ends are sliders); a toast + the Ready notification after Cut | A ⋯ menu entry; permanent In / Out buttons; a big dialog with its own preview (a second Plex transcode / YouTube iframe) | Owner's picks A, P1, a; a film is hours and a clip seconds, so only a zoomed strip gives a hand the precision. The Downloads editor's cuts moved onto the same track (keyboard-steppable now) | | 2026-10-10 | Crop in clip mode (E38) | A Crop toggle in the clip panel: a frame over the playing picture (`ui/CropFrame`, the editor's crop too), Free / 16:9 / 9:16 / 1:1, Whole picture; maximised YouTube keeps the panel in the bar's place; the wide side panel shuts while clipping | A frame always on the picture in clip mode (K2); a free shape only | Owner's K1 + a: a crop is rare, so it never covers the picture unasked; a phone or square clip needs a held shape. The iframe hides its picture's size: drawn on the 16:9 box, the cut fits it to the real picture | -| 2026-10-10 | Marks on the player chrome | `--media-mark` (amber) and `--media-danger` (light red), the same in both themes; `media-focus` points the focus ring (`--focus`) at the amber on the player and every chrome surface; `media-scope` (a form) also the errors at the red | The theme's accent ring, warn and danger | On the chrome (#171717) a light theme's accent ring (Slate 2.84:1), warn (2.99:1) and danger (2.77:1) all fall under 3:1 | +| 2026-10-10 | Marks on the player chrome | `--media-mark` (amber) and `--media-danger` (light red), the same in both themes; `media-marks` points the focus ring (`--focus`) and the chosen mark (`--chosen`: a track's tick, a tab's underline; the label stays white) at the amber on the player and every chrome surface; `media-scope` (a form) also the errors at the red | The theme's accent ring, chosen text, warn and danger | On the chrome (#171717) a light theme's accent ring (Slate 2.84:1), warn (2.99:1) and danger (2.77:1) all fall under 3:1; the accent as chosen text under 4.5:1 | diff --git a/frontend/src/components/PlayerModal.tsx b/frontend/src/components/PlayerModal.tsx index ea1ae0b2..da8dcd8d 100644 --- a/frontend/src/components/PlayerModal.tsx +++ b/frontend/src/components/PlayerModal.tsx @@ -936,7 +936,7 @@ export default function PlayerModal({ ref={stageRef} // The wheel over the video is its volume, even inside a stacked card that scrolls. data-wheel-volume="" - className={`player-stage media-focus relative aspect-video w-full bg-black overflow-hidden ${ + className={`player-stage media-marks relative aspect-video w-full bg-black overflow-hidden ${ wide ? (clipOpen ? "rounded-tl-2xl" : "rounded-l-2xl") : "rounded-t-2xl" } ${maximized ? "player-stage--max" : ""}`} > @@ -1009,7 +1009,7 @@ export default function PlayerModal({ {(maximized || isFullscreen) && !clipDocked && (
e.stopPropagation()} - className="absolute inset-x-0 bottom-0 z-menu flex h-14 items-center justify-start gap-x-3 overflow-x-auto border-t border-white/10 bg-media-chrome/95 media-focus px-3 text-sm text-white" + className="absolute inset-x-0 bottom-0 z-menu flex h-14 items-center justify-start gap-x-3 overflow-x-auto border-t border-white/10 bg-media-chrome/95 media-marks px-3 text-sm text-white" > {hasQueue && ( <> diff --git a/frontend/src/components/PlexPlayer.tsx b/frontend/src/components/PlexPlayer.tsx index 43c2b3b9..8a313c36 100644 --- a/frontend/src/components/PlexPlayer.tsx +++ b/frontend/src/components/PlexPlayer.tsx @@ -113,12 +113,13 @@ const BACK_TO_PREV_S = 3; // inside the window skips again (the keyboard keeps ← / → at the viewer's own step). // The player's menus sit on the always-dark media chrome, under white text, in every theme. const PLAYER_MENU = - "rounded-xl border border-white/15 bg-media-chrome/95 media-focus text-sm text-white shadow-2xl"; + "rounded-xl border border-white/15 bg-media-chrome/95 media-marks text-sm text-white shadow-2xl"; const PLAYER_ROW = "block w-full rounded-lg px-2 py-1.5 text-left hover:bg-white/10"; -// A track choice: the chosen one wears the tick in the leading slot (DESIGN "one of several"), which -// the highlight's white text cannot wash out the way it did the accent-coloured label. +// A track choice: white like the rest, the chosen one marked by the tick in the leading slot (DESIGN +// "one of several"), which `media-marks` paints the media amber — a light theme's accent as the +// label's colour was dark on the chrome (Slate 2.84:1), and the highlight's white washed it out. const TRACK_ROW = - "flex w-full min-h-9 items-center gap-2 rounded-lg px-2 py-1.5 text-left hover:bg-white/10"; + "flex w-full min-h-9 items-center gap-2 rounded-lg px-2 py-1.5 text-left text-white hover:bg-white/10"; const TAP_SKIP_S = 10; const DOUBLE_TAP_MS = 300; @@ -1191,7 +1192,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } return createPortal(
{ pointerRef.current = e.pointerType; }} @@ -1677,9 +1678,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } checked={(audioOrd ?? 0) === a.ord} label={audioName(a)} onSelect={() => changeAudio(a.ord)} - className={`${TRACK_ROW} ${ - (audioOrd ?? 0) === a.ord ? "text-accent" : "text-white" - }`} + className={TRACK_ROW} > {audioName(a)} @@ -1696,7 +1695,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } checked={subOrd == null} label={t("plex.player.subOff")} onSelect={() => changeSubtitle(null)} - className={`${TRACK_ROW} ${subOrd == null ? "text-accent" : "text-white"}`} + className={TRACK_ROW} > {t("plex.player.subOff")} @@ -1709,9 +1708,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } checked={subOrd === sub.ord} label={subtitleName(sub)} onSelect={() => changeSubtitle(sub.ord)} - className={`${TRACK_ROW} ${ - subOrd === sub.ord ? "text-accent" : "text-white" - }`} + className={TRACK_ROW} > {subtitleName(sub)} @@ -1840,7 +1837,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } }} >
e.stopPropagation()} > diff --git a/frontend/src/components/PlexPlayerSettings.tsx b/frontend/src/components/PlexPlayerSettings.tsx index f1d18842..d7f9a585 100644 --- a/frontend/src/components/PlexPlayerSettings.tsx +++ b/frontend/src/components/PlexPlayerSettings.tsx @@ -7,6 +7,7 @@ import { Pressable } from "./ui/Pressable"; import { ColorInput, CheckRow } from "./ui/NativeControls"; import Tabs from "./ui/Tabs"; import { useTabPanel } from "./ui/tabPanel"; +import { MEDIA_CHOSEN } from "./ui/mediaChrome"; type SettingsTab = "sync" | "playback" | "subtitle" | "clock"; @@ -65,7 +66,7 @@ export function PlexPlayerSettings(props: { aria-label={t("plex.player.settings")} onWheel={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()} - className="border border-white/15 bg-media-chrome/95 media-focus absolute bottom-full right-0 z-menu mb-2 w-80 rounded-xl p-2 text-sm text-white shadow-2xl" + className="border border-white/15 bg-media-chrome/95 media-marks absolute bottom-full right-0 z-menu mb-2 w-80 rounded-xl p-2 text-sm text-white shadow-2xl" > patchPrefs({ clockDateStyle: st })} className={`touch-target flex-1 rounded px-2 py-1 text-2xs ${ - prefs.clockDateStyle === st - ? "bg-white/15 text-accent" - : "text-white/70 hover:bg-white/10" + prefs.clockDateStyle === st ? MEDIA_CHOSEN : "text-white/70 hover:bg-white/10" }`} > {clockParts(now, { ...prefs, clockDate: true, clockDateStyle: st }).date} diff --git a/frontend/src/components/UpNextCard.tsx b/frontend/src/components/UpNextCard.tsx index ac312f0b..8481e72f 100644 --- a/frontend/src/components/UpNextCard.tsx +++ b/frontend/src/components/UpNextCard.tsx @@ -72,7 +72,7 @@ export function UpNextCard({ role="region" aria-label={t("plex.player.upNext.title")} onClick={(e) => e.stopPropagation()} - className="absolute bottom-24 right-6 z-menu flex w-[22rem] max-w-[calc(100vw-3rem)] gap-3 rounded-2xl border border-white/15 bg-media-chrome/95 media-focus p-3.5 text-white shadow-2xl" + className="absolute bottom-24 right-6 z-menu flex w-[22rem] max-w-[calc(100vw-3rem)] gap-3 rounded-2xl border border-white/15 bg-media-chrome/95 media-marks p-3.5 text-white shadow-2xl" >
{next && ( diff --git a/frontend/src/components/ui/AnchoredPopover.tsx b/frontend/src/components/ui/AnchoredPopover.tsx index 0e95c595..c188d480 100644 --- a/frontend/src/components/ui/AnchoredPopover.tsx +++ b/frontend/src/components/ui/AnchoredPopover.tsx @@ -104,7 +104,7 @@ export const ROVING_HIGHLIGHT = "data-active:bg-accent/35 data-active:text-fg data-active:ring-1 data-active:ring-inset data-active:ring-accent/60"; const HIGHLIGHT_SELECTED = "data-active:bg-accent/35 data-active:text-fg data-active:ring-2 data-active:ring-inset data-active:ring-accent/80"; -// On the always-dark media chrome (a player's menus, `bg-media-chrome media-focus` under white text) the theme's +// On the always-dark media chrome (a player's menus, `bg-media-chrome media-marks` under white text) the theme's // `text-fg` is dark in the light theme: the highlighted row would go dark-on-dark. White instead. const MEDIA_HIGHLIGHT = "data-active:bg-white/15 data-active:text-white data-active:ring-1 data-active:ring-inset data-active:ring-white/40"; diff --git a/frontend/src/components/ui/MenuCheck.tsx b/frontend/src/components/ui/MenuCheck.tsx index 9bd3f03e..adff8dfe 100644 --- a/frontend/src/components/ui/MenuCheck.tsx +++ b/frontend/src/components/ui/MenuCheck.tsx @@ -13,7 +13,7 @@ export function MenuCheck({ kind, on }: { kind: "many" | "one"; on: boolean | "m return (