mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
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) <noreply@anthropic.com> Entire-Checkpoint: 6bb6f5796090
This commit is contained in:
1 parent
51347fc226
commit
d163a1fbf2
6 files changed
+68
No files matched your search
@@ -49,9 +49,21 @@
|
||||
>
|
||||
<div
|
||||
class="rail__art"
|
||||
[class.rail__art--live]="
|
||||
card.contentType === 'live'
|
||||
"
|
||||
[style.aspect-ratio]="aspectRatio()"
|
||||
>
|
||||
@if (card.imageUrl && !failedImages()[card.id]) {
|
||||
@if (card.contentType === 'live') {
|
||||
<span
|
||||
class="rail__art-bg"
|
||||
aria-hidden="true"
|
||||
[style.background-image]="
|
||||
'url(' + card.imageUrl + ')'
|
||||
"
|
||||
></span>
|
||||
}
|
||||
<img
|
||||
[src]="card.imageUrl"
|
||||
[alt]="card.title"
|
||||
|
||||
@@ -252,6 +252,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Live TV channel logos are typically small (100–256px square) and look terrible
|
||||
// when stretched to fill a 2:3 portrait card with `object-fit: cover`. The
|
||||
// `--live` modifier centers the contained logo over a soft blurred wash sampled
|
||||
// from the logo itself — same pattern as Apple Podcasts / Plex Live TV tiles.
|
||||
.rail__art--live {
|
||||
img {
|
||||
box-sizing: border-box;
|
||||
object-fit: contain;
|
||||
padding: 26% 18%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
|
||||
}
|
||||
}
|
||||
|
||||
.rail__art-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
filter: blur(28px) saturate(1.4);
|
||||
transform: scale(1.4);
|
||||
opacity: 0.55;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.rail__action-trigger {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface DashboardRailCard {
|
||||
subtitle?: string;
|
||||
imageUrl?: string;
|
||||
icon: string;
|
||||
contentType?: 'live' | 'movie' | 'series';
|
||||
link: string[];
|
||||
state?: Record<string, unknown>;
|
||||
actions?: DashboardRailAction[];
|
||||
|
||||
+1
@@ -54,6 +54,7 @@
|
||||
[class.hero--has-default-backdrop]="
|
||||
item.backdropSource === 'fallback'
|
||||
"
|
||||
[class.hero--live]="item.contentType === 'live'"
|
||||
[routerLink]="item.link"
|
||||
[state]="item.state ?? {}"
|
||||
>
|
||||
|
||||
+21
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user