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(
{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 (
);
return (
diff --git a/frontend/src/components/ui/README.md b/frontend/src/components/ui/README.md
index aa6c0c20..4a59f260 100644
--- a/frontend/src/components/ui/README.md
+++ b/frontend/src/components/ui/README.md
@@ -35,13 +35,13 @@ Counter-examples, deliberately NOT here: `PosterTile` (knows about Plex montage
## What lives here
-| area | components |
-| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| overlays & anchoring | `AnchoredPopover` + `anchoredPopoverHooks` (the popover engine: arrows, typeahead, Escape, anchor-flip), `ActionMenu`, `MembershipMenu` (the body of a "which sets is it in" menu: checkbox rows + a field naming a new set), `ContextMenu` + `useContextMenu` (the SAME menu, opened by right-click / long-press / Shift+F10 / a row's `MoreButton` "…" via `buttonProps`; `anchor` for a panel a row opens; `PageMenu` for a page's background menu), `Modal` (+ `ModalFooter`; `initialFocus` by purpose, C4), `Overlay`, `SidePanel`, `Tooltip`, `Toaster` (+ `toastLevels`, `overlayOrder`; `lib/toastDock` docks one control under the stack, in every host), `CommandPalette`, `KeyboardHelp` |
-| actions & inputs | `Button`, `IconButton`, `Pressable` (`selectable` when its text is a title or a name), `Chip` + `chipStyles`, `DragHandle` + `OrderButtons` / `useOrderItems` (the non-drag reorder, WCAG 2.5.7) (one per button family, docs/DESIGN.md; a raw `