mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(portals): posters-only cover wall for movie and series grids (#1604)
* feat(portals): posters-only cover wall for movie and series grids Add `Settings.showCoverTitles` (Settings > General, default on). Turning it off drops the title row under VOD/series covers in catalog, favorites and recent grids and reveals the title as a bottom-gradient overlay on hover and keyboard focus, pinned open for items whose cover is missing or failed. `CoverTitlesService` is the single resolver: the opt-out AND a hover-capable pointer, so touch-only devices keep their titles. Live channel grids, search results, "recently added" rails and dashboard rails always keep labels. Catalog and collection cards become keyboard buttons (role, tabindex, aria-label, Enter/Space, focus ring) and poster alt text is the title. The default-on boolean coercion moves into `settings-opt-out.util.ts` because the settings store reached the max-lines limit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): keep nested Remove key presses from activating the card Enter/Space on the content card's nested Remove button bubbled into the card's own key handlers: Enter opened the item before removing it and Space opened it while cancelling the removal. Only keys pressed on the card element itself now activate it. Regression spec added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): keep cover titles while an in-section search filters the grid The posters-only wall exempts search results because they are identified by the name the user typed; the category grid's own in-section filter is the same case, so `app-grid-list` now keeps the title row while its `searchTerm` is non-blank. Contract docs updated, regression spec added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): keep cover titles while the collection tab search is active The unified favorites/recent tab filters by its own search term, so its matches are identified by name like every other search result. The tab now opts its cards out of the posters-only wall while the term is non-blank. Contract docs updated, regression spec added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): move the card Remove control out of the button surface An interactive control nested inside a role="button" is an invalid accessibility structure. The content card's activation surface is now its own inner element and the Remove button a sibling positioned over the poster corner, labelled by its tooltip text. Spec asserts the control is never a descendant of the button. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): draw the collection card focus ring where it is not clipped The card's overflow: hidden clipped an outline drawn on the inner activation surface on every edge, so keyboard users saw no focus indication. The ring now sits on the outer card via :has(> .content-card__activation:focus-visible). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): detect any hover-capable pointer for the posters-only wall `hover` describes only the primary pointer, so a touch-first tablet with a mouse or hover-capable stylus attached lost the wall. The resolver now reads `(any-hover: hover)`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
40092f6efc
commit
ef3f98d026
49 files changed
+1297
-106
No files matched your search
@@ -234,6 +234,62 @@ minimum dimensions and flexible columns over fixed row widths.
|
||||
- Do not render placeholder and real logo at the same time
|
||||
- Keep logos contained with `object-fit: contain`
|
||||
|
||||
## Cover Grids
|
||||
|
||||
Movie and series covers render in three surfaces: the catalog grid
|
||||
(`app-grid-list`, `libs/portal/shared/ui/.../grid-list/`), the favorites /
|
||||
recent card (`app-content-card`, same lib) and the dashboard rails. All of
|
||||
them size from the `--cover-grid-min-width` / `--cover-rail-width` /
|
||||
`--cover-gap` tokens that `Settings.coverSize` writes onto `<html>` as
|
||||
`data-cover-size` (`apps/web/src/_cover-size.scss`).
|
||||
|
||||
### Posters-only wall
|
||||
|
||||
`Settings.showCoverTitles` (Settings > General, default on, only an explicit
|
||||
`false` opts out — coerced like `webPlayerSharedControls`) removes the title
|
||||
row under VOD and series covers so the grid shows more rows per screen.
|
||||
|
||||
- **Resolution.** `CoverTitlesService.postersOnly` (`libs/portal/shared/ui`)
|
||||
is the single source: the opt-out AND a hover-capable pointer
|
||||
(`(any-hover: hover)` media query, tracked live). On touch-only devices the
|
||||
preference is ignored and titles stay under the covers, because a tap
|
||||
already opens the item and there is no gesture left to peek at a hidden
|
||||
name.
|
||||
- **Scope.** Catalog grids (Xtream/Stalker VOD and series), unified
|
||||
favorites/recent grids and the portal favorites tab. Exempt, regardless of
|
||||
the setting: live channel grids (`type` `live`/`itv`/`radio` or the
|
||||
`logo` variant — logos are too often missing to identify a channel),
|
||||
search results and "recently added" rails (they answer by name; hosts
|
||||
pass `[allowPostersOnly]="false"` to `app-content-card`; `app-grid-list`
|
||||
and `app-unified-grid-tab` drop the wall themselves while their
|
||||
`searchTerm` input is non-blank, i.e. an in-section search is filtering
|
||||
the list), and
|
||||
the dashboard rails (their meta rows do not fit an overlay).
|
||||
- **Reveal.** The title is a `.cover-title-overlay` inside the poster
|
||||
wrapper: bottom gradient scrim, two clamped lines, 150 ms ease-out
|
||||
opacity, shown on `:hover` and `:focus-visible` of the card, none under
|
||||
`prefers-reduced-motion`. It is `aria-hidden`; the card itself carries the
|
||||
accessible name.
|
||||
- **Pinned caption.** When the item has no cover to identify it — no
|
||||
poster URL, or the image failed and the default poster / placeholder is
|
||||
showing — the overlay is pinned open (`--pinned`). Both components track
|
||||
failed URLs so the fallback branch re-renders instead of swapping `src`
|
||||
in place.
|
||||
- **Layout hints.** The grid's `contain-intrinsic-size` drops from 270 px to
|
||||
222 px (bare 2/3 poster) under `.grid-list--posters-only`, and the skeleton
|
||||
hides its text lines so loading matches the cards it precedes.
|
||||
- **Keyboard.** Both cards expose a `role="button"`, `tabindex="0"` surface
|
||||
labelled by the title, activated by Enter and Space (Space prevents the
|
||||
page scroll) and carrying a `:focus-visible` ring (`card-focus-ring`
|
||||
mixin in `libs/ui/styles/_content-grid.scss`). On `app-content-card` that
|
||||
surface is the inner `.content-card__activation` element, and the Remove
|
||||
control (labelled by `removeTooltip`) is a SIBLING positioned over the
|
||||
poster corner — an interactive control nested inside a `role="button"`
|
||||
is an invalid accessibility structure. Its ring is drawn on the OUTER
|
||||
`.content-card` via `:has(> .content-card__activation:focus-visible)`,
|
||||
because the card's `overflow: hidden` would clip an outline on the inner
|
||||
surface on every edge. Poster `alt` is the title, not a literal.
|
||||
|
||||
## EPG Views
|
||||
|
||||
The shared timeline and list still contain local dark surfaces, blue selection
|
||||
|
||||
Reference in new issue
Block a user