mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 06:06:18 -08:00
style: enhance UI components with improved styles, search functionality, and error handling
This commit is contained in:
1 parent
aa6a753aa5
commit
09efead104
15 files changed
+482
-156
No files matched your search
@@ -21,7 +21,7 @@
|
||||
width: '100%',
|
||||
'border-radius': '8px',
|
||||
'aspect-ratio': '2/3',
|
||||
display: 'block'
|
||||
display: 'block',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -35,7 +35,7 @@
|
||||
height: '40px',
|
||||
width: '60%',
|
||||
'margin-bottom': '16px',
|
||||
display: 'block'
|
||||
display: 'block',
|
||||
}"
|
||||
/>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
[theme]="{
|
||||
height: '24px',
|
||||
width: '60px',
|
||||
display: 'block'
|
||||
display: 'block',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -58,7 +58,7 @@
|
||||
height: '16px',
|
||||
'margin-bottom': '8px',
|
||||
width: '100%',
|
||||
display: 'block'
|
||||
display: 'block',
|
||||
}"
|
||||
/>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
[theme]="{
|
||||
height: '40px',
|
||||
width: '100px',
|
||||
display: 'block'
|
||||
display: 'block',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -81,7 +81,7 @@
|
||||
[theme]="{
|
||||
height: '48px',
|
||||
width: '140px',
|
||||
display: 'block'
|
||||
display: 'block',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -91,13 +91,15 @@
|
||||
<!-- Backdrop Image -->
|
||||
<div
|
||||
class="hero__backdrop"
|
||||
[class.hero__backdrop--blurred]="!backdropUrl()"
|
||||
[class.hero__backdrop--blurred]="
|
||||
!backdropUrl() && posterUrl() && !posterError()
|
||||
"
|
||||
[style.backgroundImage]="
|
||||
backdropUrl()
|
||||
backdropUrl() && !posterError()
|
||||
? 'url(' + backdropUrl() + ')'
|
||||
: posterUrl()
|
||||
: posterUrl() && !posterError()
|
||||
? 'url(' + posterUrl() + ')'
|
||||
: 'none'
|
||||
: fallbackBackdropBackground()
|
||||
"
|
||||
></div>
|
||||
|
||||
@@ -108,24 +110,30 @@
|
||||
<div class="hero__content">
|
||||
<!-- Poster Card -->
|
||||
<div class="poster">
|
||||
@if (posterUrl()) {
|
||||
@if (posterUrl() && !posterError()) {
|
||||
<img
|
||||
class="poster__image"
|
||||
[src]="posterUrl()"
|
||||
(error)="
|
||||
$event.target.src =
|
||||
'./assets/images/default-poster.png'
|
||||
"
|
||||
(error)="onPosterError()"
|
||||
alt="Poster"
|
||||
/>
|
||||
} @else {
|
||||
<div class="poster__placeholder">No Poster</div>
|
||||
<div
|
||||
class="poster__placeholder"
|
||||
[style.backgroundImage]="fallbackPosterBackground()"
|
||||
>
|
||||
<mat-icon class="poster__placeholder-icon"
|
||||
>movie</mat-icon
|
||||
>
|
||||
<span class="poster__placeholder-text">{{
|
||||
formattedTitle()
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Details -->
|
||||
<div class="details">
|
||||
<h1 class="details__title">{{ title() }}</h1>
|
||||
<h1 class="details__title">{{ formattedTitle() }}</h1>
|
||||
|
||||
<!-- Tags Slot -->
|
||||
<div class="details__tags">
|
||||
|
||||
@@ -233,11 +233,32 @@
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, #3a3a3a 0%, #2a2a2a 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.875rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: var(--poster-shadow);
|
||||
overflow: hidden;
|
||||
|
||||
&-icon {
|
||||
font-size: 48px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
opacity: 0.5;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +278,11 @@
|
||||
margin: 0 0 16px 0;
|
||||
letter-spacing: -0.02em;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
overflow-wrap: anywhere;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__tags {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
import { Component, input, output } from '@angular/core';
|
||||
import { Component, computed, input, output, signal } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader';
|
||||
@@ -7,11 +6,7 @@ import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader';
|
||||
@Component({
|
||||
selector: 'app-content-hero',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
NgxSkeletonLoaderComponent
|
||||
],
|
||||
imports: [MatIconModule, MatButtonModule, NgxSkeletonLoaderComponent],
|
||||
templateUrl: './content-hero.component.html',
|
||||
styleUrls: ['./content-hero.component.scss'],
|
||||
})
|
||||
@@ -24,6 +19,40 @@ export class ContentHeroComponent {
|
||||
readonly errorMessage = input<string>();
|
||||
|
||||
readonly backClicked = output<void>();
|
||||
readonly posterError = signal(false);
|
||||
|
||||
onPosterError(): void {
|
||||
this.posterError.set(true);
|
||||
}
|
||||
|
||||
readonly formattedTitle = computed(() => {
|
||||
const t = this.title();
|
||||
if (!t) return '';
|
||||
// Replace underscores with spaces for cleaner UX on slug/filename style titles
|
||||
return t.replace(/_/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
});
|
||||
|
||||
private calculateHue(text: string): number {
|
||||
if (!text) return 0;
|
||||
let hash = 0;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
hash = text.charCodeAt(i) + ((hash << 5) - hash);
|
||||
hash = hash & hash;
|
||||
}
|
||||
return Math.abs(hash) % 360;
|
||||
}
|
||||
|
||||
readonly fallbackPosterBackground = computed(() => {
|
||||
const hue = this.calculateHue(this.title() || 'placeholder');
|
||||
const h2 = (hue + 40) % 360;
|
||||
return `linear-gradient(135deg, hsl(${hue}, 40%, 25%) 0%, hsl(${h2}, 50%, 15%) 100%)`;
|
||||
});
|
||||
|
||||
readonly fallbackBackdropBackground = computed(() => {
|
||||
const hue = this.calculateHue(this.title() || 'placeholder');
|
||||
const h2 = (hue + 60) % 360;
|
||||
return `linear-gradient(135deg, hsl(${hue}, 50%, 15%) 0%, hsl(${h2}, 80%, 5%) 100%)`;
|
||||
});
|
||||
|
||||
onBack(): void {
|
||||
this.backClicked.emit();
|
||||
|
||||
Reference in new issue
Block a user