diff --git a/.changes/ui-watch-progress-colour.md b/.changes/ui-watch-progress-colour.md new file mode 100644 index 000000000..900803ea6 --- /dev/null +++ b/.changes/ui-watch-progress-colour.md @@ -0,0 +1,9 @@ +--- +type: fix +area: ui +--- + +Watch progress has one colour again: your theme's accent on dashboard cards +and catalog posters, and the player's blue everywhere inside the player, so +the next episode's bar no longer turns red in the Up Next rail and the +fullscreen episode list. diff --git a/apps/web/src/m3-theme.scss b/apps/web/src/m3-theme.scss index 8044b7b89..a3c36fd3a 100644 --- a/apps/web/src/m3-theme.scss +++ b/apps/web/src/m3-theme.scss @@ -104,6 +104,9 @@ html { var(--app-selection-color) 34%, transparent ); + // Watch progress in app chrome (rail cards, catalog capsules, the hero). + // Over video the player's fixed `--pc-progress` applies instead. + --app-progress-color: var(--app-selection-color); // Live / on-air accent (broadcast indicator) --app-live-color: #d12c2c; @@ -190,6 +193,9 @@ html { var(--app-selection-color) 44%, transparent ); + // Re-declared so it resolves against this context's selection colour + // rather than inheriting the light value computed on `html`. + --app-progress-color: var(--app-selection-color); // Live / on-air accent (broadcast indicator) --app-live-color: #ff8f8f; diff --git a/apps/web/src/m3-theme.spec.ts b/apps/web/src/m3-theme.spec.ts new file mode 100644 index 000000000..bdacc25f6 --- /dev/null +++ b/apps/web/src/m3-theme.spec.ts @@ -0,0 +1,53 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +// A custom property that reads another one is resolved where it is declared, +// so a token derived from a per-theme colour has to be declared again inside +// `.dark-theme`; inherited from `html`, it would keep the light value there. +const THEME = readFileSync( + resolve(process.cwd(), 'apps/web/src/m3-theme.scss'), + 'utf8' +) + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/(^|\s)\/\/[^\n]*/g, '$1'); + +/** The body of the brace block that opens at `start` (an index of `{`). */ +function block(source: string, start: number): string { + let depth = 0; + for (let index = start; index < source.length; index++) { + if (source[index] === '{') depth++; + if (source[index] === '}' && --depth === 0) { + return source.slice(start + 1, index); + } + } + throw new Error('Unbalanced braces in m3-theme.scss'); +} + +/** Light declarations (the `html` block minus `.dark-theme`) and dark ones. */ +function themeContexts(): { light: string; dark: string } { + const darkStart = THEME.indexOf('.dark-theme {'); + const htmlStart = THEME.lastIndexOf('html {', darkStart); + expect(darkStart).toBeGreaterThan(-1); + expect(htmlStart).toBeGreaterThan(-1); + + const html = block(THEME, THEME.indexOf('{', htmlStart)); + const dark = block(THEME, THEME.indexOf('{', darkStart)); + return { light: html.replace(dark, ''), dark }; +} + +describe('m3-theme watch-progress token', () => { + it('declares --app-progress-color from the selection colour in both themes', () => { + const { light, dark } = themeContexts(); + const progress = + /--app-progress-color:\s*var\(--app-selection-color\);/; + const selection = /--app-selection-color:\s*#[0-9a-f]{6};/i; + + for (const context of [light, dark]) { + expect(context).toMatch(selection); + expect(context).toMatch(progress); + } + expect(light.match(selection)?.[0]).not.toBe( + dark.match(selection)?.[0] + ); + }); +}); diff --git a/docs/architecture/embedded-inline-playback.md b/docs/architecture/embedded-inline-playback.md index e34f48ff6..38751413c 100644 --- a/docs/architecture/embedded-inline-playback.md +++ b/docs/architecture/embedded-inline-playback.md @@ -211,7 +211,8 @@ becomes an "Up Next" episode rail (`app-up-next-rail`, `libs/ui/playback/src/lib/portal-inline-player/up-next-rail.component.ts`). The rail lists the currently playing episode (highlighted, click-inert) followed by the rest of its season and a spillover into the following -seasons, with per-episode watch-progress bars from playback positions. +seasons, with per-episode watch-progress bars from playback positions, +drawn in the player's `--pc-progress` like the dock's Up next card. - Data flow: the hosts (Xtream `SerialDetailsComponent`, Stalker `StalkerSeriesViewComponent`) build the entries with diff --git a/docs/architecture/iptvnator-ui-guidelines.md b/docs/architecture/iptvnator-ui-guidelines.md index 154d88a68..66ca06bdd 100644 --- a/docs/architecture/iptvnator-ui-guidelines.md +++ b/docs/architecture/iptvnator-ui-guidelines.md @@ -525,6 +525,39 @@ a cache read open and checks text contrast across live theme changes. Channel preview progress and EPG current-program progress should stay visually aligned. +### Watch progress colour + +A title's watch progress (its resume share, `progressPercent`) has exactly one +colour per context, and never a literal of its own: + +- **App chrome** — dashboard rail cards and the hero, catalog grids and season + episodes (`app-progress-capsule`, and the season list rows' own fill): + `--app-progress-color`, declared per theme in `apps/web/src/m3-theme.scss` + as that theme's `--app-selection-color`, and declared again inside + `.dark-theme` because a derived custom property resolves where it is + declared. The capsule's green from 90 % marks a finished title; it is a + status, not progress. +- **Over video** — the dock timeline, the Up next card, the Up Next rail and + the fullscreen episode panel: the player's fixed `--pc-progress` (accent + blue `#4f8eff`), never an app token, because the player palette is + theme-independent (see Player And EPG Theme Boundaries). An episode + therefore reads the same in every player surface. The rail and the episode + panel render beside the controls host, outside its `--pc-*` scope, so they + declare the token on their own `:host` with the `progress-token` mixin of + `libs/ui/playback/src/lib/player-controls/_player-palette.scss`. +- ArtPlayer's legacy skin takes a colour string, not a custom property, so it + gets `PLAYER_PROGRESS_COLOR` (`player-palette.ts`), which a spec pins to the + Sass value. +- Live programme progress next to a LIVE marker (the dashboard's live rail and + live hero slides) keeps `--app-live-color`; EPG programme progress keeps the + fill described below. + +Specs hold the rule in each owning project: `m3-theme.spec.ts` (web: the token +in both theme contexts), `player-progress.palette.spec.ts` (ui-playback), +`progress-capsule.component.spec.ts` and +`season-container.progress-colour.spec.ts` (components) and +`dashboard-progress-colour.spec.ts` (workspace-dashboard-feature). + ### Track - Height: diff --git a/docs/architecture/player-controls-contract.md b/docs/architecture/player-controls-contract.md index 4bbdd20fd..6924e2712 100644 --- a/docs/architecture/player-controls-contract.md +++ b/docs/architecture/player-controls-contract.md @@ -317,6 +317,10 @@ record glyph and the recording status (6.2:1 on the glass over a black frame). They are literal on purpose: the overlay is theme-independent (see the UI guidelines' player theme boundary), and the app's `--app-selection-color` is a different blue that would fight the video. +Watch progress reads `--pc-progress`, the accent blue declared by the +`progress-token` mixin of `_player-palette.scss`; the Up Next rail and the +fullscreen episode panel include the same mixin, so one episode's bar has +one colour in every player surface (UI guidelines, "Watch progress colour"). - **Timeline row**: current time (`--pc-font-mono`, tabular) · drawn track (`.player-controls__timeline-track` with one segment and an accent fill, @@ -406,7 +410,7 @@ the native-view legacy dock keeps its plain slider. Near the end of a series episode the dock shows an **"Up next" card** (`app-player-up-next-card`, `data-test-id="player-controls-up-next"`) in the bottom-right corner above the controls: the next episode's still (or its -`S01E03` label as a tile), a 3px accent progress line when it was partly +`S01E03` label as a tile), a 3px `--pc-progress` line when it was partly watched, "Up next · in 7 min" and the title. The host supplies the item through the optional `upNext` input (`PlayerUpNextItem { label, title, thumbnailUrl, progressPercent }`); `ControlsUpNext` decides when it shows — @@ -879,7 +883,7 @@ be. rows: a 16:9 still or, without one, a large numeral tile so the no-TMDB case still looks designed; the `S01E03` label, runtime, watched check or "Now playing" marker; the title (label as fallback); a 3-line clamped - overview when there is one; a progress bar on the thumbnail. Rows are + overview when there is one; a `--pc-progress` bar on the thumbnail. Rows are buttons inside `
  • `s of a `