From d163a1fbf23cd50fa67a6e01434e45717a97fdd2 Mon Sep 17 00:00:00 2001 From: 4gray Date: Tue, 28 Apr 2026 20:14:04 +0200 Subject: [PATCH] feat(dashboard): contained logo + blurred backdrop for live channels Render live TV channel logos with object-fit: contain on a soft blurred wash sampled from the logo itself, instead of stretching small (100-256px) logos to fill 2:3 portrait cards with object-fit: cover. Mirrors the treatment used by Apple Podcasts and Plex Live TV. VOD/series posters still get the full-bleed cover treatment. Co-Authored-By: Claude Opus 4.7 (1M context) Entire-Checkpoint: 6bb6f5796090 --- .../lib/rails/dashboard-rail.component.html | 12 ++++++++ .../lib/rails/dashboard-rail.component.scss | 28 +++++++++++++++++++ .../src/lib/rails/dashboard-rail.component.ts | 1 + .../workspace-dashboard-rails.component.html | 1 + .../workspace-dashboard-rails.component.scss | 21 ++++++++++++++ .../workspace-dashboard-rails.component.ts | 5 ++++ 6 files changed, 68 insertions(+) diff --git a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html index 60dd77c2f..07878677b 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html +++ b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html @@ -49,9 +49,21 @@ >
@if (card.imageUrl && !failedImages()[card.id]) { + @if (card.contentType === 'live') { + + } ; actions?: DashboardRailAction[]; diff --git a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html index 054be0621..abc1c3a7a 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html +++ b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html @@ -54,6 +54,7 @@ [class.hero--has-default-backdrop]=" item.backdropSource === 'fallback' " + [class.hero--live]="item.contentType === 'live'" [routerLink]="item.link" [state]="item.state ?? {}" > diff --git a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.scss b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.scss index 0e25da815..a7f1f8645 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.scss +++ b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.scss @@ -223,6 +223,27 @@ } } +// Live channel logos (small square images) get destroyed by the default +// `object-fit: cover` poster treatment. The `.hero--live` modifier centers +// the logo with breathing room over a tinted backdrop. The blurred +// `.hero__backdrop` already provides the ambient color wash for the wider hero. +.hero--live .hero__poster { + background: + radial-gradient( + circle at 50% 35%, + var(--app-selection-surface, rgba(120, 173, 255, 0.18)) 0%, + transparent 65% + ), + var(--app-widget-bg, #1e2330); + + img { + box-sizing: border-box; + object-fit: contain; + padding: 22% 18%; + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.45)); + } +} + .hero__body { position: relative; z-index: 2; diff --git a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts index abb12ce69..84e7163a9 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts +++ b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts @@ -52,6 +52,7 @@ const SKELETON_RAILS = [1, 2, 3] as const; interface DashboardHeroModel { readonly backdropUrl?: string; readonly backdropSource: DashboardHeroBackdropSource; + readonly contentType?: 'live' | 'movie' | 'series'; readonly fallbackBackdropBackground: string; readonly fallbackPosterBackground: string; readonly hasBackdrop: boolean; @@ -260,6 +261,7 @@ export class WorkspaceDashboardRailsComponent { return { ...artwork, + contentType: item.type, icon: this.typeIcon(item.type), link: this.data.getRecentItemLink(item), state: this.data.getRecentItemNavigationState(item), @@ -379,6 +381,7 @@ export class WorkspaceDashboardRailsComponent { subtitle: `${this.data.getRecentItemProviderLabel(item)} · ${this.data.getRecentItemTypeLabel(item)}`, imageUrl: item.poster_url, icon: this.typeIcon(item.type), + contentType: item.type, link: this.data.getRecentItemLink(item), state: this.data.getRecentItemNavigationState(item), }; @@ -391,6 +394,7 @@ export class WorkspaceDashboardRailsComponent { subtitle: `${this.data.getFavoriteItemProviderLabel(item)} · ${this.data.getFavoriteItemTypeLabel(item)}`, imageUrl: item.poster_url, icon: this.typeIcon(item.type), + contentType: item.type, link: this.data.getGlobalFavoriteLink(item), state: this.data.getGlobalFavoriteNavigationState(item), }; @@ -409,6 +413,7 @@ export class WorkspaceDashboardRailsComponent { subtitle: subtitleParts.join(' · '), imageUrl: item.poster_url, icon: this.typeIcon(item.type), + contentType: item.type, link: this.data.getRecentlyAddedLink(item), state: this.data.getRecentlyAddedNavigationState(item), };