26a7b04e82
Enhanced the MainLayout and search pages with improved CSS for smoother scrolling, better overflow handling, and more visually appealing backgrounds. Updated the SearchShows match result section for a cleaner, more modern look. Refined keyframe animations and mobile responsiveness for a more polished user experience.
1020 lines
29 KiB
Plaintext
1020 lines
29 KiB
Plaintext
@page "/searchshows"
|
|
@using MovieActorLookup.Models
|
|
@inject ShowService ShowService
|
|
@inject IJSRuntime JSRuntime
|
|
@rendermode InteractiveServer
|
|
|
|
<div class="search-container">
|
|
<div class="search-header">
|
|
<div class="navigation-toggle">
|
|
<a href="/searchmovies" class="nav-btn movies-btn">
|
|
<span class="nav-icon bi bi-film"></span>
|
|
<span class="nav-text">Switch to Movies</span>
|
|
</a>
|
|
</div>
|
|
<h2 class="search-title">Discover Actors & Their Shows</h2>
|
|
<p class="search-subtitle">Search by TV show and character to explore an actor's complete filmography</p>
|
|
</div>
|
|
|
|
<div class="search-form">
|
|
<div class="input-group">
|
|
<div class="input-wrapper">
|
|
<input type="text"
|
|
class="search-input"
|
|
@bind="_tvTitle"
|
|
placeholder="Enter TV show title..."
|
|
@onkeypress="@(async (e) => { if (e.Key == "Enter") await SearchForActorAndCharacters(); })" />
|
|
<div class="input-icon bi bi-tv"></div>
|
|
</div>
|
|
<div class="input-wrapper">
|
|
<input type="text"
|
|
class="search-input"
|
|
@bind="_characterName"
|
|
placeholder="Enter character name..."
|
|
@onkeypress="@(async (e) => { if (e.Key == "Enter") await SearchForActorAndCharacters(); })" />
|
|
<div class="input-icon bi bi-person-vcard"></div>
|
|
</div>
|
|
</div>
|
|
<button class="search-btn @(_isSearching ? "searching" : "")"
|
|
@onclick="SearchForActorAndCharacters"
|
|
disabled="@_isSearching">
|
|
@if (_isSearching)
|
|
{
|
|
<span class="spinner"></span>
|
|
<span>Searching...</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="bi bi-search"></span>
|
|
<span> Search</span>
|
|
}
|
|
</button>
|
|
</div>
|
|
|
|
@if (_isSearching)
|
|
{
|
|
<div class="loading-state">
|
|
<div class="loading-animation">
|
|
<div class="loading-dot"></div>
|
|
<div class="loading-dot"></div>
|
|
<div class="loading-dot"></div>
|
|
</div>
|
|
<p>Finding the perfect match...</p>
|
|
</div>
|
|
}
|
|
else if (_actor != null)
|
|
{
|
|
<div class="results-section">
|
|
<div class="actor-header">
|
|
<h3 class="actor-name">@_actor.Name</h3>
|
|
<div class="results-count">@_actor.TvCharacters.Count show@(_actor.TvCharacters.Count != 1 ? "s" : "") found</div>
|
|
</div>
|
|
|
|
@if (_matchedShow != null)
|
|
{
|
|
<div class="matched-show-section">
|
|
<h4 class="section-title">
|
|
<span class="match-indicator"></span>
|
|
Your Search Result
|
|
</h4>
|
|
<div class="search-match-banner">
|
|
<div class="match-poster">
|
|
<img src="@($"https://image.tmdb.org/t/p/w200{_matchedShow.ShowPosterPath}")"
|
|
alt="@_matchedShow.ShowTitle Poster"
|
|
loading="lazy" />
|
|
</div>
|
|
<div class="match-info">
|
|
<h5 class="match-title">@_matchedShow.ShowTitle</h5>
|
|
<div class="match-character">as @_matchedShow.CharacterName</div>
|
|
<div class="match-date">First aired: @_matchedShow.FirstAirDate</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="filmography-section">
|
|
<h4 class="section-title">Complete TV Filmography</h4>
|
|
|
|
@foreach (var group in _actor.TvCharacters
|
|
.GroupBy(tc => tc.Franchise)
|
|
.OrderBy(g => g.Key == "Standalone")
|
|
.ThenBy(g => g.Key))
|
|
{
|
|
@if (group.Key != "Standalone")
|
|
{
|
|
<div class="franchise-group">
|
|
<h5 class="franchise-title">@group.Key Series</h5>
|
|
</div>
|
|
}
|
|
|
|
<div class="shows-grid">
|
|
@foreach (var show in (group.Key == "Standalone"
|
|
? group.OrderByDescending(tc => DateTime.TryParse(tc.FirstAirDate, out var d) ? d : DateTime.MinValue)
|
|
: group.OrderBy(tc => DateTime.TryParse(tc.FirstAirDate, out var d) ? d : DateTime.MaxValue)))
|
|
{
|
|
<div class="show-card @(show == _matchedShow ? "matched" : "")">
|
|
<div class="poster-container">
|
|
<img class="poster-image"
|
|
src="@($"https://image.tmdb.org/t/p/w500{show.ShowPosterPath}")"
|
|
alt="@show.ShowTitle Poster"
|
|
loading="lazy" />
|
|
<div class="poster-overlay">
|
|
<div class="overlay-content">
|
|
<span class="view-details">View Details</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<h5 class="show-title">@show.ShowTitle</h5>
|
|
<div class="character-name">as @show.CharacterName</div>
|
|
@if (!string.IsNullOrEmpty(show.Overview))
|
|
{
|
|
<div class="overview-container">
|
|
<p class="show-overview @(IsOverviewExpanded(show.ShowTitle) ? "expanded" : "truncated")">
|
|
@show.Overview
|
|
</p>
|
|
@if (show.Overview.Length > 150)
|
|
{
|
|
<button class="read-more-btn" @onclick="() => ToggleOverview(show.ShowTitle)">
|
|
@(IsOverviewExpanded(show.ShowTitle) ? "Show less" : "Read more")
|
|
</button>
|
|
}
|
|
</div>
|
|
}
|
|
<div class="air-date">@show.FirstAirDate</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (!string.IsNullOrEmpty(_tvTitle) || !string.IsNullOrEmpty(_characterName))
|
|
{
|
|
<div class="no-results">
|
|
<div class="no-results-icon bi-search"></div>
|
|
<h4>No matches found</h4>
|
|
<p>Try adjusting your search terms or check the spelling</p>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="welcome-state">
|
|
<div class="welcome-icon bi bi-tv"></div>
|
|
<h4>Ready to explore?</h4>
|
|
<p>Enter a TV show title and character name to discover an actor's complete television journey</p>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
|
min-height: 100vh;
|
|
position: relative;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
body::before {
|
|
content: '';
|
|
position: fixed;
|
|
top: -100px;
|
|
left: -100px;
|
|
right: -100px;
|
|
bottom: -100px;
|
|
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="10" y="20" width="15" height="10" fill="rgba(255,255,255,0.06)" rx="2"/><rect x="70" y="10" width="20" height="15" fill="rgba(255,255,255,0.04)" rx="2"/><rect x="30" y="70" width="12" height="8" fill="rgba(255,255,255,0.05)" rx="1"/></svg>') repeat;
|
|
opacity: 0.4;
|
|
pointer-events: none;
|
|
animation: floatContained 25s ease-in-out infinite;
|
|
z-index: -1;
|
|
will-change: transform;
|
|
backface-visibility: hidden;
|
|
transform-style: preserve-3d;
|
|
}
|
|
|
|
.search-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 2rem 1rem;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
position: relative;
|
|
z-index: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.search-container::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -100px;
|
|
left: -100px;
|
|
right: -100px;
|
|
bottom: -100px;
|
|
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="10" y="20" width="15" height="10" fill="rgba(255,255,255,0.06)" rx="2"/><rect x="70" y="10" width="20" height="15" fill="rgba(255,255,255,0.04)" rx="2"/><rect x="30" y="70" width="12" height="8" fill="rgba(255,255,255,0.05)" rx="1"/></svg>') repeat;
|
|
opacity: 0.4;
|
|
pointer-events: none;
|
|
animation: floatContained 25s ease-in-out infinite;
|
|
z-index: -1;
|
|
overflow: hidden;
|
|
will-change: transform;
|
|
backface-visibility: hidden;
|
|
transform-style: preserve-3d;
|
|
}
|
|
|
|
@@keyframes floatContained {
|
|
0%, 100% {
|
|
transform: translateY(0px) rotate(0deg) scale(1);
|
|
}
|
|
25% {
|
|
transform: translateY(-5px) rotate(45deg) scale(1.02);
|
|
}
|
|
50% {
|
|
transform: translateY(-8px) rotate(90deg) scale(0.98);
|
|
}
|
|
75% {
|
|
transform: translateY(-5px) rotate(135deg) scale(1.02);
|
|
}
|
|
}
|
|
|
|
html {
|
|
overflow-x: hidden;
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
.search-header {
|
|
text-align: center;
|
|
margin-bottom: 2.5rem;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.navigation-toggle {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-bottom: 1.5rem;
|
|
animation: slideInDown 0.8s ease-out 0.2s both;
|
|
}
|
|
|
|
.nav-btn {
|
|
background: rgba(255,255,255,0.15);
|
|
color: white;
|
|
text-decoration: none;
|
|
padding: 0.75rem 1.5rem;
|
|
border-radius: 2rem;
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
transition: all 0.3s ease;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.nav-btn:hover {
|
|
background: rgba(255,255,255,0.25);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
|
color: white;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.nav-btn:active {
|
|
transform: translateY(0px);
|
|
}
|
|
|
|
.nav-icon {
|
|
font-size: 1.1rem;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.nav-btn:hover .nav-icon {
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
.movies-btn {
|
|
background: linear-gradient(135deg, rgba(102, 126, 234, 0.3), rgba(118, 75, 162, 0.3));
|
|
}
|
|
|
|
.movies-btn:hover {
|
|
background: linear-gradient(135deg, rgba(102, 126, 234, 0.5), rgba(118, 75, 162, 0.5));
|
|
}
|
|
|
|
.search-title {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
color: white;
|
|
margin-bottom: 0.5rem;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
animation: slideInDown 1s ease-out;
|
|
}
|
|
|
|
@@keyframes slideInDown {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-50px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.search-subtitle {
|
|
font-size: 1.1rem;
|
|
color: rgba(255,255,255,0.9);
|
|
margin: 0;
|
|
animation: fadeIn 1.5s ease-out 0.3s both;
|
|
}
|
|
|
|
@@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.search-form {
|
|
background: rgba(255,255,255,0.95);
|
|
border-radius: 1.5rem;
|
|
padding: 2rem;
|
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
margin-bottom: 2rem;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
animation: slideInUp 1s ease-out 0.6s both;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
@@keyframes slideInUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(30px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.input-group {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin-bottom: 1.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.input-wrapper {
|
|
flex: 1;
|
|
min-width: 250px;
|
|
position: relative;
|
|
}
|
|
|
|
.search-input {
|
|
width: 100%;
|
|
padding: 1rem 1rem 1rem 3.5rem;
|
|
border: 2px solid #e1e5e9;
|
|
border-radius: 1rem;
|
|
font-size: 1rem;
|
|
transition: all 0.3s ease;
|
|
background: #fafbfc;
|
|
}
|
|
|
|
.search-input:focus {
|
|
outline: none;
|
|
border-color: #4f46e5;
|
|
background: white;
|
|
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1), 0 8px 25px rgba(79, 70, 229, 0.15);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.input-icon {
|
|
position: absolute;
|
|
left: 1.2rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
font-size: 1.3rem;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.input-wrapper:focus-within .input-icon {
|
|
transform: translateY(-50%) scale(1.1);
|
|
}
|
|
|
|
.search-btn {
|
|
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
|
border: none;
|
|
color: white;
|
|
padding: 1rem 2.5rem;
|
|
border-radius: 1rem;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
min-width: 160px;
|
|
justify-content: center;
|
|
box-shadow: 0 4px 15px rgba(79, 70, 229, 0.3);
|
|
}
|
|
|
|
.search-btn:hover:not(:disabled) {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 12px 35px rgba(79, 70, 229, 0.4);
|
|
}
|
|
|
|
.search-btn:active:not(:disabled) {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.search-btn:disabled {
|
|
opacity: 0.7;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.spinner {
|
|
width: 18px;
|
|
height: 18px;
|
|
border: 2px solid rgba(255,255,255,0.3);
|
|
border-top: 2px solid white;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.loading-state, .welcome-state, .no-results {
|
|
text-align: center;
|
|
padding: 3rem 2rem;
|
|
color: rgba(255,255,255,0.9);
|
|
animation: fadeIn 0.5s ease-out;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.loading-animation {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.loading-dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
background: white;
|
|
border-radius: 50%;
|
|
animation: bounce 1.4s ease-in-out infinite both;
|
|
}
|
|
|
|
.loading-dot:nth-child(1) {
|
|
animation-delay: -0.32s;
|
|
}
|
|
|
|
.loading-dot:nth-child(2) {
|
|
animation-delay: -0.16s;
|
|
}
|
|
|
|
@@keyframes bounce {
|
|
0%, 80%, 100% {
|
|
transform: scale(0);
|
|
}
|
|
40% {
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
.welcome-icon, .no-results-icon {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
@@keyframes pulse {
|
|
0%, 100% {
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
transform: scale(1.1);
|
|
}
|
|
}
|
|
|
|
.results-section {
|
|
margin-top: 2rem;
|
|
animation: slideInUp 0.8s ease-out;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.actor-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 2rem;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
background: rgba(255,255,255,0.1);
|
|
padding: 1.5rem 2rem;
|
|
border-radius: 1rem;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.actor-name {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: white;
|
|
margin: 0;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.results-count {
|
|
background: rgba(255,255,255,0.2);
|
|
color: white;
|
|
padding: 0.5rem 1.5rem;
|
|
border-radius: 2rem;
|
|
font-weight: 500;
|
|
font-size: 0.9rem;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
}
|
|
|
|
.matched-show-section {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.search-match-banner {
|
|
background: rgba(255,255,255,0.15);
|
|
border-radius: 1rem;
|
|
padding: 1.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.match-poster {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.match-poster img {
|
|
width: 80px;
|
|
height: 120px;
|
|
object-fit: cover;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.match-info {
|
|
flex: 1;
|
|
color: white;
|
|
}
|
|
|
|
.match-title {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
margin: 0 0 0.5rem 0;
|
|
color: white;
|
|
}
|
|
|
|
.match-character {
|
|
font-size: 0.95rem;
|
|
color: rgba(255,255,255,0.8);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.match-date {
|
|
font-size: 0.85rem;
|
|
color: rgba(255,255,255,0.6);
|
|
}
|
|
|
|
.match-indicator {
|
|
font-family: 'Courier New', monospace;
|
|
background: rgba(16, 185, 129, 0.8);
|
|
color: white;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 0.5rem;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
color: white;
|
|
margin-bottom: 1.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.franchise-group {
|
|
margin: 2rem 0 1rem 0;
|
|
}
|
|
|
|
.franchise-title {
|
|
font-size: 1.3rem;
|
|
font-weight: 600;
|
|
color: white;
|
|
margin: 0;
|
|
padding: 1rem 1.5rem;
|
|
background: rgba(255,255,255,0.1);
|
|
border-radius: 0.5rem;
|
|
border-left: 4px solid rgba(255,255,255,0.5);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.shows-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.show-card {
|
|
background: white;
|
|
border-radius: 1.2rem;
|
|
overflow: hidden;
|
|
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
|
transition: all 0.4s ease;
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
position: relative;
|
|
transform-style: preserve-3d;
|
|
}
|
|
|
|
.show-card:hover {
|
|
transform: translateY(-8px) scale(1.02);
|
|
box-shadow: 0 20px 40px rgba(0,0,0,0.25);
|
|
}
|
|
|
|
.show-card.featured {
|
|
border: 2px solid rgba(255,255,255,0.5);
|
|
box-shadow: 0 12px 35px rgba(79, 70, 229, 0.25);
|
|
}
|
|
|
|
.show-card.matched {
|
|
border: 2px solid #10b981;
|
|
}
|
|
|
|
.poster-container {
|
|
position: relative;
|
|
width: 100%;
|
|
aspect-ratio: 2/3;
|
|
overflow: hidden;
|
|
background: linear-gradient(45deg, #f0f0f0, #e0e0e0);
|
|
}
|
|
|
|
.poster-image {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
background-color: #f8f9fa;
|
|
transition: all 0.4s ease;
|
|
}
|
|
|
|
.poster-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.8) 100%);
|
|
opacity: 0;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: flex-end;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.show-card:hover .poster-overlay {
|
|
opacity: 1;
|
|
}
|
|
|
|
.show-card:hover .poster-image {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.overlay-content {
|
|
color: white;
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.match-badge {
|
|
position: absolute;
|
|
top: 0.8rem;
|
|
right: 0.8rem;
|
|
background: linear-gradient(135deg, #10b981, #059669);
|
|
color: white;
|
|
padding: 0.4rem 1rem;
|
|
border-radius: 1.5rem;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);
|
|
z-index: 2;
|
|
}
|
|
|
|
.card-content {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.show-title {
|
|
font-size: 1.2rem;
|
|
font-weight: 600;
|
|
color: #1a1a1a;
|
|
margin: 0 0 0.5rem 0;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.character-name {
|
|
color: #4f46e5;
|
|
font-weight: 500;
|
|
margin-bottom: 0.75rem;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.show-overview {
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
line-height: 1.4;
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.show-overview.truncated {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 3;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.show-overview.expanded {
|
|
display: block;
|
|
}
|
|
|
|
.read-more-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #4f46e5;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
padding: 0.25rem 0;
|
|
margin-top: 0.5rem;
|
|
text-decoration: underline;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.read-more-btn:hover {
|
|
color: #3730a3;
|
|
transform: translateX(5px);
|
|
}
|
|
|
|
.air-date {
|
|
color: #999;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Mobile optimization */
|
|
@@media (max-width: 768px) {
|
|
.search-container {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.search-title {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.input-group {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.input-wrapper {
|
|
min-width: auto;
|
|
}
|
|
|
|
.actor-header {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.shows-grid {
|
|
grid-template-columns: 1fr;
|
|
gap: 1rem;
|
|
}
|
|
|
|
body::before,
|
|
.search-container::before {
|
|
animation: floatContained 30s ease-in-out infinite;
|
|
}
|
|
|
|
@@keyframes floatContained {
|
|
0%, 100% {
|
|
transform: translateY(0px) rotate(0deg) scale(1);
|
|
}
|
|
25% {
|
|
transform: translateY(-3px) rotate(45deg) scale(1.01);
|
|
}
|
|
50% {
|
|
transform: translateY(-5px) rotate(90deg) scale(0.99);
|
|
}
|
|
75% {
|
|
transform: translateY(-3px) rotate(135deg) scale(1.01);
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
window.startAnimations = () => {
|
|
console.log('Starting TV show page animations...');
|
|
|
|
const spinners = document.querySelectorAll('.spinner');
|
|
spinners.forEach((spinner, index) => {
|
|
let rotation = 0;
|
|
const animate = () => {
|
|
rotation += 6;
|
|
spinner.style.transform = `rotate(${rotation}deg)`;
|
|
if (document.body.contains(spinner)) {
|
|
requestAnimationFrame(animate);
|
|
}
|
|
};
|
|
animate();
|
|
});
|
|
|
|
const loadingDots = document.querySelectorAll('.loading-dot');
|
|
loadingDots.forEach((dot, index) => {
|
|
setTimeout(() => {
|
|
dot.style.animationDelay = `${index * 0.16}s`;
|
|
}, 50);
|
|
});
|
|
|
|
const showCards = document.querySelectorAll('.show-card');
|
|
showCards.forEach((card, index) => {
|
|
card.style.opacity = '0';
|
|
card.style.transform = 'translateY(30px)';
|
|
setTimeout(() => {
|
|
card.style.transition = 'all 0.6s ease';
|
|
card.style.opacity = '1';
|
|
card.style.transform = 'translateY(0)';
|
|
}, index * 100 + 200);
|
|
});
|
|
|
|
const icons = document.querySelectorAll('.input-icon');
|
|
icons.forEach((icon, index) => {
|
|
setTimeout(() => {
|
|
icon.style.animation = 'iconFloat 3s ease-in-out infinite';
|
|
icon.style.animationDelay = `${index * 0.5}s`;
|
|
}, 1000);
|
|
});
|
|
};
|
|
|
|
window.addEventListener('scroll', () => {
|
|
const scrolled = window.pageYOffset;
|
|
const parallaxElements = document.querySelectorAll('.search-container::before');
|
|
parallaxElements.forEach(element => {
|
|
element.style.transform = `translateY(${scrolled * 0.5}px)`;
|
|
});
|
|
});
|
|
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@@keyframes iconFloat {
|
|
0%, 100% { transform: translateY(-50%) translateX(0px); }
|
|
50% { transform: translateY(-50%) translateX(2px); }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
</script>
|
|
|
|
@code {
|
|
private string _tvTitle = "";
|
|
private string _characterName = "";
|
|
private Actor _actor;
|
|
private TvCharacter _matchedShow;
|
|
private bool _isSearching = false;
|
|
private HashSet<string> _expandedOverviews = new HashSet<string>();
|
|
|
|
private bool IsOverviewExpanded(string showTitle)
|
|
{
|
|
return _expandedOverviews.Contains(showTitle);
|
|
}
|
|
|
|
private void ToggleOverview(string showTitle)
|
|
{
|
|
if (_expandedOverviews.Contains(showTitle))
|
|
{
|
|
_expandedOverviews.Remove(showTitle);
|
|
}
|
|
else
|
|
{
|
|
_expandedOverviews.Add(showTitle);
|
|
}
|
|
}
|
|
|
|
private async Task SearchForActorAndCharacters()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(_tvTitle) || string.IsNullOrWhiteSpace(_characterName))
|
|
return;
|
|
|
|
_isSearching = true;
|
|
_actor = null;
|
|
_matchedShow = null;
|
|
StateHasChanged();
|
|
|
|
await Task.Delay(100);
|
|
await JSRuntime.InvokeVoidAsync("startAnimations");
|
|
|
|
try
|
|
{
|
|
var shows = await ShowService.GetShowsByTitle(_tvTitle);
|
|
|
|
foreach (var show in shows)
|
|
{
|
|
var credits = await ShowService.GetShowCreditsById(show.Id);
|
|
|
|
foreach (var cast in credits.Cast)
|
|
{
|
|
if (cast.Character.Contains(_characterName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (_actor == null)
|
|
{
|
|
_actor = new Actor(cast.Name);
|
|
await LoadActorFilmography(cast.Id, (object)show, (object)cast);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_actor != null) break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Search error: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
_isSearching = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task LoadActorFilmography(int personId, object originalShow, object originalCast)
|
|
{
|
|
var filmography = await ShowService.GetActorTvCreditsById(personId);
|
|
|
|
if (filmography?.Cast != null)
|
|
{
|
|
foreach (var role in filmography.Cast)
|
|
{
|
|
var fullShow = await ShowService.GetShowDetailsById(role.Id);
|
|
|
|
var tvCharacter = new TvCharacter
|
|
{
|
|
ShowTitle = role.ShowTitle ?? "Unknown Title",
|
|
CharacterName = role.Character ?? "Unknown Character",
|
|
ShowPosterPath = role.PosterPath ?? "",
|
|
FirstAirDate = DateTime.TryParse(fullShow?.FirstAirDate, out var parsedDate)
|
|
? parsedDate.ToString("MMM dd, yyyy")
|
|
: "Unknown Date",
|
|
Overview = fullShow?.Overview ?? "",
|
|
Franchise = fullShow?.BelongsToCollection?.Name ?? "Standalone"
|
|
};
|
|
|
|
_actor.TvCharacters.Add(tvCharacter);
|
|
|
|
if (_matchedShow == null &&
|
|
string.Equals(role.ShowTitle, GetPropertyValue(originalShow, "Name")?.ToString(), StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(role.Character, GetPropertyValue(originalCast, "Character")?.ToString(), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_matchedShow = tvCharacter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private object GetPropertyValue(object obj, string propertyName)
|
|
{
|
|
if (obj == null) return null;
|
|
var property = obj.GetType().GetProperty(propertyName);
|
|
return property?.GetValue(obj);
|
|
}
|
|
} |