# IPTVnator UI Guidelines This document captures the current UI language used across IPTVnator, with emphasis on channel lists, EPG views, settings surfaces, and shared selection patterns. Use it when changing existing views or introducing new list-based UI in the workspace, Xtream, or Stalker flows. ## Core Principles 1. Prefer shared components over duplicated markup. The canonical channel row is `app-channel-list-item`. 2. Drive emphasis through selection state, not through constant decoration. Neutral rows should stay quiet. Only active or current items should pick up strong color. 3. Use the same selection language everywhere. Selected nav items, channels, and current EPG cards should feel like the same system. 4. Keep dark and light themes intentionally different. Dark theme can carry more density and tinted surfaces. Light theme should be flatter and cleaner, with white or near-white cards. 5. Scroll ownership must be explicit. Headers stay visible. Lists scroll. Do not let nested panes compete for scroll. ## Canonical References - Channel row: `libs/ui/components/src/lib/channel-list-container/channel-list-item/channel-list-item.component.html` - Channel row styles: `libs/ui/components/src/lib/channel-list-container/channel-list-item/channel-list-item.component.scss` - Shared EPG timeline: `libs/ui/epg/src/lib/epg-timeline/epg-timeline.component.html` - Shared EPG timeline styles: `libs/ui/epg/src/lib/epg-timeline/epg-timeline.component.scss` - Shared list selection style: `apps/web/src/nav-list.scss` - Theme tokens: `apps/web/src/m3-theme.scss` - Settings surfaces: `apps/web/src/app/settings/settings.component.scss` - Detail view shell styles: `libs/ui/styles/_detail-view.scss` ## Shared Tokens These tokens are the base for interactive emphasis: - `--app-selection-color` - `--app-selection-surface` - `--app-selection-surface-strong` - `--app-selection-border` - `--app-selection-glow` Use Material surface tokens for neutral surfaces: - `--mat-sys-surface` - `--mat-sys-surface-container-low` - `--mat-sys-surface-container` - `--mat-sys-surface-container-high` - `--mat-sys-outline-variant` - `--mat-sys-on-surface` - `--mat-sys-on-surface-variant` Do not hardcode unrelated accent colors for selected state when these tokens already exist. ## Selection Pattern Apply the same visual recipe to selected list items, active channels, and current EPG items: - Background: `linear-gradient(135deg, var(--app-selection-surface-strong), var(--app-selection-surface))` - Border: `var(--app-selection-border)` - Glow: outer shadow using `var(--app-selection-glow)` - Lift: `transform: translateY(-1px)` for selected list items only - Text: selected text should inherit `var(--app-selection-color)` Use this pattern for: - `.nav-item.selected` / `.nav-item.active` - `.channel-list-item.active` - `.epg-item.current-program` Do not add extra badges, left rails, or second selection systems unless there is a strong reason. ## Detail Views VOD and series detail screens share the detail-view Sass mixin (`@mixin base`) from `libs/ui/styles/_detail-view.scss`. Feature-local `styles/detail-view.scss` files should only `@use` that module and `@include detail-view.base(...)` with small typography overrides when a provider needs them. Do not copy the full detail-view stylesheet into feature libraries. Add shared layout changes to the mixin, and keep provider-specific differences explicit in the wrapper file that includes it. ## Channel List Item The shared row should be reused instead of rebuilding channel markup per view. ### Structure - Min height: `68px` - Horizontal gap: `12px` - Padding: `8px 10px 8px 12px` - Radius: `12px` - Logo shell: `44x44`, rounded, subtle inset treatment - Compact variant: `52px` min height with slightly tighter padding ### Content Layout - Title is one line, medium-bold, slightly condensed - Program title is a secondary line with lower emphasis - Timeline uses three columns: start time, progress bar, end time - Action buttons sit on the trailing edge and inherit row color ### Logo Rules - Show fallback icon only when no image is available or image loading fails - Do not render placeholder and real logo at the same time - Keep logos contained with `object-fit: contain` ## EPG Views ### Shared EPG Pane - Header title stays sticky - Program list is the only scrolling region - Add bottom padding so the last program is not clipped - Current program card uses the same selection treatment as selected channels ### Collapsible Live EPG - Live TV layouts with an internal player render `app-epg-timeline` as the EPG content, including playlist-specific live pages and the global favorites/recent live tabs. - The timeline's own panel bar owns the current-program summary and live date navigation together; there is no separate wrapper component around it. - Collapsed state is shared across M3U, Xtream, and Stalker with `live-epg-panel-state`; missing or invalid values restore to expanded. - The collapsed panel is a slim current-program strip with a trailing progress line and an expand button. Date controls stay out of the collapsed strip. - Do not render the collapsed strip for external MPV/VLC playback; those layouts keep the full EPG-only panel. - Keep the EPG content mounted while collapsed so current-program state can continue updating. ### Collapsible Live Sidebar - M3U, Xtream, and Stalker live layouts share a single sidebar collapse toggle that hides the channels rail to give the player and EPG full width. - Xtream Live TV's root view (`/live` with no selected category) follows the same paginated `All Items` shell as VOD and Series: a widget header with the total channel count, page-size controls, and page navigation above the shared `app-grid-list`. Use the grid list's logo-oriented live variant so channel logos stay contained in 16:9 thumbnails instead of being cropped like VOD/series posters. Selecting a channel from that root grid starts playback, selects the channel's category, highlights the active category and channel, and scrolls the category rail plus virtual channels list to the selected rows when those rails are visible. - In Xtream and Stalker live TV, the same toggle also collapses the workspace shell context sidebar (the "Live Categories" rail rendered by `WorkspaceShellContextSidebarComponent`), matching M3U's "everything quiets" behaviour. The shell categories rail only collapses when the active section is `live` (Xtream) or `itv`/`radio` (Stalker); movies, series, favorites, and recent routes leave it untouched. - Collapsed state is owned by `LiveLayoutSidebarStateService` (`providedIn: 'root'`) in `@iptvnator/portal/shared/util`. Every surface that participates injects the service and reads `isCollapsed`; any toggle calls `service.toggle()`. Persistence delegates to the existing `live-sidebar-state` helpers, so the localStorage key stays unchanged and missing/invalid values restore to expanded. - A `mat-icon-button` with `chevron_left` lives in the sidebar header and toggles state. While collapsed, a floating `chevron_right` mini-fab appears at the left edge of `.content-container` to restore the rail (and the categories rail, in Xtream/Stalker live). - Keyboard shortcut: `Cmd/Ctrl+B`. The handler ignores events that originate inside ``, `