Revamp UI styles and component theming
Visual refresh across multiple views: increased max layout widths (1200/1600 → 1920), adjusted typographic scale, and updated component styling for a more modern, cohesive look. Changes include backdrop-blur, softer borders (reduced border opacity), gradients for accents, rounded-xl corners, hover/transition improvements, and refined spacing for Footer, AddMediaView, BrowseView, CastDetailView, CastView, and various shared components. No functional logic changes — purely presentational updates to improve spacing, responsiveness, and visual polish.
This commit is contained in:
18
src/App.tsx
18
src/App.tsx
@@ -496,16 +496,16 @@ function AppContent() {
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="py-12 px-6 border-t border-border bg-muted/50">
|
<footer className="py-8 px-6 border-t border-border/50 bg-muted/30 backdrop-blur-sm">
|
||||||
<div className="max-w-[1600px] mx-auto flex flex-col md:flex-row items-center justify-between gap-6">
|
<div className="max-w-[1920px] mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-2 text-xl font-black text-muted-foreground">
|
<div className="flex items-center gap-2 text-lg font-black text-muted-foreground">
|
||||||
<div className="w-5 h-5 bg-muted rounded-full" />
|
<div className="w-5 h-5 bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] rounded-full" />
|
||||||
kyoo
|
<span className="bg-clip-text text-transparent bg-gradient-to-r from-foreground to-foreground/70">kyoo</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-8 text-sm font-bold text-muted-foreground">
|
<div className="flex items-center gap-6 text-sm font-bold text-muted-foreground">
|
||||||
<a href="#" className="hover:text-[#6d28d9] transition-colors">Terms</a>
|
<a href="#" className="hover:text-[#6d28d9] transition-colors duration-300">Terms</a>
|
||||||
<a href="#" className="hover:text-[#6d28d9] transition-colors">Privacy</a>
|
<a href="#" className="hover:text-[#6d28d9] transition-colors duration-300">Privacy</a>
|
||||||
<a href="#" className="hover:text-[#6d28d9] transition-colors">Contact</a>
|
<a href="#" className="hover:text-[#6d28d9] transition-colors duration-300">Contact</a>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-medium text-muted-foreground">
|
<p className="text-xs font-medium text-muted-foreground">
|
||||||
© 2026 Kyoo Media Discovery. All rights reserved.
|
© 2026 Kyoo Media Discovery. All rights reserved.
|
||||||
|
|||||||
@@ -181,30 +181,30 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-24 pb-12 px-6 max-w-[1200px] mx-auto">
|
<div className="pt-24 pb-12 px-6 max-w-[1920px] mx-auto">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
className="mb-6 gap-2 text-muted-foreground hover:text-foreground"
|
className="mb-6 gap-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded-xl transition-all duration-300"
|
||||||
>
|
>
|
||||||
<ArrowLeft size={20} />
|
<ArrowLeft size={20} />
|
||||||
Back to Browse
|
Back to Browse
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="bg-card rounded-3xl shadow-xl p-8 border border-border">
|
<div className="bg-card/50 backdrop-blur-sm rounded-3xl shadow-xl p-8 border border-border/50">
|
||||||
<h1 className="text-3xl font-black text-foreground mb-2">Add New Media</h1>
|
<h1 className="text-4xl font-black text-foreground mb-2">Add New Media</h1>
|
||||||
<p className="text-muted-foreground font-medium mb-8">
|
<p className="text-muted-foreground font-medium text-lg mb-8">
|
||||||
Add a new item to your {activeCategory} library.
|
Add a new item to your {activeCategory} library.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{submitStatus === 'success' && (
|
{submitStatus === 'success' && (
|
||||||
<div className="mb-6 p-4 bg-green-500/10 border border-green-500/30 rounded-xl">
|
<div className="mb-6 p-4 bg-green-500/10 border border-green-500/30 rounded-xl backdrop-blur-sm">
|
||||||
<p className="text-green-500 font-bold">✓ Successfully added to library!</p>
|
<p className="text-green-500 font-bold">✓ Successfully added to library!</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{submitStatus === 'error' && (
|
{submitStatus === 'error' && (
|
||||||
<div className="mb-6 p-4 bg-red-500/10 border border-red-500/30 rounded-xl">
|
<div className="mb-6 p-4 bg-red-500/10 border border-red-500/30 rounded-xl backdrop-blur-sm">
|
||||||
<p className="text-red-500 font-bold">✗ Error: {errorMessage}</p>
|
<p className="text-red-500 font-bold">✗ Error: {errorMessage}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -217,7 +217,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.title}
|
value={newMedia.title}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, title: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, title: e.target.value }))}
|
||||||
placeholder="e.g. Mob Psycho 100"
|
placeholder="e.g. Mob Psycho 100"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -229,7 +229,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.year}
|
value={newMedia.year}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, year: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, year: e.target.value }))}
|
||||||
placeholder="2024"
|
placeholder="2024"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -238,7 +238,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
id="category"
|
id="category"
|
||||||
value={newMedia.category}
|
value={newMedia.category}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, category: e.target.value as MediaCategory }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, category: e.target.value as MediaCategory }))}
|
||||||
className="bg-background border-border rounded-xl h-11 px-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none"
|
className="bg-background border-border/50 rounded-xl h-11 px-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none"
|
||||||
>
|
>
|
||||||
{enabledCategories.map(cat => (
|
{enabledCategories.map(cat => (
|
||||||
<option key={cat} value={cat}>{cat}</option>
|
<option key={cat} value={cat}>{cat}</option>
|
||||||
@@ -253,7 +253,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
id="type"
|
id="type"
|
||||||
value={newMedia.type}
|
value={newMedia.type}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, type: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, type: e.target.value }))}
|
||||||
className="bg-background border-border rounded-xl h-11 px-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none"
|
className="bg-background border-border/50 rounded-xl h-11 px-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none"
|
||||||
>
|
>
|
||||||
{newMedia.category === 'Music' ? (
|
{newMedia.category === 'Music' ? (
|
||||||
<>
|
<>
|
||||||
@@ -292,7 +292,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
id="status"
|
id="status"
|
||||||
value={newMedia.status}
|
value={newMedia.status}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, status: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, status: e.target.value }))}
|
||||||
className="bg-background border-border rounded-xl h-11 px-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none"
|
className="bg-background border-border/50 rounded-xl h-11 px-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none"
|
||||||
>
|
>
|
||||||
<option value="Released">Released</option>
|
<option value="Released">Released</option>
|
||||||
<option value="Ongoing">Ongoing</option>
|
<option value="Ongoing">Ongoing</option>
|
||||||
@@ -313,7 +313,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
id="aspectRatio"
|
id="aspectRatio"
|
||||||
value={newMedia.aspectRatio}
|
value={newMedia.aspectRatio}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, aspectRatio: e.target.value as '2/3' | '16/9' | '1/1' }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, aspectRatio: e.target.value as '2/3' | '16/9' | '1/1' }))}
|
||||||
className="bg-background border-border rounded-xl h-11 px-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none"
|
className="bg-background border-border/50 rounded-xl h-11 px-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none"
|
||||||
>
|
>
|
||||||
<option value="2/3">2:3 (Standard Poster)</option>
|
<option value="2/3">2:3 (Standard Poster)</option>
|
||||||
<option value="16/9">16:9 (Wide Thumbnail)</option>
|
<option value="16/9">16:9 (Wide Thumbnail)</option>
|
||||||
@@ -327,7 +327,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.poster}
|
value={newMedia.poster}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, poster: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, poster: e.target.value }))}
|
||||||
placeholder="https://example.com/poster.jpg"
|
placeholder="https://example.com/poster.jpg"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -338,7 +338,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.banner}
|
value={newMedia.banner}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, banner: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, banner: e.target.value }))}
|
||||||
placeholder="https://example.com/banner.jpg"
|
placeholder="https://example.com/banner.jpg"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -348,7 +348,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.description}
|
value={newMedia.description}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, description: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, description: e.target.value }))}
|
||||||
placeholder="Brief description..."
|
placeholder="Brief description..."
|
||||||
className="bg-muted border-border rounded-xl p-3 h-20 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none resize-none"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl p-3 h-20 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none resize-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -362,7 +362,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.rating}
|
value={newMedia.rating}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, rating: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, rating: e.target.value }))}
|
||||||
placeholder="8.5"
|
placeholder="8.5"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{(newMedia.category === 'Anime' || newMedia.category === 'Movies' || newMedia.category === 'TV Series' || newMedia.category === 'Adult') && (
|
{(newMedia.category === 'Anime' || newMedia.category === 'Movies' || newMedia.category === 'TV Series' || newMedia.category === 'Adult') && (
|
||||||
@@ -376,7 +376,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.runtime}
|
value={newMedia.runtime}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, runtime: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, runtime: e.target.value }))}
|
||||||
placeholder="120"
|
placeholder="120"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -386,7 +386,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
type="date"
|
type="date"
|
||||||
value={newMedia.releaseDate}
|
value={newMedia.releaseDate}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, releaseDate: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, releaseDate: e.target.value }))}
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -397,7 +397,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.director}
|
value={newMedia.director}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, director: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, director: e.target.value }))}
|
||||||
placeholder="Director name"
|
placeholder="Director name"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -407,7 +407,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.writer}
|
value={newMedia.writer}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, writer: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, writer: e.target.value }))}
|
||||||
placeholder="Writer name"
|
placeholder="Writer name"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -419,7 +419,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.genres}
|
value={newMedia.genres}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, genres: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, genres: e.target.value }))}
|
||||||
placeholder="Action, Drama, Sci-Fi"
|
placeholder="Action, Drama, Sci-Fi"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -429,7 +429,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.tags}
|
value={newMedia.tags}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, tags: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, tags: e.target.value }))}
|
||||||
placeholder="Classic, Best-selling"
|
placeholder="Classic, Best-selling"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -439,7 +439,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.studios}
|
value={newMedia.studios}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, studios: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, studios: e.target.value }))}
|
||||||
placeholder="Studio A, Studio B"
|
placeholder="Studio A, Studio B"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -449,7 +449,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
value={newMedia.source}
|
value={newMedia.source}
|
||||||
onChange={e => setNewMedia(prev => ({ ...prev, source: e.target.value }))}
|
onChange={e => setNewMedia(prev => ({ ...prev, source: e.target.value }))}
|
||||||
placeholder="e.g. username, xbvr, stashapp"
|
placeholder="e.g. username, xbvr, stashapp"
|
||||||
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border-border/50 rounded-xl h-11 focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -464,12 +464,12 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
{staff.length > 0 && (
|
{staff.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{staff.map((member, index) => (
|
{staff.map((member, index) => (
|
||||||
<div key={index} className="flex items-center gap-3 p-3 bg-muted/50 rounded-xl border border-border">
|
<div key={index} className="flex items-center gap-3 p-3 bg-muted/50 backdrop-blur-sm rounded-xl border border-border/50">
|
||||||
{member.photo && (
|
{member.photo && (
|
||||||
<img
|
<img
|
||||||
src={member.photo}
|
src={member.photo}
|
||||||
alt={member.name}
|
alt={member.name}
|
||||||
className="w-12 h-12 rounded-lg object-cover"
|
className="w-12 h-12 rounded-xl object-cover border border-border/30"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -482,7 +482,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => setStaff(prev => prev.filter((_, i) => i !== index))}
|
onClick={() => setStaff(prev => prev.filter((_, i) => i !== index))}
|
||||||
className="h-8 w-8 text-muted-foreground hover:text-red-500"
|
className="h-8 w-8 text-muted-foreground hover:text-red-500 hover:bg-red-500/10 rounded-xl"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</Button>
|
</Button>
|
||||||
@@ -492,13 +492,13 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Add Staff Form */}
|
{/* Add Staff Form */}
|
||||||
<div className="grid gap-3 p-4 bg-muted/30 rounded-xl border border-border">
|
<div className="grid gap-3 p-4 bg-muted/30 backdrop-blur-sm rounded-xl border border-border/50">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="staffName" className="text-xs font-black text-foreground">Name</Label>
|
<Label htmlFor="staffName" className="text-xs font-black text-foreground">Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id="staffName"
|
id="staffName"
|
||||||
placeholder="Actor name"
|
placeholder="Actor name"
|
||||||
className="bg-background border-border rounded-lg h-9 text-sm focus:ring-[#6d28d9]"
|
className="bg-background border-border/50 rounded-lg h-9 text-sm focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -517,7 +517,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
<Input
|
<Input
|
||||||
id="staffRole"
|
id="staffRole"
|
||||||
placeholder="e.g. Actor, Director"
|
placeholder="e.g. Actor, Director"
|
||||||
className="bg-background border-border rounded-lg h-9 text-sm focus:ring-[#6d28d9]"
|
className="bg-background border-border/50 rounded-lg h-9 text-sm focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -535,7 +535,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
<Input
|
<Input
|
||||||
id="staffCharacter"
|
id="staffCharacter"
|
||||||
placeholder="Character name"
|
placeholder="Character name"
|
||||||
className="bg-background border-border rounded-lg h-9 text-sm focus:ring-[#6d28d9]"
|
className="bg-background border-border/50 rounded-lg h-9 text-sm focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -544,14 +544,14 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
<Input
|
<Input
|
||||||
id="staffPhoto"
|
id="staffPhoto"
|
||||||
placeholder="https://example.com/photo.jpg"
|
placeholder="https://example.com/photo.jpg"
|
||||||
className="bg-background border-border rounded-lg h-9 text-sm focus:ring-[#6d28d9]"
|
className="bg-background border-border/50 rounded-lg h-9 text-sm focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={addStaffMember}
|
onClick={addStaffMember}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full border-border text-sm font-bold"
|
className="w-full border-border/50 text-sm font-bold hover:border-[#6d28d9]/50 hover:bg-[#6d28d9]/10 rounded-xl transition-all duration-300"
|
||||||
>
|
>
|
||||||
+ Add Cast Member
|
+ Add Cast Member
|
||||||
</Button>
|
</Button>
|
||||||
@@ -561,7 +561,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="w-full bg-[#6d28d9] hover:bg-[#5b21b6] text-white font-black h-12 rounded-xl shadow-lg shadow-[#6d28d9]/20 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] hover:from-[#5b21b6] hover:to-[#7c3aed] text-white font-black h-12 rounded-xl shadow-lg shadow-[#6d28d9]/30 transition-all duration-300 hover:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100"
|
||||||
>
|
>
|
||||||
{isSubmitting ? 'SAVING...' : 'SAVE TO LIBRARY'}
|
{isSubmitting ? 'SAVING...' : 'SAVE TO LIBRARY'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -124,14 +124,14 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-24 pb-12 px-6 max-w-[1600px] mx-auto">
|
<div className="pt-24 pb-12 px-6 max-w-[1920px] mx-auto">
|
||||||
{/* Filters Bar */}
|
{/* Filters Bar */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-4 mb-8">
|
<div className="flex flex-wrap items-center justify-between gap-4 mb-8">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{/* Genre Filter */}
|
{/* Genre Filter */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5 font-bold gap-2", selectedGenre ? "text-[#6d28d9] bg-[#6d28d9]/10" : "text-muted-foreground")}>
|
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all duration-300 outline-none select-none focus-visible:ring-2 focus-visible:ring-[#6d28d9]/50 hover:bg-muted/50 hover:text-foreground aria-expanded:bg-muted/50 aria-expanded:text-foreground h-9 gap-2 px-4 font-bold backdrop-blur-sm", selectedGenre ? "text-[#6d28d9] bg-[#6d28d9]/10 border-[#6d28d9]/20" : "text-muted-foreground border-border/50")}>
|
||||||
<Star size={16} />
|
<Star size={16} />
|
||||||
{selectedGenre || 'Genres'}
|
{selectedGenre || 'Genres'}
|
||||||
</button>
|
</button>
|
||||||
@@ -147,7 +147,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
{/* Studio Filter */}
|
{/* Studio Filter */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5 font-bold gap-2", selectedStudio ? "text-[#6d28d9] bg-[#6d28d9]/10" : "text-muted-foreground")}>
|
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all duration-300 outline-none select-none focus-visible:ring-2 focus-visible:ring-[#6d28d9]/50 hover:bg-muted/50 hover:text-foreground aria-expanded:bg-muted/50 aria-expanded:text-foreground h-9 gap-2 px-4 font-bold backdrop-blur-sm", selectedStudio ? "text-[#6d28d9] bg-[#6d28d9]/10 border-[#6d28d9]/20" : "text-muted-foreground border-border/50")}>
|
||||||
Studios
|
Studios
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -163,7 +163,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
{activeCategory === 'Games' && (
|
{activeCategory === 'Games' && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5 font-bold gap-2", selectedPlatform ? "text-[#6d28d9] bg-[#6d28d9]/10" : "text-muted-foreground")}>
|
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all duration-300 outline-none select-none focus-visible:ring-2 focus-visible:ring-[#6d28d9]/50 hover:bg-muted/50 hover:text-foreground aria-expanded:bg-muted/50 aria-expanded:text-foreground h-9 gap-2 px-4 font-bold backdrop-blur-sm", selectedPlatform ? "text-[#6d28d9] bg-[#6d28d9]/10 border-[#6d28d9]/20" : "text-muted-foreground border-border/50")}>
|
||||||
<Monitor size={16} />
|
<Monitor size={16} />
|
||||||
{selectedPlatform || 'Platforms'}
|
{selectedPlatform || 'Platforms'}
|
||||||
</button>
|
</button>
|
||||||
@@ -181,7 +181,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
{activeCategory === 'Games' && (
|
{activeCategory === 'Games' && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5 font-bold gap-2", selectedDeveloper ? "text-[#6d28d9] bg-[#6d28d9]/10" : "text-muted-foreground")}>
|
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all duration-300 outline-none select-none focus-visible:ring-2 focus-visible:ring-[#6d28d9]/50 hover:bg-muted/50 hover:text-foreground aria-expanded:bg-muted/50 aria-expanded:text-foreground h-9 gap-2 px-4 font-bold backdrop-blur-sm", selectedDeveloper ? "text-[#6d28d9] bg-[#6d28d9]/10 border-[#6d28d9]/20" : "text-muted-foreground border-border/50")}>
|
||||||
<Users size={16} />
|
<Users size={16} />
|
||||||
{selectedDeveloper || 'Developers'}
|
{selectedDeveloper || 'Developers'}
|
||||||
</button>
|
</button>
|
||||||
@@ -199,7 +199,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
{activeCategory === 'Games' && (
|
{activeCategory === 'Games' && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5 font-bold gap-2", selectedCategory ? "text-[#6d28d9] bg-[#6d28d9]/10" : "text-muted-foreground")}>
|
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all duration-300 outline-none select-none focus-visible:ring-2 focus-visible:ring-[#6d28d9]/50 hover:bg-muted/50 hover:text-foreground aria-expanded:bg-muted/50 aria-expanded:text-foreground h-9 gap-2 px-4 font-bold backdrop-blur-sm", selectedCategory ? "text-[#6d28d9] bg-[#6d28d9]/10 border-[#6d28d9]/20" : "text-muted-foreground border-border/50")}>
|
||||||
<FolderTree size={16} />
|
<FolderTree size={16} />
|
||||||
{selectedCategory || 'Categories'}
|
{selectedCategory || 'Categories'}
|
||||||
</button>
|
</button>
|
||||||
@@ -217,7 +217,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
{allSources.length > 0 && (
|
{allSources.length > 0 && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5 font-bold gap-2", selectedSource ? "text-[#6d28d9] bg-[#6d28d9]/10" : "text-muted-foreground")}>
|
<button type="button" className={cn("group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all duration-300 outline-none select-none focus-visible:ring-2 focus-visible:ring-[#6d28d9]/50 hover:bg-muted/50 hover:text-foreground aria-expanded:bg-muted/50 aria-expanded:text-foreground h-9 gap-2 px-4 font-bold backdrop-blur-sm", selectedSource ? "text-[#6d28d9] bg-[#6d28d9]/10 border-[#6d28d9]/20" : "text-muted-foreground border-border/50")}>
|
||||||
<Tag size={16} />
|
<Tag size={16} />
|
||||||
{selectedSource || 'Source'}
|
{selectedSource || 'Source'}
|
||||||
</button>
|
</button>
|
||||||
@@ -235,7 +235,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-muted-foreground font-bold"
|
className="text-muted-foreground font-bold hover:text-[#6d28d9] transition-colors"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedGenre(null);
|
setSelectedGenre(null);
|
||||||
setSelectedStudio(null);
|
setSelectedStudio(null);
|
||||||
@@ -250,9 +250,9 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-3">
|
||||||
{/* Grid item size slider */}
|
{/* Grid item size slider */}
|
||||||
<div className="flex items-center gap-3 bg-muted rounded-md px-3 py-2">
|
<div className="flex items-center gap-3 bg-muted/50 backdrop-blur-sm rounded-xl px-4 py-2.5 border border-border/50">
|
||||||
<span className="text-xs font-bold text-muted-foreground">Size</span>
|
<span className="text-xs font-bold text-muted-foreground">Size</span>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
@@ -271,7 +271,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button type="button" className="group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5 text-muted-foreground font-bold gap-2">
|
<button type="button" className="group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all duration-300 outline-none select-none focus-visible:ring-2 focus-visible:ring-[#6d28d9]/50 hover:bg-muted/50 hover:text-foreground aria-expanded:bg-muted/50 aria-expanded:text-foreground h-9 gap-2 px-4 text-muted-foreground font-bold backdrop-blur-sm border-border/50">
|
||||||
<ArrowUpDown size={16} />
|
<ArrowUpDown size={16} />
|
||||||
{sortBy === 'default' ? 'Sort' : sortBy === 'title-asc' ? 'Title (A-Z)' : 'Title (Z-A)'}
|
{sortBy === 'default' ? 'Sort' : sortBy === 'title-asc' ? 'Title (A-Z)' : 'Title (Z-A)'}
|
||||||
</button>
|
</button>
|
||||||
@@ -283,13 +283,13 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
<div className="flex items-center bg-muted rounded-md p-1">
|
<div className="flex items-center bg-muted/50 backdrop-blur-sm rounded-xl p-1 border border-border/50">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-8 w-8 transition-all",
|
"h-8 w-8 transition-all rounded-lg",
|
||||||
viewMode === 'grid' ? "bg-background shadow-sm text-[#6d28d9]" : "text-muted-foreground"
|
viewMode === 'grid' ? "bg-background shadow-sm text-[#6d28d9]" : "text-muted-foreground hover:bg-background/50"
|
||||||
)}
|
)}
|
||||||
onClick={() => setViewMode('grid')}
|
onClick={() => setViewMode('grid')}
|
||||||
>
|
>
|
||||||
@@ -299,8 +299,8 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-8 w-8 transition-all",
|
"h-8 w-8 transition-all rounded-lg",
|
||||||
viewMode === 'list' ? "bg-background shadow-sm text-[#6d28d9]" : "text-muted-foreground"
|
viewMode === 'list' ? "bg-background shadow-sm text-[#6d28d9]" : "text-muted-foreground hover:bg-background/50"
|
||||||
)}
|
)}
|
||||||
onClick={() => setViewMode('list')}
|
onClick={() => setViewMode('list')}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background pb-20">
|
<div className="min-h-screen bg-background pb-20">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<div className="relative h-[40vh] md:h-[50vh] overflow-hidden bg-zinc-900">
|
<div className="relative h-[50vh] md:h-[60vh] overflow-hidden bg-zinc-900">
|
||||||
<img
|
<img
|
||||||
src={person.photo}
|
src={person.photo}
|
||||||
alt={person.name}
|
alt={person.name}
|
||||||
@@ -44,11 +44,11 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
<div className="absolute inset-0 bg-gradient-to-t from-background via-transparent to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-background via-transparent to-transparent" />
|
||||||
|
|
||||||
<div className="absolute inset-0 flex items-end px-6 pb-12">
|
<div className="absolute inset-0 flex items-end px-6 pb-12">
|
||||||
<div className="max-w-[1200px] mx-auto w-full flex flex-col md:flex-row items-center md:items-end gap-8">
|
<div className="max-w-[1920px] mx-auto w-full flex flex-col md:flex-row items-center md:items-end gap-8">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="h-48 md:h-64 rounded-2xl overflow-hidden border-4 border-background shadow-2xl shrink-0"
|
className="h-48 md:h-72 rounded-2xl overflow-hidden border-4 border-background shadow-2xl shrink-0"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={person.photo}
|
src={person.photo}
|
||||||
@@ -64,17 +64,17 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={{ delay: 0.1 }}
|
transition={{ delay: 0.1 }}
|
||||||
>
|
>
|
||||||
<h1 className="text-4xl md:text-6xl font-black text-foreground mb-4 drop-shadow-sm">
|
<h1 className="text-5xl md:text-7xl font-black text-foreground mb-4 drop-shadow-sm">
|
||||||
{person.name}
|
{person.name}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex flex-wrap justify-center md:justify-start gap-3">
|
<div className="flex flex-wrap justify-center md:justify-start gap-3">
|
||||||
{person.occupations?.map(occ => (
|
{person.occupations?.map(occ => (
|
||||||
<Badge key={occ} variant="secondary" className="bg-[#6d28d9]/10 text-[#6d28d9] border-none font-bold px-4 py-1">
|
<Badge key={occ} variant="secondary" className="bg-[#6d28d9]/10 text-[#6d28d9] border-[#6d28d9]/20 font-bold px-4 py-1.5 backdrop-blur-sm">
|
||||||
{occ}
|
{occ}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
{person.filmography && person.filmography.length > 0 && (
|
{person.filmography && person.filmography.length > 0 && (
|
||||||
<Badge variant="outline" className="border-[#6d28d9]/30 text-[#6d28d9] font-bold px-4 py-1">
|
<Badge variant="outline" className="border-[#6d28d9]/30 text-[#6d28d9] font-bold px-4 py-1.5">
|
||||||
{person.filmography.length} Role{person.filmography.length !== 1 ? 's' : ''}
|
{person.filmography.length} Role{person.filmography.length !== 1 ? 's' : ''}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
@@ -88,22 +88,22 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
className="absolute top-24 left-6 bg-white/20 hover:bg-white/40 text-white rounded-full backdrop-blur-md"
|
className="absolute top-24 left-6 bg-white/30 hover:bg-white/50 text-white rounded-2xl backdrop-blur-md transition-all duration-300 hover:scale-110 border border-white/20"
|
||||||
>
|
>
|
||||||
<ArrowLeft size={24} />
|
<ArrowLeft size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Section */}
|
{/* Content Section */}
|
||||||
<div className="max-w-[1200px] mx-auto px-6 mt-12 grid grid-cols-1 lg:grid-cols-3 gap-12">
|
<div className="max-w-[1920px] mx-auto px-6 mt-12 grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||||
{/* Sidebar Info */}
|
{/* Sidebar Info */}
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="bg-muted/50 rounded-3xl p-8 space-y-6 border border-border">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-3xl p-8 space-y-6 border border-border/50">
|
||||||
<h3 className="text-xl font-black text-foreground">Personal Info</h3>
|
<h3 className="text-2xl font-black text-foreground">Personal Info</h3>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Calendar size={20} />
|
<Calendar size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -113,7 +113,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<MapPin size={20} />
|
<MapPin size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -123,7 +123,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Briefcase size={20} />
|
<Briefcase size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -134,7 +134,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
|
|
||||||
{(person.ethnicity || person.adult_specifics?.ethnicity) && (
|
{(person.ethnicity || person.adult_specifics?.ethnicity) && (
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<User size={20} />
|
<User size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -146,13 +146,13 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-muted/50 rounded-3xl p-8 space-y-6 border border-border">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-3xl p-8 space-y-6 border border-border/50">
|
||||||
<h3 className="text-xl font-black text-foreground">Measurements</h3>
|
<h3 className="text-2xl font-black text-foreground">Measurements</h3>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Ruler size={20} />
|
<Ruler size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -164,7 +164,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
|
|
||||||
{(person.weight || person.adult_specifics?.weight) && (
|
{(person.weight || person.adult_specifics?.weight) && (
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Ruler size={20} />
|
<Ruler size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -176,7 +176,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
|
|
||||||
{(person.adult_specifics?.measurements || person.bust_size || person.cup_size || person.waist_size || person.hip_size) && (
|
{(person.adult_specifics?.measurements || person.bust_size || person.cup_size || person.waist_size || person.hip_size) && (
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Ruler size={20} />
|
<Ruler size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -199,7 +199,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
|
|
||||||
{(person.hair_color || person.adult_specifics?.hair_color) && (
|
{(person.hair_color || person.adult_specifics?.hair_color) && (
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Palette size={20} />
|
<Palette size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -211,7 +211,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
|
|
||||||
{(person.eye_color || person.adult_specifics?.eye_color) && (
|
{(person.eye_color || person.adult_specifics?.eye_color) && (
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Eye size={20} />
|
<Eye size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -223,7 +223,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
|
|
||||||
{person.adult_specifics?.tattoos && (
|
{person.adult_specifics?.tattoos && (
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Palette size={20} />
|
<Palette size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -235,7 +235,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
|
|
||||||
{person.adult_specifics?.piercings && (
|
{person.adult_specifics?.piercings && (
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/50">
|
||||||
<Palette size={20} />
|
<Palette size={20} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -252,7 +252,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
<div className="lg:col-span-2 space-y-12">
|
<div className="lg:col-span-2 space-y-12">
|
||||||
{person.bio && (
|
{person.bio && (
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-black text-foreground mb-6 flex items-center gap-3">
|
<h2 className="text-3xl font-black text-foreground mb-6 flex items-center gap-3">
|
||||||
Biography
|
Biography
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-foreground leading-relaxed text-lg">
|
<p className="text-foreground leading-relaxed text-lg">
|
||||||
@@ -263,7 +263,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
|
|
||||||
{person.filmography && person.filmography.length > 0 && (
|
{person.filmography && person.filmography.length > 0 && (
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-black text-foreground mb-6 flex items-center gap-3">
|
<h2 className="text-3xl font-black text-foreground mb-6 flex items-center gap-3">
|
||||||
<User className="text-[#6d28d9]" />
|
<User className="text-[#6d28d9]" />
|
||||||
Characters
|
Characters
|
||||||
</h2>
|
</h2>
|
||||||
@@ -271,7 +271,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
{person.filmography.map(item => (
|
{person.filmography.map(item => (
|
||||||
<div
|
<div
|
||||||
key={`${item.id}-char`}
|
key={`${item.id}-char`}
|
||||||
className="flex items-center gap-4 p-4 rounded-2xl bg-muted/50 border border-border"
|
className="flex items-center gap-4 p-5 rounded-2xl bg-muted/50 border border-border/50 hover:border-[#6d28d9]/30 hover:shadow-lg transition-all duration-300"
|
||||||
>
|
>
|
||||||
<div className="w-20 h-20 rounded-xl overflow-hidden shrink-0 shadow-sm border-2 border-background">
|
<div className="w-20 h-20 rounded-xl overflow-hidden shrink-0 shadow-sm border-2 border-background">
|
||||||
<img
|
<img
|
||||||
@@ -286,7 +286,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
<h4 className="font-black text-foreground truncate">{item.characterName || item.role}</h4>
|
<h4 className="font-black text-foreground truncate">{item.characterName || item.role}</h4>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleMediaClick(item.id.toString())}
|
onClick={() => handleMediaClick(item.id.toString())}
|
||||||
className="text-xs font-bold text-[#6d28d9] hover:underline mt-1 text-left"
|
className="text-xs font-bold text-[#6d28d9] hover:underline mt-1 text-left transition-colors"
|
||||||
>
|
>
|
||||||
in {item.title}
|
in {item.title}
|
||||||
</button>
|
</button>
|
||||||
@@ -305,7 +305,7 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
{person.filmography && person.filmography.length > 0 && (
|
{person.filmography && person.filmography.length > 0 && (
|
||||||
<section>
|
<section>
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h2 className="text-2xl font-black text-foreground flex items-center gap-3">
|
<h2 className="text-3xl font-black text-foreground flex items-center gap-3">
|
||||||
<Film className="text-[#6d28d9]" />
|
<Film className="text-[#6d28d9]" />
|
||||||
Filmography
|
Filmography
|
||||||
</h2>
|
</h2>
|
||||||
@@ -314,14 +314,14 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
|
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
|
||||||
className="rounded-full border-border"
|
className="rounded-xl border-border hover:border-[#6d28d9]/50 transition-all duration-300"
|
||||||
>
|
>
|
||||||
<ListFilter size={16} />
|
<ListFilter size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
<select
|
<select
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => setSortBy(e.target.value as 'year' | 'title' | 'role')}
|
onChange={(e) => setSortBy(e.target.value as 'year' | 'title' | 'role')}
|
||||||
className="bg-muted border border-border rounded-full px-3 py-1.5 text-sm font-bold text-foreground focus:outline-none focus:ring-2 focus:ring-[#6d28d9]"
|
className="bg-muted/50 backdrop-blur-sm border border-border/50 rounded-xl px-4 py-2 text-sm font-bold text-foreground focus:outline-none focus:ring-2 focus:ring-[#6d28d9]/50"
|
||||||
>
|
>
|
||||||
<option value="year">Year</option>
|
<option value="year">Year</option>
|
||||||
<option value="title">Title</option>
|
<option value="title">Title</option>
|
||||||
@@ -334,9 +334,9 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => handleMediaClick(item.id.toString())}
|
onClick={() => handleMediaClick(item.id.toString())}
|
||||||
className="group flex items-center gap-4 p-4 rounded-2xl bg-card border border-border hover:border-[#6d28d9]/30 hover:shadow-lg transition-all cursor-pointer"
|
className="group flex items-center gap-4 p-4 rounded-2xl bg-card border border-border/50 hover:border-[#6d28d9]/30 hover:shadow-lg hover:shadow-[#6d28d9]/10 transition-all duration-300 cursor-pointer"
|
||||||
>
|
>
|
||||||
<div className="w-16 h-20 rounded-lg overflow-hidden shrink-0 shadow-sm">
|
<div className="w-16 h-20 rounded-xl overflow-hidden shrink-0 shadow-sm border border-border/30">
|
||||||
<img
|
<img
|
||||||
src={item.poster || person.photo}
|
src={item.poster || person.photo}
|
||||||
alt={item.title}
|
alt={item.title}
|
||||||
@@ -345,14 +345,14 @@ export default function CastDetailView({ person, relatedMedia }: CastDetailViewP
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<h4 className="font-black text-foreground truncate group-hover:text-[#6d28d9] transition-colors">
|
<h4 className="font-black text-foreground truncate group-hover:text-[#6d28d9] transition-colors duration-300">
|
||||||
{item.title}
|
{item.title}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-xs font-bold text-muted-foreground uppercase tracking-wider mb-1">
|
<p className="text-xs font-bold text-muted-foreground uppercase tracking-wider mb-1">
|
||||||
{item.year || 'Unknown'}
|
{item.year || 'Unknown'}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline" className="text-[10px] font-bold py-0 h-5 border-border">
|
<Badge variant="outline" className="text-[10px] font-bold py-0 h-5 border-border/50">
|
||||||
{item.role}
|
{item.role}
|
||||||
</Badge>
|
</Badge>
|
||||||
{item.category && (
|
{item.category && (
|
||||||
|
|||||||
@@ -186,27 +186,29 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-24 pb-12 px-6 max-w-[1200px] mx-auto">
|
<div className="pt-24 pb-12 px-6 max-w-[1920px] mx-auto">
|
||||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-12">
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-12">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-4xl font-black text-foreground mb-2">Cast & Staff</h1>
|
<h1 className="text-5xl font-black text-foreground mb-3 bg-clip-text text-transparent bg-gradient-to-r from-foreground to-foreground/70">
|
||||||
<p className="text-muted-foreground font-medium">Discover the people behind your favorite media</p>
|
Cast & Staff
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground font-medium text-lg">Discover the people behind your favorite media</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-3">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={18} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={18} />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search cast..."
|
placeholder="Search cast..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="pl-10 w-full md:w-[300px] bg-muted border-none rounded-full h-11"
|
className="pl-10 w-full md:w-[300px] bg-muted/50 backdrop-blur-sm border-none rounded-full h-11"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant={showFilters ? 'default' : 'outline'}
|
variant={showFilters ? 'default' : 'outline'}
|
||||||
size="icon"
|
size="icon"
|
||||||
className={`rounded-full h-11 w-11 ${showFilters ? 'bg-[#6d28d9] text-white border-[#6d28d9]' : 'border-border'}`}
|
className={`rounded-xl h-11 w-11 transition-all duration-300 ${showFilters ? 'bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] text-white border-[#6d28d9]' : 'border-border hover:border-[#6d28d9]/50'}`}
|
||||||
onClick={() => setShowFilters(!showFilters)}
|
onClick={() => setShowFilters(!showFilters)}
|
||||||
>
|
>
|
||||||
<Filter size={20} />
|
<Filter size={20} />
|
||||||
@@ -214,7 +216,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="rounded-full h-11 w-11 border-border"
|
className="rounded-xl h-11 w-11 border-border hover:border-[#6d28d9]/50 transition-all duration-300"
|
||||||
onClick={() => setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc')}
|
onClick={() => setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc')}
|
||||||
>
|
>
|
||||||
<ArrowUpDown size={20} />
|
<ArrowUpDown size={20} />
|
||||||
@@ -223,7 +225,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="rounded-full h-11 w-11 text-muted-foreground hover:text-foreground"
|
className="rounded-xl h-11 w-11 text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-all duration-300"
|
||||||
onClick={handleResetFilters}
|
onClick={handleResetFilters}
|
||||||
title="Reset filters"
|
title="Reset filters"
|
||||||
>
|
>
|
||||||
@@ -238,7 +240,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
initial={{ opacity: 0, height: 0 }}
|
initial={{ opacity: 0, height: 0 }}
|
||||||
animate={{ opacity: 1, height: 'auto' }}
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
exit={{ opacity: 0, height: 0 }}
|
exit={{ opacity: 0, height: 0 }}
|
||||||
className="bg-muted/50 rounded-2xl p-6 mb-6 border border-border"
|
className="bg-muted/50 backdrop-blur-sm rounded-2xl p-6 mb-6 border border-border/50"
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -246,7 +248,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
<select
|
<select
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => setSortBy(e.target.value as any)}
|
onChange={(e) => setSortBy(e.target.value as any)}
|
||||||
className="w-full bg-background border-border rounded-lg px-3 py-2 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none"
|
className="w-full bg-background border-border/50 rounded-xl px-4 py-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none"
|
||||||
>
|
>
|
||||||
<option value="name">Name</option>
|
<option value="name">Name</option>
|
||||||
<option value="role">Role</option>
|
<option value="role">Role</option>
|
||||||
@@ -260,7 +262,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
<select
|
<select
|
||||||
value={filterOccupation}
|
value={filterOccupation}
|
||||||
onChange={(e) => setFilterOccupation(e.target.value)}
|
onChange={(e) => setFilterOccupation(e.target.value)}
|
||||||
className="w-full bg-background border-border rounded-lg px-3 py-2 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none"
|
className="w-full bg-background border-border/50 rounded-xl px-4 py-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none"
|
||||||
>
|
>
|
||||||
<option value="">All Occupations</option>
|
<option value="">All Occupations</option>
|
||||||
{uniqueOccupations.map(occ => (
|
{uniqueOccupations.map(occ => (
|
||||||
@@ -273,7 +275,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
<select
|
<select
|
||||||
value={filterMediaType}
|
value={filterMediaType}
|
||||||
onChange={(e) => setFilterMediaType(e.target.value)}
|
onChange={(e) => setFilterMediaType(e.target.value)}
|
||||||
className="w-full bg-background border-border rounded-lg px-3 py-2 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none"
|
className="w-full bg-background border-border/50 rounded-xl px-4 py-3 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none"
|
||||||
>
|
>
|
||||||
<option value="">All Media Types</option>
|
<option value="">All Media Types</option>
|
||||||
{uniqueMediaTypes.map(type => (
|
{uniqueMediaTypes.map(type => (
|
||||||
@@ -284,7 +286,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex items-center gap-2">
|
<div className="mt-4 flex items-center gap-2">
|
||||||
{searchQuery && (
|
{searchQuery && (
|
||||||
<Badge variant="secondary" className="gap-1">
|
<Badge variant="secondary" className="gap-1 bg-[#6d28d9]/10 text-[#6d28d9] border-[#6d28d9]/20">
|
||||||
Search: {searchQuery}
|
Search: {searchQuery}
|
||||||
<button onClick={() => setSearchQuery('')} className="hover:text-foreground">
|
<button onClick={() => setSearchQuery('')} className="hover:text-foreground">
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
@@ -292,7 +294,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{filterOccupation && (
|
{filterOccupation && (
|
||||||
<Badge variant="secondary" className="gap-1">
|
<Badge variant="secondary" className="gap-1 bg-[#6d28d9]/10 text-[#6d28d9] border-[#6d28d9]/20">
|
||||||
Occupation: {filterOccupation}
|
Occupation: {filterOccupation}
|
||||||
<button onClick={() => setFilterOccupation('')} className="hover:text-foreground">
|
<button onClick={() => setFilterOccupation('')} className="hover:text-foreground">
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
@@ -300,7 +302,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{filterMediaType && (
|
{filterMediaType && (
|
||||||
<Badge variant="secondary" className="gap-1">
|
<Badge variant="secondary" className="gap-1 bg-[#6d28d9]/10 text-[#6d28d9] border-[#6d28d9]/20">
|
||||||
Media Type: {filterMediaType}
|
Media Type: {filterMediaType}
|
||||||
<button onClick={() => setFilterMediaType('')} className="hover:text-foreground">
|
<button onClick={() => setFilterMediaType('')} className="hover:text-foreground">
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
@@ -308,7 +310,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{(sortBy !== 'name' || sortOrder !== 'asc') && (
|
{(sortBy !== 'name' || sortOrder !== 'asc') && (
|
||||||
<Badge variant="secondary" className="gap-1">
|
<Badge variant="secondary" className="gap-1 bg-[#6d28d9]/10 text-[#6d28d9] border-[#6d28d9]/20">
|
||||||
Sort: {sortBy} ({sortOrder})
|
Sort: {sortBy} ({sortOrder})
|
||||||
<button onClick={() => { setSortBy('name'); setSortOrder('asc'); }} className="hover:text-foreground">
|
<button onClick={() => { setSortBy('name'); setSortOrder('asc'); }} className="hover:text-foreground">
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
@@ -322,9 +324,11 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Loading message="Loading cast..." />
|
<Loading message="Loading cast..." />
|
||||||
) : filteredStaff.length === 0 ? (
|
) : filteredStaff.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-20 text-muted-foreground">
|
<div className="flex flex-col items-center justify-center py-32 text-muted-foreground">
|
||||||
<User size={48} className="mb-4 opacity-20" />
|
<div className="w-20 h-20 bg-muted/50 rounded-2xl flex items-center justify-center mb-6 backdrop-blur-sm border border-border/50">
|
||||||
<p className="text-lg font-bold">No cast members found</p>
|
<User size={40} />
|
||||||
|
</div>
|
||||||
|
<p className="text-xl font-bold">No cast members found</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
@@ -336,11 +340,11 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
exit={{ opacity: 0, scale: 0.9 }}
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
className="group bg-card rounded-2xl p-4 shadow-sm border border-border hover:shadow-xl hover:border-[#6d28d9]/20 transition-all duration-300 cursor-pointer"
|
className="group bg-card rounded-2xl p-5 shadow-sm border border-border/50 hover:shadow-xl hover:border-[#6d28d9]/30 hover:shadow-[#6d28d9]/10 transition-all duration-300 cursor-pointer"
|
||||||
onClick={() => onPersonClick(person)}
|
onClick={() => onPersonClick(person)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4 mb-4">
|
<div className="flex items-center gap-4 mb-4">
|
||||||
<div className="w-16 h-16 rounded-full overflow-hidden border-2 border-border group-hover:border-[#6d28d9] transition-colors">
|
<div className="w-16 h-16 rounded-full overflow-hidden border-2 border-border/50 group-hover:border-[#6d28d9] transition-colors duration-300">
|
||||||
<img
|
<img
|
||||||
src={person.photo}
|
src={person.photo}
|
||||||
alt={person.name}
|
alt={person.name}
|
||||||
@@ -349,7 +353,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h3 className="font-black text-foreground truncate group-hover:text-[#6d28d9] transition-colors">
|
<h3 className="font-black text-foreground truncate group-hover:text-[#6d28d9] transition-colors duration-300">
|
||||||
{person.name}
|
{person.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs font-bold text-muted-foreground uppercase tracking-wider">
|
<p className="text-xs font-bold text-muted-foreground uppercase tracking-wider">
|
||||||
@@ -364,8 +368,8 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{person.filmography && person.filmography.length > 0 && (
|
{person.filmography && person.filmography.length > 0 && (
|
||||||
<div className="bg-muted/50 rounded-xl p-3 flex items-center gap-3">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-xl p-3 flex items-center gap-3 border border-border/30">
|
||||||
<div className="w-10 h-12 rounded-lg overflow-hidden shrink-0 bg-background">
|
<div className="w-10 h-12 rounded-lg overflow-hidden shrink-0 bg-background border border-border/30">
|
||||||
<img
|
<img
|
||||||
src={person.filmography[0].poster || person.photo}
|
src={person.filmography[0].poster || person.photo}
|
||||||
alt={person.filmography[0].title}
|
alt={person.filmography[0].title}
|
||||||
@@ -388,7 +392,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
|
|
||||||
{/* Pagination Controls */}
|
{/* Pagination Controls */}
|
||||||
{filteredStaff.length > 0 && (
|
{filteredStaff.length > 0 && (
|
||||||
<div className="mt-12 flex flex-col sm:flex-row items-center justify-between gap-6 border-t border-border pt-8">
|
<div className="mt-12 flex flex-col sm:flex-row items-center justify-between gap-6 border-t border-border/50 pt-8">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span className="text-sm text-muted-foreground font-medium">Items per page:</span>
|
<span className="text-sm text-muted-foreground font-medium">Items per page:</span>
|
||||||
<select
|
<select
|
||||||
@@ -396,7 +400,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setItemsPerPage(Number(e.target.value));
|
setItemsPerPage(Number(e.target.value));
|
||||||
}}
|
}}
|
||||||
className="bg-muted border-none rounded-md px-2 py-1 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9] outline-none"
|
className="bg-muted/50 backdrop-blur-sm border-none rounded-xl px-3 py-2 text-sm font-bold text-foreground focus:ring-2 focus:ring-[#6d28d9]/50 outline-none"
|
||||||
>
|
>
|
||||||
{[12, 20, 36, 48, 60].map(size => (
|
{[12, 20, 36, 48, 60].map(size => (
|
||||||
<option key={size} value={size}>{size}</option>
|
<option key={size} value={size}>{size}</option>
|
||||||
@@ -410,7 +414,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handlePrevPage}
|
onClick={handlePrevPage}
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
className="gap-2 font-bold border-border"
|
className="gap-2 font-bold border-border hover:border-[#6d28d9]/50 rounded-xl transition-all duration-300"
|
||||||
>
|
>
|
||||||
<ChevronLeft size={16} />
|
<ChevronLeft size={16} />
|
||||||
Previous
|
Previous
|
||||||
@@ -427,7 +431,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleNextPage}
|
onClick={handleNextPage}
|
||||||
disabled={currentPage === totalPages || totalPages === 0}
|
disabled={currentPage === totalPages || totalPages === 0}
|
||||||
className="gap-2 font-bold border-border"
|
className="gap-2 font-bold border-border hover:border-[#6d28d9]/50 rounded-xl transition-all duration-300"
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
<ChevronRight size={16} />
|
<ChevronRight size={16} />
|
||||||
|
|||||||
@@ -92,69 +92,83 @@ export default function DashboardView({ mediaList, onMediaClick, loading = false
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-24 pb-12 px-6 max-w-[1600px] mx-auto">
|
<div className="pt-24 pb-12 px-6 max-w-[1920px] mx-auto">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-10">
|
||||||
<h1 className="text-4xl font-black text-foreground mb-2">Dashboard</h1>
|
<h1 className="text-5xl font-black text-foreground mb-3 bg-clip-text text-transparent bg-gradient-to-r from-foreground to-foreground/70">
|
||||||
<p className="text-muted-foreground font-medium">Overview of your media collection</p>
|
Dashboard
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground font-medium text-lg">Overview of your media collection</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-10">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.1 }}
|
transition={{ delay: 0.1 }}
|
||||||
className="bg-muted/50 rounded-xl p-6 border border-border"
|
className="relative overflow-hidden rounded-2xl p-6 bg-gradient-to-br from-[#6d28d9]/10 to-[#8b5cf6]/5 border border-[#6d28d9]/20 hover:border-[#6d28d9]/40 transition-all duration-300 hover:shadow-lg hover:shadow-[#6d28d9]/10"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="absolute top-0 right-0 w-32 h-32 bg-[#6d28d9]/10 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
|
||||||
<Hash className="w-8 h-8 text-[#6d28d9]" />
|
<div className="relative">
|
||||||
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Total</span>
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<Hash className="w-10 h-10 text-[#6d28d9]" />
|
||||||
|
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Total</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-4xl font-black text-foreground">{stats.totalMedia}</div>
|
||||||
|
<div className="text-sm text-muted-foreground font-medium mt-1">Media Items</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-black text-foreground">{stats.totalMedia}</div>
|
|
||||||
<div className="text-sm text-muted-foreground font-medium mt-1">Media Items</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.2 }}
|
transition={{ delay: 0.2 }}
|
||||||
className="bg-muted/50 rounded-xl p-6 border border-border"
|
className="relative overflow-hidden rounded-2xl p-6 bg-gradient-to-br from-yellow-500/10 to-amber-500/5 border border-yellow-500/20 hover:border-yellow-500/40 transition-all duration-300 hover:shadow-lg hover:shadow-yellow-500/10"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="absolute top-0 right-0 w-32 h-32 bg-yellow-500/10 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
|
||||||
<Star className="w-8 h-8 text-yellow-500" />
|
<div className="relative">
|
||||||
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Average</span>
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<Star className="w-10 h-10 text-yellow-500" />
|
||||||
|
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Average</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-4xl font-black text-foreground">{stats.avgRating}</div>
|
||||||
|
<div className="text-sm text-muted-foreground font-medium mt-1">Rating</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-black text-foreground">{stats.avgRating}</div>
|
|
||||||
<div className="text-sm text-muted-foreground font-medium mt-1">Rating</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.3 }}
|
transition={{ delay: 0.3 }}
|
||||||
className="bg-muted/50 rounded-xl p-6 border border-border"
|
className="relative overflow-hidden rounded-2xl p-6 bg-gradient-to-br from-green-500/10 to-emerald-500/5 border border-green-500/20 hover:border-green-500/40 transition-all duration-300 hover:shadow-lg hover:shadow-green-500/10"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="absolute top-0 right-0 w-32 h-32 bg-green-500/10 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
|
||||||
<Play className="w-8 h-8 text-green-500" />
|
<div className="relative">
|
||||||
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Total</span>
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<Play className="w-10 h-10 text-green-500" />
|
||||||
|
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Total</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-4xl font-black text-foreground">{stats.totalPlayCount}</div>
|
||||||
|
<div className="text-sm text-muted-foreground font-medium mt-1">Play Count</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-black text-foreground">{stats.totalPlayCount}</div>
|
|
||||||
<div className="text-sm text-muted-foreground font-medium mt-1">Play Count</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.4 }}
|
transition={{ delay: 0.4 }}
|
||||||
className="bg-muted/50 rounded-xl p-6 border border-border"
|
className="relative overflow-hidden rounded-2xl p-6 bg-gradient-to-br from-blue-500/10 to-cyan-500/5 border border-blue-500/20 hover:border-blue-500/40 transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/10"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="absolute top-0 right-0 w-32 h-32 bg-blue-500/10 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
|
||||||
<Clock className="w-8 h-8 text-blue-500" />
|
<div className="relative">
|
||||||
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Total</span>
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<Clock className="w-10 h-10 text-blue-500" />
|
||||||
|
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Total</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-4xl font-black text-foreground">{formatPlaytime(stats.totalPlaytime)}</div>
|
||||||
|
<div className="text-sm text-muted-foreground font-medium mt-1">Playtime</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-black text-foreground">{formatPlaytime(stats.totalPlaytime)}</div>
|
|
||||||
<div className="text-sm text-muted-foreground font-medium mt-1">Playtime</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -163,13 +177,13 @@ export default function DashboardView({ mediaList, onMediaClick, loading = false
|
|||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.5 }}
|
transition={{ delay: 0.5 }}
|
||||||
className="bg-muted/50 rounded-xl p-6 border border-border mb-8"
|
className="relative overflow-hidden rounded-2xl p-8 bg-gradient-to-br from-muted/50 to-muted/30 border border-border mb-10"
|
||||||
>
|
>
|
||||||
<h2 className="text-lg font-black text-foreground mb-4 flex items-center gap-2">
|
<h2 className="text-2xl font-black text-foreground mb-6 flex items-center gap-3">
|
||||||
<TrendingUp className="w-5 h-5 text-[#6d28d9]" />
|
<TrendingUp className="w-6 h-6 text-[#6d28d9]" />
|
||||||
Category Breakdown
|
Category Breakdown
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-3">
|
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-4">
|
||||||
{(Object.keys(stats.categories) as MediaCategory[]).map((category) => {
|
{(Object.keys(stats.categories) as MediaCategory[]).map((category) => {
|
||||||
const Icon = categoryIcons[category];
|
const Icon = categoryIcons[category];
|
||||||
const count = stats.categories[category] || 0;
|
const count = stats.categories[category] || 0;
|
||||||
@@ -178,11 +192,11 @@ export default function DashboardView({ mediaList, onMediaClick, loading = false
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={category}
|
key={category}
|
||||||
className={`rounded-lg p-4 border ${categoryColors[category]} flex flex-col items-center justify-center gap-2`}
|
className={`rounded-xl p-5 border backdrop-blur-sm transition-all duration-300 hover:scale-105 hover:shadow-lg ${categoryColors[category]} flex flex-col items-center justify-center gap-2`}
|
||||||
>
|
>
|
||||||
<Icon className="w-6 h-6" />
|
<Icon className="w-7 h-7" />
|
||||||
<div className="text-xs font-bold uppercase tracking-wider">{category}</div>
|
<div className="text-xs font-bold uppercase tracking-wider">{category}</div>
|
||||||
<div className="text-2xl font-black">{count}</div>
|
<div className="text-3xl font-black">{count}</div>
|
||||||
<div className="text-xs font-medium opacity-75">{percentage}%</div>
|
<div className="text-xs font-medium opacity-75">{percentage}%</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -196,13 +210,13 @@ export default function DashboardView({ mediaList, onMediaClick, loading = false
|
|||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.6 }}
|
transition={{ delay: 0.6 }}
|
||||||
className="mb-8"
|
className="mb-10"
|
||||||
>
|
>
|
||||||
<h2 className="text-lg font-black text-foreground mb-4 flex items-center gap-2">
|
<h2 className="text-2xl font-black text-foreground mb-6 flex items-center gap-3">
|
||||||
<Clock className="w-5 h-5 text-[#6d28d9]" />
|
<Clock className="w-6 h-6 text-[#6d28d9]" />
|
||||||
Recent Additions
|
Recent Additions
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-4">
|
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-6">
|
||||||
{recentMedia.map((media) => (
|
{recentMedia.map((media) => (
|
||||||
<MediaCard key={media.id} media={media} onClick={onMediaClick} />
|
<MediaCard key={media.id} media={media} onClick={onMediaClick} />
|
||||||
))}
|
))}
|
||||||
@@ -216,13 +230,13 @@ export default function DashboardView({ mediaList, onMediaClick, loading = false
|
|||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.7 }}
|
transition={{ delay: 0.7 }}
|
||||||
className="mb-8"
|
className="mb-10"
|
||||||
>
|
>
|
||||||
<h2 className="text-lg font-black text-foreground mb-4 flex items-center gap-2">
|
<h2 className="text-2xl font-black text-foreground mb-6 flex items-center gap-3">
|
||||||
<Award className="w-5 h-5 text-[#6d28d9]" />
|
<Award className="w-6 h-6 text-[#6d28d9]" />
|
||||||
Top Rated
|
Top Rated
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-4">
|
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-6">
|
||||||
{topRatedMedia.map((media) => (
|
{topRatedMedia.map((media) => (
|
||||||
<MediaCard key={media.id} media={media} onClick={onMediaClick} />
|
<MediaCard key={media.id} media={media} onClick={onMediaClick} />
|
||||||
))}
|
))}
|
||||||
@@ -236,13 +250,13 @@ export default function DashboardView({ mediaList, onMediaClick, loading = false
|
|||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.8 }}
|
transition={{ delay: 0.8 }}
|
||||||
className="mb-8"
|
className="mb-10"
|
||||||
>
|
>
|
||||||
<h2 className="text-lg font-black text-foreground mb-4 flex items-center gap-2">
|
<h2 className="text-2xl font-black text-foreground mb-6 flex items-center gap-3">
|
||||||
<Play className="w-5 h-5 text-[#6d28d9]" />
|
<Play className="w-6 h-6 text-[#6d28d9]" />
|
||||||
Most Played
|
Most Played
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-4">
|
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-6">
|
||||||
{mostPlayedMedia.map((media) => (
|
{mostPlayedMedia.map((media) => (
|
||||||
<MediaCard key={media.id} media={media} onClick={onMediaClick} />
|
<MediaCard key={media.id} media={media} onClick={onMediaClick} />
|
||||||
))}
|
))}
|
||||||
@@ -252,11 +266,11 @@ export default function DashboardView({ mediaList, onMediaClick, loading = false
|
|||||||
|
|
||||||
{/* Empty State */}
|
{/* Empty State */}
|
||||||
{mediaList.length === 0 && (
|
{mediaList.length === 0 && (
|
||||||
<div className="flex flex-col items-center justify-center py-20 text-muted-foreground">
|
<div className="flex flex-col items-center justify-center py-32 text-muted-foreground">
|
||||||
<div className="w-16 h-16 bg-muted rounded-full flex items-center justify-center mb-4">
|
<div className="w-20 h-20 bg-muted/50 rounded-2xl flex items-center justify-center mb-6 backdrop-blur-sm border border-border/50">
|
||||||
<Hash size={32} />
|
<Hash size={40} />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg font-bold">No media found</p>
|
<p className="text-xl font-bold">No media found</p>
|
||||||
<p className="text-sm">Start by adding media to your collection</p>
|
<p className="text-sm">Start by adding media to your collection</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -71,31 +71,31 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
{/* Banner */}
|
{/* Banner */}
|
||||||
<div className="relative h-[400px] w-full overflow-hidden">
|
<div className="relative h-[450px] w-full overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={media.banner || media.poster}
|
src={media.banner || media.poster}
|
||||||
alt={media.title}
|
alt={media.title}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/40 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/50 to-transparent" />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
className="absolute top-24 left-6 p-2 bg-black/20 hover:bg-black/40 text-white rounded-full transition-colors z-10"
|
className="absolute top-24 left-6 p-3 bg-black/30 hover:bg-black/50 backdrop-blur-md text-white rounded-2xl transition-all duration-300 hover:scale-110 z-10 border border-white/20"
|
||||||
>
|
>
|
||||||
<ChevronLeft size={24} />
|
<ChevronLeft size={24} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="max-w-[1400px] mx-auto px-6 -mt-32 relative z-10 pb-24">
|
<div className="max-w-[1920px] mx-auto px-6 -mt-32 relative z-10 pb-24">
|
||||||
<div className="flex flex-col md:flex-row gap-6">
|
<div className="flex flex-col md:flex-row gap-8">
|
||||||
{/* Left Column: Poster + Metadata */}
|
{/* Left Column: Poster + Metadata */}
|
||||||
<div className="w-full md:w-[300px] shrink-0">
|
<div className="w-full md:w-[320px] shrink-0">
|
||||||
<motion.div
|
<motion.div
|
||||||
layoutId={`media-${media.id}`}
|
layoutId={`media-${media.id}`}
|
||||||
className={`rounded-xl overflow-hidden shadow-2xl bg-card ${
|
className={`rounded-2xl overflow-hidden shadow-2xl bg-card border border-border/50 ${
|
||||||
media.aspectRatio === '16/9' ? 'aspect-video' :
|
media.aspectRatio === '16/9' ? 'aspect-video' :
|
||||||
media.aspectRatio === '1/1' ? 'aspect-square' :
|
media.aspectRatio === '1/1' ? 'aspect-square' :
|
||||||
'aspect-[2/3]'
|
'aspect-[2/3]'
|
||||||
@@ -187,25 +187,25 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
|
|
||||||
{/* Right Column: Info */}
|
{/* Right Column: Info */}
|
||||||
<div className="flex-1 pt-4 md:pt-8">
|
<div className="flex-1 pt-4 md:pt-8">
|
||||||
<div className="flex flex-wrap items-end justify-between gap-4 mb-6">
|
<div className="flex flex-wrap items-end justify-between gap-4 mb-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-4xl font-black text-foreground mb-2">
|
<h1 className="text-5xl font-black text-foreground mb-3">
|
||||||
{media.title} <span className="text-muted-foreground font-medium">({media.year})</span>
|
{media.title} <span className="text-muted-foreground font-medium">({media.year})</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button size="icon" className="rounded-full bg-[#6d28d9] hover:bg-[#5b21b6]">
|
<Button size="icon" className="rounded-full bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] hover:from-[#5b21b6] hover:to-[#7c3aed] shadow-lg shadow-[#6d28d9]/30 transition-all duration-300 hover:scale-110">
|
||||||
<Play size={20} fill="currentColor" />
|
<Play size={20} fill="currentColor" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="icon" variant="outline" className="rounded-full border-border">
|
<Button size="icon" variant="outline" className="rounded-full border-border hover:border-[#6d28d9]/50 transition-all duration-300 hover:scale-110">
|
||||||
<Bookmark size={20} />
|
<Bookmark size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="icon" variant="outline" className="rounded-full border-border">
|
<Button size="icon" variant="outline" className="rounded-full border-border hover:border-[#6d28d9]/50 transition-all duration-300 hover:scale-110">
|
||||||
<MoreHorizontal size={20} />
|
<MoreHorizontal size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 text-foreground font-bold">
|
<div className="flex items-center gap-1 text-foreground font-bold">
|
||||||
<Star size={18} className="text-yellow-500" fill="currentColor" />
|
<Star size={20} className="text-yellow-500 fill-yellow-500" />
|
||||||
{media.rating} / 10
|
{media.rating} / 10
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -215,7 +215,7 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
<h3 className="text-xs font-black text-[#6d28d9] uppercase tracking-wider mb-2">Genres</h3>
|
<h3 className="text-xs font-black text-[#6d28d9] uppercase tracking-wider mb-2">Genres</h3>
|
||||||
<div className="flex flex-col items-end gap-1">
|
<div className="flex flex-col items-end gap-1">
|
||||||
{media.genres?.map(genre => (
|
{media.genres?.map(genre => (
|
||||||
<span key={genre} className="text-sm font-bold text-foreground hover:text-[#6d28d9] cursor-pointer transition-colors">
|
<span key={genre} className="text-sm font-bold text-foreground hover:text-[#6d28d9] cursor-pointer transition-colors duration-300">
|
||||||
• {genre}
|
• {genre}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@@ -243,7 +243,7 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
{media.staff && media.staff.length > 0 && (
|
{media.staff && media.staff.length > 0 && (
|
||||||
<section className="mt-20">
|
<section className="mt-20">
|
||||||
<div className="flex items-center justify-between mb-8">
|
<div className="flex items-center justify-between mb-8">
|
||||||
<h2 className="text-2xl font-black text-foreground">Cast & Crew</h2>
|
<h2 className="text-3xl font-black text-foreground">Cast & Crew</h2>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span className="text-sm font-bold text-muted-foreground">
|
<span className="text-sm font-bold text-muted-foreground">
|
||||||
{showAllCast ? media.staff.length : displayedCast.length} / {media.staff.length}
|
{showAllCast ? media.staff.length : displayedCast.length} / {media.staff.length}
|
||||||
@@ -253,7 +253,7 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setShowAllCast(!showAllCast)}
|
onClick={() => setShowAllCast(!showAllCast)}
|
||||||
className="rounded-full border-border font-bold"
|
className="rounded-xl border-border font-bold hover:border-[#6d28d9]/50 transition-all duration-300"
|
||||||
>
|
>
|
||||||
{showAllCast ? 'Show Less' : 'Show All'}
|
{showAllCast ? 'Show Less' : 'Show All'}
|
||||||
<ChevronDown size={16} className={`ml-2 transition-transform ${showAllCast ? 'rotate-180' : ''}`} />
|
<ChevronDown size={16} className={`ml-2 transition-transform ${showAllCast ? 'rotate-180' : ''}`} />
|
||||||
@@ -265,17 +265,17 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
{displayedCast.map(person => (
|
{displayedCast.map(person => (
|
||||||
<div
|
<div
|
||||||
key={person.id}
|
key={person.id}
|
||||||
className="flex items-center gap-4 bg-card p-3 rounded-xl shadow-sm border border-border hover:shadow-md transition-shadow cursor-pointer group"
|
className="flex items-center gap-4 bg-card p-4 rounded-2xl shadow-sm border border-border/50 hover:shadow-xl hover:border-[#6d28d9]/30 transition-all duration-300 cursor-pointer group"
|
||||||
onClick={() => onPersonClick(person)}
|
onClick={() => onPersonClick(person)}
|
||||||
>
|
>
|
||||||
<div className="w-16 h-20 rounded-lg overflow-hidden shrink-0">
|
<div className="w-16 h-20 rounded-xl overflow-hidden shrink-0 border border-border/30">
|
||||||
<img src={person.photo} alt={person.name} className="w-full h-full object-cover group-hover:scale-105 transition-transform" referrerPolicy="no-referrer" />
|
<img src={person.photo} alt={person.name} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" referrerPolicy="no-referrer" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="font-bold text-foreground truncate group-hover:text-[#6d28d9] transition-colors">{person.name}</h4>
|
<h4 className="font-bold text-foreground truncate group-hover:text-[#6d28d9] transition-colors duration-300">{person.name}</h4>
|
||||||
<p className="text-xs text-muted-foreground truncate">{person.characterName || person.role}</p>
|
<p className="text-xs text-muted-foreground truncate">{person.characterName || person.role}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-16 h-20 rounded-lg overflow-hidden shrink-0 bg-muted">
|
<div className="w-16 h-20 rounded-xl overflow-hidden shrink-0 bg-muted border border-border/30">
|
||||||
<img src={person.characterImage} alt={person.characterName} className="w-full h-full object-contain" referrerPolicy="no-referrer" />
|
<img src={person.characterImage} alt={person.characterName} className="w-full h-full object-contain" referrerPolicy="no-referrer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,7 +289,7 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
<section className="mt-20">
|
<section className="mt-20">
|
||||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="flex items-center gap-2 text-[#6d28d9] font-black text-xl">
|
<div className="flex items-center gap-2 text-[#6d28d9] font-black text-2xl">
|
||||||
<span className="opacity-40">{media.episodes.length}</span> Episode{media.episodes.length !== 1 ? 's' : ''}
|
<span className="opacity-40">{media.episodes.length}</span> Episode{media.episodes.length !== 1 ? 's' : ''}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-bold text-muted-foreground">
|
<div className="text-sm font-bold text-muted-foreground">
|
||||||
@@ -299,12 +299,12 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={16} />
|
||||||
<Input placeholder="Search" className="pl-10 w-[200px] bg-muted border-none rounded-full h-9 text-sm" />
|
<Input placeholder="Search" className="pl-10 w-[200px] bg-muted/50 backdrop-blur-sm border-none rounded-full h-9 text-sm" />
|
||||||
</div>
|
</div>
|
||||||
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
<Button variant="ghost" size="icon" className="text-muted-foreground hover:bg-muted/50 rounded-xl transition-all duration-300">
|
||||||
<MoreHorizontal size={20} />
|
<MoreHorizontal size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
<Button variant="ghost" size="icon" className="text-muted-foreground hover:bg-muted/50 rounded-xl transition-all duration-300">
|
||||||
<ListFilter size={20} />
|
<ListFilter size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -315,10 +315,10 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
.map(Number)
|
.map(Number)
|
||||||
.sort((a, b) => a - b)
|
.sort((a, b) => a - b)
|
||||||
.map(season => (
|
.map(season => (
|
||||||
<div key={season} className="border border-border rounded-2xl overflow-hidden">
|
<div key={season} className="border border-border/50 rounded-2xl overflow-hidden bg-card/50 backdrop-blur-sm">
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleSeason(season)}
|
onClick={() => toggleSeason(season)}
|
||||||
className="w-full flex items-center justify-between p-6 bg-card hover:bg-muted/50 transition-colors"
|
className="w-full flex items-center justify-between p-6 bg-card/50 hover:bg-muted/50 transition-colors duration-300"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<h3 className="text-2xl font-black text-foreground">Season {season}</h3>
|
<h3 className="text-2xl font-black text-foreground">Season {season}</h3>
|
||||||
@@ -338,13 +338,13 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
{episodesBySeason[season].map(episode => (
|
{episodesBySeason[season].map(episode => (
|
||||||
<div key={episode.id} className="group cursor-pointer">
|
<div key={episode.id} className="group cursor-pointer">
|
||||||
<div className="flex flex-col md:flex-row gap-6">
|
<div className="flex flex-col md:flex-row gap-6">
|
||||||
<div className="w-full md:w-[240px] shrink-0 aspect-video rounded-xl overflow-hidden shadow-sm relative">
|
<div className="w-full md:w-[240px] shrink-0 aspect-video rounded-2xl overflow-hidden shadow-sm relative border border-border/30">
|
||||||
<img src={episode.thumbnail} alt={episode.title} className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" referrerPolicy="no-referrer" />
|
<img src={episode.thumbnail} alt={episode.title} className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" referrerPolicy="no-referrer" />
|
||||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
|
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors duration-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 py-1">
|
<div className="flex-1 py-1">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<h3 className="font-black text-foreground group-hover:text-[#6d28d9] transition-colors">
|
<h3 className="font-black text-foreground group-hover:text-[#6d28d9] transition-colors duration-300">
|
||||||
E{episode.episode_number} • {episode.title}
|
E{episode.episode_number} • {episode.title}
|
||||||
</h3>
|
</h3>
|
||||||
<span className="text-xs font-bold text-muted-foreground">{episode.air_date} • {episode.duration}m</span>
|
<span className="text-xs font-bold text-muted-foreground">{episode.air_date} • {episode.duration}m</span>
|
||||||
@@ -354,7 +354,7 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="mt-6 bg-border" />
|
<Separator className="mt-6 bg-border/50" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -370,36 +370,36 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
<section className="mt-20">
|
<section className="mt-20">
|
||||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="flex items-center gap-2 text-[#6d28d9] font-black text-xl">
|
<div className="flex items-center gap-2 text-[#6d28d9] font-black text-2xl">
|
||||||
<span className="opacity-40">{media.tracks.length}</span> Track{media.tracks.length !== 1 ? 's' : ''}
|
<span className="opacity-40">{media.tracks.length}</span> Track{media.tracks.length !== 1 ? 's' : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={16} />
|
||||||
<Input placeholder="Search" className="pl-10 w-[200px] bg-muted border-none rounded-full h-9 text-sm" />
|
<Input placeholder="Search" className="pl-10 w-[200px] bg-muted/50 backdrop-blur-sm border-none rounded-full h-9 text-sm" />
|
||||||
</div>
|
</div>
|
||||||
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
<Button variant="ghost" size="icon" className="text-muted-foreground hover:bg-muted/50 rounded-xl transition-all duration-300">
|
||||||
<MoreHorizontal size={20} />
|
<MoreHorizontal size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
<Button variant="ghost" size="icon" className="text-muted-foreground hover:bg-muted/50 rounded-xl transition-all duration-300">
|
||||||
<ListFilter size={20} />
|
<ListFilter size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border border-border rounded-2xl overflow-hidden">
|
<div className="border border-border/50 rounded-2xl overflow-hidden bg-card/50 backdrop-blur-sm">
|
||||||
<div className="divide-y divide-border">
|
<div className="divide-y divide-border/50">
|
||||||
{media.tracks
|
{media.tracks
|
||||||
.sort((a, b) => a.track_number - b.track_number)
|
.sort((a, b) => a.track_number - b.track_number)
|
||||||
.map((track, index) => (
|
.map((track, index) => (
|
||||||
<div key={track.id} className="group cursor-pointer hover:bg-muted/50 transition-colors">
|
<div key={track.id} className="group cursor-pointer hover:bg-muted/50 transition-colors duration-300">
|
||||||
<div className="flex items-center gap-4 p-4">
|
<div className="flex items-center gap-4 p-4">
|
||||||
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center text-xs font-bold text-muted-foreground group-hover:bg-[#6d28d9] group-hover:text-white transition-colors">
|
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center text-xs font-bold text-muted-foreground group-hover:bg-gradient-to-br group-hover:from-[#6d28d9] group-hover:to-[#8b5cf6] group-hover:text-white transition-all duration-300">
|
||||||
{track.track_number}
|
{track.track_number}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-bold text-foreground group-hover:text-[#6d28d9] transition-colors truncate">
|
<h3 className="font-bold text-foreground group-hover:text-[#6d28d9] transition-colors duration-300 truncate">
|
||||||
{track.title}
|
{track.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground">{track.artist}</p>
|
<p className="text-sm text-muted-foreground">{track.artist}</p>
|
||||||
@@ -409,7 +409,7 @@ export default function DetailView({ media, onPersonClick }: DetailViewProps) {
|
|||||||
{track.duration}s
|
{track.duration}s
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Button size="icon" variant="ghost" className="text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">
|
<Button size="icon" variant="ghost" className="text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity hover:bg-[#6d28d9]/10 hover:text-[#6d28d9] rounded-xl">
|
||||||
<Play size={18} />
|
<Play size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -67,81 +67,91 @@ export default function Header({
|
|||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed top-0 left-0 right-0 z-50 flex items-center justify-between px-6 py-4 transition-all duration-300",
|
"fixed top-0 left-0 right-0 z-50 flex items-center justify-between px-6 py-4 transition-all duration-500",
|
||||||
transparent && !scrolled
|
transparent && !scrolled
|
||||||
? "bg-transparent"
|
? "bg-transparent"
|
||||||
: transparent && scrolled
|
: transparent && scrolled
|
||||||
? "backdrop-blur-md bg-background/80 border-b border-border/50"
|
? "backdrop-blur-xl bg-background/70 border-b border-border/30"
|
||||||
: "bg-[#6d28d9]"
|
: "backdrop-blur-xl bg-gradient-to-r from-[#6d28d9]/90 via-[#8b5cf6]/90 to-[#6d28d9]/90 border-b border-white/10"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-8">
|
<div className="flex items-center gap-8">
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-2xl font-black flex items-center gap-1",
|
"text-2xl font-black flex items-center gap-2 transition-all duration-300 hover:scale-105",
|
||||||
(transparent && !scrolled) || !transparent ? "text-white" : "text-foreground"
|
(transparent && !scrolled) || !transparent ? "text-white" : "text-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"w-6 h-6 rounded-full flex items-center justify-center",
|
"w-8 h-8 rounded-xl flex items-center justify-center shadow-lg transition-all duration-300",
|
||||||
(transparent && !scrolled) || !transparent ? "bg-white" : "bg-[#6d28d9]"
|
(transparent && !scrolled) || !transparent
|
||||||
|
? "bg-white/20 backdrop-blur-sm border border-white/30"
|
||||||
|
: "bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] shadow-[#6d28d9]/30"
|
||||||
)}>
|
)}>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"w-3 h-3 rounded-full",
|
"w-4 h-4 rounded-full",
|
||||||
(transparent && !scrolled) || !transparent ? "bg-[#6d28d9]" : "bg-white"
|
(transparent && !scrolled) || !transparent ? "bg-white" : "bg-white"
|
||||||
)} />
|
)} />
|
||||||
</div>
|
</div>
|
||||||
kyoo
|
<span className="bg-clip-text text-transparent bg-gradient-to-r from-white to-white/80">
|
||||||
|
kyoo
|
||||||
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"md:hidden p-2 transition-colors",
|
"md:hidden p-2 rounded-lg transition-all duration-300 hover:bg-white/10",
|
||||||
(transparent && !scrolled) || !transparent
|
(transparent && !scrolled) || !transparent
|
||||||
? "text-white/90 hover:text-white"
|
? "text-white/90 hover:text-white"
|
||||||
: "text-foreground hover:text-foreground"
|
: "text-foreground hover:text-foreground hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Menu size={20} />
|
<Menu size={20} />
|
||||||
</button>
|
</button>
|
||||||
<nav className="hidden md:flex items-center gap-6">
|
<nav className="hidden md:flex items-center gap-1">
|
||||||
{enabledCategories.map(cat => (
|
{enabledCategories.map(cat => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={cat}
|
key={cat}
|
||||||
to={`/${categoryPaths[cat]}`}
|
to={`/${categoryPaths[cat]}`}
|
||||||
className={({ isActive }) => cn(
|
className={({ isActive }) => cn(
|
||||||
"text-sm font-bold transition-colors uppercase tracking-wider",
|
"text-sm font-bold transition-all duration-300 uppercase tracking-wider px-4 py-2 rounded-lg relative",
|
||||||
(transparent && !scrolled) || !transparent
|
(transparent && !scrolled) || !transparent
|
||||||
? isActive ? "text-white" : "text-white/60 hover:text-white"
|
? isActive
|
||||||
: isActive ? "text-foreground" : "text-muted-foreground hover:text-foreground"
|
? "text-white bg-white/10"
|
||||||
|
: "text-white/70 hover:text-white hover:bg-white/5"
|
||||||
|
: isActive
|
||||||
|
? "text-foreground bg-[#6d28d9]/10"
|
||||||
|
: "text-muted-foreground hover:text-foreground hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{cat}
|
{cat}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"w-px h-4 mx-2",
|
"w-px h-6 mx-2",
|
||||||
(transparent && !scrolled) || !transparent ? "bg-white/20" : "bg-border"
|
(transparent && !scrolled) || !transparent ? "bg-white/20" : "bg-border"
|
||||||
)} />
|
)} />
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/cast"
|
to="/cast"
|
||||||
className={({ isActive }) => cn(
|
className={({ isActive }) => cn(
|
||||||
"text-sm font-bold transition-colors uppercase tracking-wider",
|
"text-sm font-bold transition-all duration-300 uppercase tracking-wider px-4 py-2 rounded-lg",
|
||||||
(transparent && !scrolled) || !transparent
|
(transparent && !scrolled) || !transparent
|
||||||
? isActive ? "text-white" : "text-white/60 hover:text-white"
|
? isActive ? "text-white bg-white/10" : "text-white/70 hover:text-white hover:bg-white/5"
|
||||||
: isActive ? "text-foreground" : "text-muted-foreground hover:text-foreground"
|
: isActive ? "text-foreground bg-[#6d28d9]/10" : "text-muted-foreground hover:text-foreground hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
CAST
|
CAST
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-2">
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"flex items-center transition-all duration-300 overflow-hidden",
|
"flex items-center transition-all duration-300 overflow-hidden rounded-2xl",
|
||||||
isSearchOpen ? "w-48 md:w-64 rounded-full px-3 py-1" : "w-0",
|
isSearchOpen ? "w-48 md:w-72 px-4 py-2.5" : "w-0",
|
||||||
(transparent && !scrolled) || !transparent ? "bg-white/10" : "bg-muted"
|
(transparent && !scrolled) || !transparent
|
||||||
|
? "bg-white/10 backdrop-blur-md border border-white/20"
|
||||||
|
: "bg-muted/50 backdrop-blur-md border border-border"
|
||||||
)}>
|
)}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -149,9 +159,9 @@ export default function Header({
|
|||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-transparent border-none outline-none text-sm w-full",
|
"bg-transparent border-none outline-none text-sm w-full placeholder:opacity-60",
|
||||||
(transparent && !scrolled) || !transparent
|
(transparent && !scrolled) || !transparent
|
||||||
? "text-white placeholder:text-white/50"
|
? "text-white placeholder:text-white"
|
||||||
: "text-foreground placeholder:text-muted-foreground"
|
: "text-foreground placeholder:text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
autoFocus={isSearchOpen}
|
autoFocus={isSearchOpen}
|
||||||
@@ -160,50 +170,52 @@ export default function Header({
|
|||||||
<button
|
<button
|
||||||
onClick={toggleSearch}
|
onClick={toggleSearch}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-2 transition-colors",
|
"p-2.5 rounded-xl transition-all duration-300 hover:scale-110",
|
||||||
(transparent && !scrolled) || !transparent
|
(transparent && !scrolled) || !transparent
|
||||||
? "text-white/90 hover:text-white"
|
? "text-white/90 hover:text-white hover:bg-white/10"
|
||||||
: "text-foreground hover:text-foreground"
|
: "text-foreground hover:text-foreground hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isSearchOpen ? <X size={20} /> : <Search size={20} />}
|
{isSearchOpen ? <X size={18} /> : <Search size={18} />}
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
to="/add"
|
to="/add"
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-2 transition-colors",
|
"p-2.5 rounded-xl transition-all duration-300 hover:scale-110",
|
||||||
(transparent && !scrolled) || !transparent
|
(transparent && !scrolled) || !transparent
|
||||||
? "text-white/90 hover:text-white"
|
? "text-white/90 hover:text-white hover:bg-white/10"
|
||||||
: "text-foreground hover:text-foreground"
|
: "text-foreground hover:text-foreground hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Plus size={20} />
|
<Plus size={18} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/import"
|
to="/import"
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-2 transition-colors",
|
"p-2.5 rounded-xl transition-all duration-300 hover:scale-110",
|
||||||
(transparent && !scrolled) || !transparent
|
(transparent && !scrolled) || !transparent
|
||||||
? "text-white/90 hover:text-white"
|
? "text-white/90 hover:text-white hover:bg-white/10"
|
||||||
: "text-foreground hover:text-foreground"
|
: "text-foreground hover:text-foreground hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Download size={20} />
|
<Download size={18} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/settings"
|
to="/settings"
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-2 transition-colors",
|
"p-2.5 rounded-xl transition-all duration-300 hover:scale-110",
|
||||||
(transparent && !scrolled) || !transparent
|
(transparent && !scrolled) || !transparent
|
||||||
? "text-white/90 hover:text-white"
|
? "text-white/90 hover:text-white hover:bg-white/10"
|
||||||
: "text-foreground hover:text-foreground"
|
: "text-foreground hover:text-foreground hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Settings size={20} />
|
<Settings size={18} />
|
||||||
</Link>
|
</Link>
|
||||||
<button className={cn(
|
<button className={cn(
|
||||||
"w-8 h-8 rounded-full overflow-hidden border-2",
|
"w-9 h-9 rounded-xl overflow-hidden border-2 transition-all duration-300 hover:scale-110 hover:shadow-lg",
|
||||||
(transparent && !scrolled) || !transparent ? "border-white/20" : "border-border"
|
(transparent && !scrolled) || !transparent
|
||||||
|
? "border-white/30 hover:border-white/50"
|
||||||
|
: "border-border hover:border-[#6d28d9]/50"
|
||||||
)}>
|
)}>
|
||||||
<img
|
<img
|
||||||
src="https://picsum.photos/seed/user/100/100"
|
src="https://picsum.photos/seed/user/100/100"
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ export default function ImporterView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-24 pb-12 px-6 max-w-[1600px] mx-auto">
|
<div className="pt-24 pb-12 px-6 max-w-[1920px] mx-auto">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-8">
|
<div className="flex items-center justify-between mb-8">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
@@ -349,12 +349,12 @@ export default function ImporterView() {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
className="text-muted-foreground hover:text-[#6d28d9]"
|
className="text-muted-foreground hover:text-[#6d28d9] hover:bg-muted/50 rounded-xl transition-all duration-300"
|
||||||
>
|
>
|
||||||
<ArrowLeft size={20} />
|
<ArrowLeft size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-black text-foreground">Media Importers</h1>
|
<h1 className="text-4xl font-black text-foreground mb-1">Media Importers</h1>
|
||||||
<p className="text-sm text-muted-foreground font-medium">Import media from external platforms</p>
|
<p className="text-sm text-muted-foreground font-medium">Import media from external platforms</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -364,7 +364,7 @@ export default function ImporterView() {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
||||||
{/* XBVR Importer Card */}
|
{/* XBVR Importer Card */}
|
||||||
{xbvrConfig.url && (
|
{xbvrConfig.url && (
|
||||||
<div className="bg-card border border-border rounded-xl p-6 hover:border-[#6d28d9]/50 transition-colors">
|
<div className="bg-card/50 backdrop-blur-sm border border-border/50 rounded-xl p-6 hover:border-[#6d28d9]/50 hover:shadow-lg hover:shadow-[#6d28d9]/10 transition-all duration-300">
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center">
|
<div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||||
@@ -433,7 +433,7 @@ export default function ImporterView() {
|
|||||||
|
|
||||||
{/* StashAPP Importer Card */}
|
{/* StashAPP Importer Card */}
|
||||||
{stashappConfig.url && (
|
{stashappConfig.url && (
|
||||||
<div className="bg-card border border-border rounded-xl p-6 hover:border-[#6d28d9]/50 transition-colors">
|
<div className="bg-card/50 backdrop-blur-sm border border-border/50 rounded-xl p-6 hover:border-[#6d28d9]/50 hover:shadow-lg hover:shadow-[#6d28d9]/10 transition-all duration-300">
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||||
@@ -513,7 +513,7 @@ export default function ImporterView() {
|
|||||||
|
|
||||||
{/* StashAPP Actor Updater Card */}
|
{/* StashAPP Actor Updater Card */}
|
||||||
{stashappConfig.url && (
|
{stashappConfig.url && (
|
||||||
<div className="bg-card border border-border rounded-xl p-6 hover:border-[#6d28d9]/50 transition-colors">
|
<div className="bg-card/50 backdrop-blur-sm border border-border/50 rounded-xl p-6 hover:border-[#6d28d9]/50 hover:shadow-lg hover:shadow-[#6d28d9]/10 transition-all duration-300">
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
|
<div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
|
||||||
@@ -571,7 +571,7 @@ export default function ImporterView() {
|
|||||||
|
|
||||||
{/* Playnite Importer Card */}
|
{/* Playnite Importer Card */}
|
||||||
{playniteConfig.ip && playniteConfig.apiToken && (
|
{playniteConfig.ip && playniteConfig.apiToken && (
|
||||||
<div className="bg-card border border-border rounded-xl p-6 hover:border-[#6d28d9]/50 transition-colors">
|
<div className="bg-card/50 backdrop-blur-sm border border-border/50 rounded-xl p-6 hover:border-[#6d28d9]/50 hover:shadow-lg hover:shadow-[#6d28d9]/10 transition-all duration-300">
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-12 h-12 bg-orange-100 rounded-lg flex items-center justify-center">
|
<div className="w-12 h-12 bg-orange-100 rounded-lg flex items-center justify-center">
|
||||||
@@ -662,7 +662,7 @@ export default function ImporterView() {
|
|||||||
|
|
||||||
{/* Jellyfin Importer Card */}
|
{/* Jellyfin Importer Card */}
|
||||||
{jellyfinConfig.url && (
|
{jellyfinConfig.url && (
|
||||||
<div className="bg-card border border-border rounded-xl p-6 hover:border-[#6d28d9]/50 transition-colors">
|
<div className="bg-card/50 backdrop-blur-sm border border-border/50 rounded-xl p-6 hover:border-[#6d28d9]/50 hover:shadow-lg hover:shadow-[#6d28d9]/10 transition-all duration-300">
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center">
|
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center">
|
||||||
@@ -844,7 +844,7 @@ export default function ImporterView() {
|
|||||||
|
|
||||||
{/* Jellyfin Cleanup Card */}
|
{/* Jellyfin Cleanup Card */}
|
||||||
{jellyfinConfig.url && (
|
{jellyfinConfig.url && (
|
||||||
<div className="bg-card border border-border rounded-xl p-6 hover:border-[#6d28d9]/50 transition-colors">
|
<div className="bg-card/50 backdrop-blur-sm border border-border/50 rounded-xl p-6 hover:border-[#6d28d9]/50 hover:shadow-lg hover:shadow-[#6d28d9]/10 transition-all duration-300">
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center">
|
<div className="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center">
|
||||||
@@ -937,20 +937,20 @@ export default function ImporterView() {
|
|||||||
|
|
||||||
{/* Progress Section */}
|
{/* Progress Section */}
|
||||||
{progress.stage !== 'idle' && (
|
{progress.stage !== 'idle' && (
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-card/50 backdrop-blur-sm border border-border/50 rounded-xl p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{progress.stage === 'complete' ? (
|
{progress.stage === 'complete' ? (
|
||||||
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
|
<div className="w-10 h-10 bg-green-500/10 rounded-full flex items-center justify-center border border-green-500/30">
|
||||||
<CheckCircle className="text-green-600" size={20} />
|
<CheckCircle className="text-green-500" size={20} />
|
||||||
</div>
|
</div>
|
||||||
) : progress.stage === 'error' ? (
|
) : progress.stage === 'error' ? (
|
||||||
<div className="w-10 h-10 bg-red-100 rounded-full flex items-center justify-center">
|
<div className="w-10 h-10 bg-red-500/10 rounded-full flex items-center justify-center border border-red-500/30">
|
||||||
<XCircle className="text-red-600" size={20} />
|
<XCircle className="text-red-500" size={20} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center">
|
<div className="w-10 h-10 bg-purple-500/10 rounded-full flex items-center justify-center border border-purple-500/30">
|
||||||
<Loader2 className="text-muted-foreground animate-spin" size={20} />
|
<Loader2 className="text-purple-500 animate-spin" size={20} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
@@ -968,7 +968,7 @@ export default function ImporterView() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={resetImport}
|
onClick={resetImport}
|
||||||
className="gap-2 font-bold border-border"
|
className="gap-2 font-bold border-border/50 hover:border-[#6d28d9]/50 transition-all duration-300"
|
||||||
>
|
>
|
||||||
<RefreshCw size={16} />
|
<RefreshCw size={16} />
|
||||||
Reset
|
Reset
|
||||||
@@ -983,7 +983,7 @@ export default function ImporterView() {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-full transition-all duration-300 ease-out",
|
"h-full transition-all duration-300 ease-out",
|
||||||
progress.stage === 'error' ? "bg-red-500" : "bg-[#6d28d9]"
|
progress.stage === 'error' ? "bg-gradient-to-r from-red-500 to-red-600" : "bg-gradient-to-r from-[#6d28d9] to-[#8b5cf6]"
|
||||||
)}
|
)}
|
||||||
style={{ width: `${getProgressPercentage()}%` }}
|
style={{ width: `${getProgressPercentage()}%` }}
|
||||||
/>
|
/>
|
||||||
@@ -997,9 +997,9 @@ export default function ImporterView() {
|
|||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="grid grid-cols-3 gap-4 mb-6">
|
<div className="grid grid-cols-3 gap-4 mb-6">
|
||||||
<div className="bg-muted rounded-lg p-4">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-xl p-4 border border-border/50">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Film size={16} className="text-muted-foreground" />
|
<Film size={16} className="text-[#6d28d9]" />
|
||||||
<span className="text-xs font-bold text-muted-foreground">
|
<span className="text-xs font-bold text-muted-foreground">
|
||||||
{(progress as any).gamesImported !== undefined ? 'Games' :
|
{(progress as any).gamesImported !== undefined ? 'Games' :
|
||||||
(progress as any).moviesImported !== undefined ? 'Movies' :
|
(progress as any).moviesImported !== undefined ? 'Movies' :
|
||||||
@@ -1014,16 +1014,16 @@ export default function ImporterView() {
|
|||||||
(progress as any).musicImported !== undefined ? (progress as any).musicImported : progress.videosImported}
|
(progress as any).musicImported !== undefined ? (progress as any).musicImported : progress.videosImported}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-muted rounded-lg p-4">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-xl p-4 border border-border/50">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Users size={16} className="text-muted-foreground" />
|
<Users size={16} className="text-[#6d28d9]" />
|
||||||
<span className="text-xs font-bold text-muted-foreground">{(progress as any).castImported !== undefined ? 'Cast' : 'Actors'}</span>
|
<span className="text-xs font-bold text-muted-foreground">{(progress as any).castImported !== undefined ? 'Cast' : 'Actors'}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-black text-foreground">{(progress as any).castImported !== undefined ? (progress as any).castImported : progress.actorsImported}</p>
|
<p className="text-2xl font-black text-foreground">{(progress as any).castImported !== undefined ? (progress as any).castImported : progress.actorsImported}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-muted rounded-lg p-4">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-xl p-4 border border-border/50">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<AlertCircle size={16} className="text-muted-foreground" />
|
<AlertCircle size={16} className="text-red-500" />
|
||||||
<span className="text-xs font-bold text-muted-foreground">Errors</span>
|
<span className="text-xs font-bold text-muted-foreground">Errors</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-black text-foreground">{progress.errors.length}</p>
|
<p className="text-2xl font-black text-foreground">{progress.errors.length}</p>
|
||||||
@@ -1034,7 +1034,7 @@ export default function ImporterView() {
|
|||||||
{importLog.length > 0 && (
|
{importLog.length > 0 && (
|
||||||
<div
|
<div
|
||||||
ref={logContainerRef}
|
ref={logContainerRef}
|
||||||
className="bg-zinc-900 rounded-lg p-4 max-h-64 overflow-y-auto"
|
className="bg-zinc-900/90 backdrop-blur-sm rounded-xl p-4 max-h-64 overflow-y-auto border border-border/50"
|
||||||
>
|
>
|
||||||
<pre className="text-xs text-green-400 font-mono whitespace-pre-wrap">
|
<pre className="text-xs text-green-400 font-mono whitespace-pre-wrap">
|
||||||
{importLog.join('\n')}
|
{importLog.join('\n')}
|
||||||
@@ -1045,10 +1045,10 @@ export default function ImporterView() {
|
|||||||
{/* Errors */}
|
{/* Errors */}
|
||||||
{progress.errors.length > 0 && (
|
{progress.errors.length > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h4 className="text-sm font-bold text-red-600 mb-2">Errors</h4>
|
<h4 className="text-sm font-bold text-red-500 mb-2">Errors</h4>
|
||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-3 max-h-32 overflow-y-auto">
|
<div className="bg-red-500/10 border border-red-500/30 rounded-xl p-3 max-h-32 overflow-y-auto backdrop-blur-sm">
|
||||||
{progress.errors.map((error, index) => (
|
{progress.errors.map((error, index) => (
|
||||||
<p key={index} className="text-xs text-red-700 font-medium mb-1">
|
<p key={index} className="text-xs text-red-500 font-medium mb-1">
|
||||||
• {error}
|
• {error}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface LibrarySettingsProps {
|
|||||||
const CATEGORY_ICONS: Record<MediaCategory, React.ReactNode> = {
|
const CATEGORY_ICONS: Record<MediaCategory, React.ReactNode> = {
|
||||||
Anime: <Tv size={18} />,
|
Anime: <Tv size={18} />,
|
||||||
Movies: <Film size={18} />,
|
Movies: <Film size={18} />,
|
||||||
|
'TV Series': <Tv size={18} />,
|
||||||
Music: <Music size={18} />,
|
Music: <Music size={18} />,
|
||||||
Books: <Book size={18} />,
|
Books: <Book size={18} />,
|
||||||
Consoles: <Gamepad2 size={18} />,
|
Consoles: <Gamepad2 size={18} />,
|
||||||
@@ -34,29 +35,29 @@ export default function LibrarySettings({ enabledCategories, onToggleCategory }:
|
|||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<button type="button" className="group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 size-8 text-white/90 hover:text-white transition-colors">
|
<button type="button" className="group/button inline-flex shrink-0 items-center justify-center rounded-xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 hover:bg-muted/50 hover:text-foreground aria-expanded:bg-muted/50 aria-expanded:text-foreground dark:hover:bg-muted/50 size-8 text-white/90 hover:text-white transition-all duration-300 hover:scale-110">
|
||||||
<Settings size={20} />
|
<Settings size={20} />
|
||||||
</button>
|
</button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[425px] bg-white rounded-3xl">
|
<DialogContent className="sm:max-w-[425px] bg-card/50 backdrop-blur-sm rounded-3xl border border-border/50">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-2xl font-black text-zinc-900">Library Settings</DialogTitle>
|
<DialogTitle className="text-2xl font-black text-foreground">Library Settings</DialogTitle>
|
||||||
<DialogDescription className="text-zinc-500 font-medium">
|
<DialogDescription className="text-muted-foreground font-medium">
|
||||||
Toggle which media areas you want to see in your library.
|
Toggle which media areas you want to see in your library.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-6 py-6">
|
<div className="grid gap-6 py-6">
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<div key={category} className="flex items-center justify-between p-4 rounded-2xl bg-zinc-50 border border-zinc-100 transition-all hover:border-[#6d28d9]/20">
|
<div key={category} className="flex items-center justify-between p-4 rounded-2xl bg-muted/30 border border-border/50 transition-all hover:border-[#6d28d9]/30 hover:bg-muted/50">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-10 h-10 rounded-xl bg-white flex items-center justify-center text-[#6d28d9] shadow-sm">
|
<div className="w-10 h-10 rounded-xl bg-background flex items-center justify-center text-[#6d28d9] shadow-sm border border-border/30">
|
||||||
{CATEGORY_ICONS[category]}
|
{CATEGORY_ICONS[category]}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor={category} className="text-sm font-black text-zinc-900 cursor-pointer">
|
<Label htmlFor={category} className="text-sm font-black text-foreground cursor-pointer">
|
||||||
{category}
|
{category}
|
||||||
</Label>
|
</Label>
|
||||||
<p className="text-[10px] font-bold text-zinc-400 uppercase tracking-widest">
|
<p className="text-[10px] font-bold text-muted-foreground uppercase tracking-widest">
|
||||||
{enabledCategories.includes(category) ? 'Enabled' : 'Disabled'}
|
{enabledCategories.includes(category) ? 'Enabled' : 'Disabled'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Media } from '@/types';
|
import { Media } from '@/types';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
|
import { Star } from 'lucide-react';
|
||||||
|
|
||||||
interface MediaCardProps {
|
interface MediaCardProps {
|
||||||
key?: string;
|
key?: string;
|
||||||
@@ -48,34 +49,58 @@ export default function MediaCard({ media, onClick }: MediaCardProps) {
|
|||||||
layoutId={`media-${media.id}`}
|
layoutId={`media-${media.id}`}
|
||||||
className="group cursor-pointer"
|
className="group cursor-pointer"
|
||||||
onClick={() => onClick(media)}
|
onClick={() => onClick(media)}
|
||||||
whileHover={{ y: -4 }}
|
whileHover={{ y: -8, scale: 1.02 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||||
>
|
>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"relative rounded-lg overflow-hidden shadow-lg bg-card transition-all duration-300",
|
"relative rounded-2xl overflow-hidden bg-card transition-all duration-500 shadow-lg group-hover:shadow-2xl group-hover:shadow-[#6d28d9]/20",
|
||||||
aspectRatioClass
|
aspectRatioClass
|
||||||
)}>
|
)}>
|
||||||
<img
|
<img
|
||||||
src={media.poster}
|
src={media.poster}
|
||||||
alt={media.title}
|
alt={media.title}
|
||||||
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Gradient Overlay */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||||
|
|
||||||
|
{/* Rating Badge */}
|
||||||
|
{media.rating && (
|
||||||
|
<div className="absolute top-3 right-3 bg-black/70 backdrop-blur-md px-2.5 py-1 rounded-full flex items-center gap-1.5 opacity-0 group-hover:opacity-100 transition-all duration-500 transform translate-y-[-10px] group-hover:translate-y-0">
|
||||||
|
<Star size={12} className="text-yellow-400 fill-yellow-400" />
|
||||||
|
<span className="text-xs font-bold text-white">{media.rating}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{media.status && (
|
{media.status && (
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"absolute top-2 left-2 w-3 h-3 rounded-full border border-white/20 shadow-sm",
|
"absolute top-3 left-3 w-3.5 h-3.5 rounded-full border-2 border-white/30 shadow-lg z-10",
|
||||||
statusColors[media.status]
|
statusColors[media.status]
|
||||||
)} />
|
)} />
|
||||||
)}
|
)}
|
||||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors duration-300" />
|
|
||||||
|
{/* Glow Effect on Hover */}
|
||||||
|
<div className="absolute inset-0 rounded-2xl ring-2 ring-[#6d28d9]/0 group-hover:ring-[#6d28d9]/50 transition-all duration-500 pointer-events-none" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 space-y-1">
|
<div className="mt-4 space-y-1.5">
|
||||||
<h3 className="text-sm font-bold text-foreground line-clamp-1 group-hover:text-[#6d28d9] transition-colors">
|
<h3 className="text-sm font-bold text-foreground line-clamp-2 group-hover:text-[#6d28d9] transition-colors duration-300">
|
||||||
{media.title}
|
{media.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs font-medium text-muted-foreground">
|
<div className="flex items-center gap-2">
|
||||||
{media.year}
|
<p className="text-xs font-medium text-muted-foreground">
|
||||||
</p>
|
{media.year}
|
||||||
|
</p>
|
||||||
|
{media.genres && media.genres.length > 0 && (
|
||||||
|
<>
|
||||||
|
<span className="text-xs text-muted-foreground/50">•</span>
|
||||||
|
<p className="text-xs font-medium text-muted-foreground/70 line-clamp-1">
|
||||||
|
{media.genres[0]}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ export default function MediaListItem({ media, onClick }: MediaListItemProps) {
|
|||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: -10 }}
|
exit={{ opacity: 0, y: -10 }}
|
||||||
className="group flex items-center gap-6 p-4 rounded-xl hover:bg-muted/50 transition-colors cursor-pointer border border-transparent hover:border-border"
|
className="group flex items-center gap-6 p-5 rounded-xl hover:bg-muted/50 transition-all duration-300 cursor-pointer border border-border/50 hover:border-[#6d28d9]/30 hover:shadow-lg hover:shadow-[#6d28d9]/10"
|
||||||
onClick={() => onClick(media)}
|
onClick={() => onClick(media)}
|
||||||
>
|
>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"relative rounded-lg overflow-hidden shrink-0 shadow-md bg-card transition-all duration-300",
|
"relative rounded-xl overflow-hidden shrink-0 shadow-md bg-card transition-all duration-300 group-hover:scale-105 border border-border/30",
|
||||||
aspectRatioClass
|
aspectRatioClass
|
||||||
)}>
|
)}>
|
||||||
<img
|
<img
|
||||||
@@ -57,6 +57,7 @@ export default function MediaListItem({ media, onClick }: MediaListItemProps) {
|
|||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
|
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors duration-300" />
|
||||||
{media.status && (
|
{media.status && (
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"absolute top-2 left-2 w-3 h-3 rounded-full border border-white/20 shadow-sm",
|
"absolute top-2 left-2 w-3 h-3 rounded-full border border-white/20 shadow-sm",
|
||||||
@@ -67,7 +68,7 @@ export default function MediaListItem({ media, onClick }: MediaListItemProps) {
|
|||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-3 mb-1">
|
<div className="flex items-center gap-3 mb-1">
|
||||||
<h3 className="text-lg font-black text-foreground truncate group-hover:text-[#6d28d9] transition-colors">
|
<h3 className="text-lg font-black text-foreground truncate group-hover:text-[#6d28d9] transition-colors duration-300">
|
||||||
{media.title}
|
{media.title}
|
||||||
</h3>
|
</h3>
|
||||||
<span className="text-sm font-bold text-muted-foreground">({media.year})</span>
|
<span className="text-sm font-bold text-muted-foreground">({media.year})</span>
|
||||||
@@ -89,10 +90,10 @@ export default function MediaListItem({ media, onClick }: MediaListItemProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden md:flex items-center gap-2">
|
<div className="hidden md:flex items-center gap-2">
|
||||||
<Button size="icon" variant="ghost" className="rounded-full text-muted-foreground hover:text-[#6d28d9] hover:bg-[#6d28d9]/10">
|
<Button size="icon" variant="ghost" className="rounded-xl text-muted-foreground hover:text-[#6d28d9] hover:bg-[#6d28d9]/10 transition-all duration-300">
|
||||||
<Play size={18} fill="currentColor" />
|
<Play size={18} fill="currentColor" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="icon" variant="ghost" className="rounded-full text-muted-foreground hover:text-[#6d28d9] hover:bg-[#6d28d9]/10">
|
<Button size="icon" variant="ghost" className="rounded-xl text-muted-foreground hover:text-[#6d28d9] hover:bg-[#6d28d9]/10 transition-all duration-300">
|
||||||
<Bookmark size={18} />
|
<Bookmark size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -107,22 +107,22 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background pt-20">
|
<div className="min-h-screen bg-background pt-20">
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="max-w-[1600px] mx-auto px-6 py-12">
|
<div className="max-w-[1920px] mx-auto px-6 py-12">
|
||||||
<div className="flex items-center justify-between mb-8">
|
<div className="flex items-center justify-between mb-8">
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className="inline-flex items-center gap-2 text-sm font-bold text-muted-foreground hover:text-[#6d28d9] transition-colors mb-2"
|
className="inline-flex items-center gap-2 text-sm font-bold text-muted-foreground hover:text-[#6d28d9] transition-colors mb-2 hover:bg-muted/50 px-3 py-1 rounded-xl transition-all duration-300"
|
||||||
>
|
>
|
||||||
<ArrowLeft size={16} />
|
<ArrowLeft size={16} />
|
||||||
Back to home
|
Back to home
|
||||||
</Link>
|
</Link>
|
||||||
<h1 className="text-3xl font-black text-foreground">Settings</h1>
|
<h1 className="text-4xl font-black text-foreground">Settings</h1>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="bg-[#6d28d9] text-white hover:bg-[#5b21b6] font-bold px-6 py-3 h-12 rounded-lg flex items-center gap-2 transition-colors disabled:opacity-50"
|
className="bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] hover:from-[#5b21b6] hover:to-[#7c3aed] text-white font-bold px-6 py-3 h-12 rounded-xl flex items-center gap-2 transition-all duration-300 hover:scale-[1.02] shadow-lg shadow-[#6d28d9]/30 disabled:opacity-50 disabled:hover:scale-100"
|
||||||
>
|
>
|
||||||
{isSaving ? (
|
{isSaving ? (
|
||||||
'Saving...'
|
'Saving...'
|
||||||
@@ -136,12 +136,12 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{saveStatus === 'success' && (
|
{saveStatus === 'success' && (
|
||||||
<div className="mb-6 p-4 bg-green-50 border border-green-200 rounded-xl text-green-700 font-medium">
|
<div className="mb-6 p-4 bg-green-500/10 border border-green-500/30 rounded-xl text-green-500 font-medium backdrop-blur-sm">
|
||||||
Settings saved successfully!
|
Settings saved successfully!
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{saveStatus === 'error' && (
|
{saveStatus === 'error' && (
|
||||||
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 font-medium">
|
<div className="mb-6 p-4 bg-red-500/10 border border-red-500/30 rounded-xl text-red-500 font-medium backdrop-blur-sm">
|
||||||
Failed to save settings. Please try again.
|
Failed to save settings. Please try again.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -149,16 +149,16 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
<div className="grid gap-8">
|
<div className="grid gap-8">
|
||||||
{/* Library Settings */}
|
{/* Library Settings */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-xl font-black text-foreground mb-6">Library Settings</h2>
|
<h2 className="text-2xl font-black text-foreground mb-6">Library Settings</h2>
|
||||||
<div className="bg-muted/50 rounded-2xl p-6 border border-border">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50">
|
||||||
<p className="text-sm font-medium text-muted-foreground mb-4">
|
<p className="text-sm font-medium text-muted-foreground mb-4">
|
||||||
Toggle which media areas you want to see in your library.
|
Toggle which media areas you want to see in your library.
|
||||||
</p>
|
</p>
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
{(['Anime', 'Movies', 'Music', 'Books', 'Consoles', 'Games', 'Adult'] as MediaCategory[]).map((category) => (
|
{(['Anime', 'Movies', 'Music', 'Books', 'Consoles', 'Games', 'Adult'] as MediaCategory[]).map((category) => (
|
||||||
<div key={category} className="flex items-center justify-between p-4 rounded-xl bg-background border border-border transition-all hover:border-[#6d28d9]/20">
|
<div key={category} className="flex items-center justify-between p-4 rounded-xl bg-background border border-border/50 transition-all hover:border-[#6d28d9]/30 hover:bg-muted/50">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-10 h-10 rounded-lg bg-muted flex items-center justify-center text-[#6d28d9]">
|
<div className="w-10 h-10 rounded-xl bg-muted flex items-center justify-center text-[#6d28d9] border border-border/30">
|
||||||
{CATEGORY_ICONS[category]}
|
{CATEGORY_ICONS[category]}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -183,8 +183,8 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
|
|
||||||
{/* Display Settings */}
|
{/* Display Settings */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-xl font-black text-foreground mb-6">Display Settings</h2>
|
<h2 className="text-2xl font-black text-foreground mb-6">Display Settings</h2>
|
||||||
<div className="bg-muted/50 rounded-2xl p-6 border border-border space-y-6">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50 space-y-6">
|
||||||
{/* Items per page */}
|
{/* Items per page */}
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-sm font-black text-foreground mb-2 block">Items per page</Label>
|
<Label className="text-sm font-black text-foreground mb-2 block">Items per page</Label>
|
||||||
@@ -193,10 +193,10 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
<button
|
<button
|
||||||
key={option}
|
key={option}
|
||||||
onClick={() => setSettings(prev => ({ ...prev, itemsPerPage: option }))}
|
onClick={() => setSettings(prev => ({ ...prev, itemsPerPage: option }))}
|
||||||
className={`px-4 py-2 rounded-lg text-sm font-bold transition-all ${
|
className={`px-4 py-2 rounded-xl text-sm font-bold transition-all ${
|
||||||
settings.itemsPerPage === option
|
settings.itemsPerPage === option
|
||||||
? 'bg-[#6d28d9] text-white'
|
? 'bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] text-white shadow-lg shadow-[#6d28d9]/20'
|
||||||
: 'bg-background text-foreground hover:bg-muted border border-border'
|
: 'bg-background text-foreground hover:bg-muted border border-border/50 hover:border-[#6d28d9]/30'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{option}
|
{option}
|
||||||
@@ -211,10 +211,10 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSettings(prev => ({ ...prev, defaultView: 'grid' }))}
|
onClick={() => setSettings(prev => ({ ...prev, defaultView: 'grid' }))}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-lg text-sm font-bold transition-all ${
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-sm font-bold transition-all ${
|
||||||
settings.defaultView === 'grid'
|
settings.defaultView === 'grid'
|
||||||
? 'bg-[#6d28d9] text-white'
|
? 'bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] text-white shadow-lg shadow-[#6d28d9]/20'
|
||||||
: 'bg-background text-foreground hover:bg-muted border border-border'
|
: 'bg-background text-foreground hover:bg-muted border border-border/50 hover:border-[#6d28d9]/30'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<LayoutGrid size={18} />
|
<LayoutGrid size={18} />
|
||||||
@@ -222,10 +222,10 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setSettings(prev => ({ ...prev, defaultView: 'list' }))}
|
onClick={() => setSettings(prev => ({ ...prev, defaultView: 'list' }))}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-lg text-sm font-bold transition-all ${
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-sm font-bold transition-all ${
|
||||||
settings.defaultView === 'list'
|
settings.defaultView === 'list'
|
||||||
? 'bg-[#6d28d9] text-white'
|
? 'bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] text-white shadow-lg shadow-[#6d28d9]/20'
|
||||||
: 'bg-background text-foreground hover:bg-muted border border-border'
|
: 'bg-background text-foreground hover:bg-muted border border-border/50 hover:border-[#6d28d9]/30'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<List size={18} />
|
<List size={18} />
|
||||||
@@ -260,10 +260,10 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
<button
|
<button
|
||||||
key={theme}
|
key={theme}
|
||||||
onClick={() => setSettings(prev => ({ ...prev, theme }))}
|
onClick={() => setSettings(prev => ({ ...prev, theme }))}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-lg text-sm font-bold transition-all ${
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-sm font-bold transition-all ${
|
||||||
settings.theme === theme
|
settings.theme === theme
|
||||||
? 'bg-[#6d28d9] text-white'
|
? 'bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] text-white shadow-lg shadow-[#6d28d9]/20'
|
||||||
: 'bg-background text-foreground hover:bg-muted border border-border'
|
: 'bg-background text-foreground hover:bg-muted border border-border/50 hover:border-[#6d28d9]/30'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{theme === 'light' && <Sun size={18} />}
|
{theme === 'light' && <Sun size={18} />}
|
||||||
@@ -279,10 +279,10 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
|
|
||||||
{/* Content Settings */}
|
{/* Content Settings */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-xl font-black text-foreground mb-6">Content Settings</h2>
|
<h2 className="text-2xl font-black text-foreground mb-6">Content Settings</h2>
|
||||||
<div className="bg-muted/50 rounded-2xl p-6 border border-border space-y-4">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50 space-y-4">
|
||||||
{/* Show adult content */}
|
{/* Show adult content */}
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl bg-background border border-border">
|
<div className="flex items-center justify-between p-4 rounded-xl bg-background border border-border/50 hover:border-[#6d28d9]/30 transition-all">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="showAdult" className="text-sm font-black text-foreground cursor-pointer">
|
<Label htmlFor="showAdult" className="text-sm font-black text-foreground cursor-pointer">
|
||||||
Show adult content
|
Show adult content
|
||||||
@@ -299,7 +299,7 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Auto-play trailers */}
|
{/* Auto-play trailers */}
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl bg-background border border-border">
|
<div className="flex items-center justify-between p-4 rounded-xl bg-background border border-border/50 hover:border-[#6d28d9]/30 transition-all">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="autoPlay" className="text-sm font-black text-foreground cursor-pointer">
|
<Label htmlFor="autoPlay" className="text-sm font-black text-foreground cursor-pointer">
|
||||||
Auto-play trailers
|
Auto-play trailers
|
||||||
@@ -319,8 +319,8 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
|
|
||||||
{/* Language Settings */}
|
{/* Language Settings */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-xl font-black text-foreground mb-6">Language</h2>
|
<h2 className="text-2xl font-black text-foreground mb-6">Language</h2>
|
||||||
<div className="bg-muted/50 rounded-2xl p-6 border border-border">
|
<div className="bg-muted/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Globe size={18} className="text-[#6d28d9]" />
|
<Globe size={18} className="text-[#6d28d9]" />
|
||||||
<Label className="text-sm font-black text-foreground">Interface language</Label>
|
<Label className="text-sm font-black text-foreground">Interface language</Label>
|
||||||
@@ -330,10 +330,10 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
|||||||
<button
|
<button
|
||||||
key={option.value}
|
key={option.value}
|
||||||
onClick={() => setSettings(prev => ({ ...prev, language: option.value }))}
|
onClick={() => setSettings(prev => ({ ...prev, language: option.value }))}
|
||||||
className={`px-4 py-2 rounded-lg text-sm font-bold transition-all ${
|
className={`px-4 py-2 rounded-xl text-sm font-bold transition-all ${
|
||||||
settings.language === option.value
|
settings.language === option.value
|
||||||
? 'bg-[#6d28d9] text-white'
|
? 'bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] text-white shadow-lg shadow-[#6d28d9]/20'
|
||||||
: 'bg-background text-foreground hover:bg-muted border border-border'
|
: 'bg-background text-foreground hover:bg-muted border border-border/50 hover:border-[#6d28d9]/30'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
--chart-3: oklch(0.439 0 0);
|
--chart-3: oklch(0.439 0 0);
|
||||||
--chart-4: oklch(0.371 0 0);
|
--chart-4: oklch(0.371 0 0);
|
||||||
--chart-5: oklch(0.269 0 0);
|
--chart-5: oklch(0.269 0 0);
|
||||||
--radius: 0.625rem;
|
--radius: 0.75rem;
|
||||||
--sidebar: oklch(0.985 0 0);
|
--sidebar: oklch(0.985 0 0);
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
@@ -92,40 +92,60 @@
|
|||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
|
||||||
|
/* Custom gradient colors */
|
||||||
|
--gradient-purple: linear-gradient(135deg, #6d28d9 0%, #8b5cf6 50%, #a78bfa 100%);
|
||||||
|
--gradient-blue: linear-gradient(135deg, #3b82f6 0%, #60a5fa 50%, #93c5fd 100%);
|
||||||
|
--gradient-green: linear-gradient(135deg, #22c55e 0%, #4ade80 50%, #86efac 100%);
|
||||||
|
--gradient-yellow: linear-gradient(135deg, #eab308 0%, #facc15 50%, #fde047 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.145 0 0);
|
--background: oklch(0.12 0.01 264);
|
||||||
--foreground: oklch(0.985 0 0);
|
--foreground: oklch(0.985 0 0);
|
||||||
--card: oklch(0.205 0 0);
|
--card: oklch(0.18 0.02 264);
|
||||||
--card-foreground: oklch(0.985 0 0);
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--popover: oklch(0.205 0 0);
|
--popover: oklch(0.18 0.02 264);
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--primary: oklch(0.922 0 0);
|
--primary: oklch(0.922 0 0);
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
--secondary: oklch(0.269 0 0);
|
--secondary: oklch(0.269 0.01 264);
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--muted: oklch(0.269 0 0);
|
--muted: oklch(0.25 0.01 264);
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
--accent: oklch(0.269 0 0);
|
--accent: oklch(0.269 0.01 264);
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
--border: oklch(1 0 0 / 10%);
|
--border: oklch(0.985 0 0 / 15%);
|
||||||
--input: oklch(1 0 0 / 15%);
|
--input: oklch(0.985 0 0 / 20%);
|
||||||
--ring: oklch(0.556 0 0);
|
--ring: oklch(0.556 0 0);
|
||||||
--chart-1: oklch(0.87 0 0);
|
--chart-1: oklch(0.87 0 0);
|
||||||
--chart-2: oklch(0.556 0 0);
|
--chart-2: oklch(0.556 0 0);
|
||||||
--chart-3: oklch(0.439 0 0);
|
--chart-3: oklch(0.439 0 0);
|
||||||
--chart-4: oklch(0.371 0 0);
|
--chart-4: oklch(0.371 0 0);
|
||||||
--chart-5: oklch(0.269 0 0);
|
--chart-5: oklch(0.269 0 0);
|
||||||
--sidebar: oklch(0.205 0 0);
|
--sidebar: oklch(0.18 0.02 264);
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--sidebar-border: oklch(0.985 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
|
||||||
|
/* Custom gradient colors for dark mode - more vibrant */
|
||||||
|
--gradient-purple: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 50%, #a78bfa 100%);
|
||||||
|
--gradient-blue: linear-gradient(135deg, #2563eb 0%, #3b82f6 50%, #60a5fa 100%);
|
||||||
|
--gradient-green: linear-gradient(135deg, #16a34a 0%, #22c55e 50%, #4ade80 100%);
|
||||||
|
--gradient-yellow: linear-gradient(135deg, #ca8a04 0%, #eab308 50%, #facc15 100%);
|
||||||
|
--gradient-pink: linear-gradient(135deg, #db2777 0%, #ec4899 50%, #f472b6 100%);
|
||||||
|
--gradient-orange: linear-gradient(135deg, #ea580c 0%, #f97316 50%, #fb923c 100%);
|
||||||
|
--gradient-cyan: linear-gradient(135deg, #0891b2 0%, #06b6d4 50%, #22d3ee 100%);
|
||||||
|
|
||||||
|
/* Background gradients for dark mode */
|
||||||
|
--bg-gradient-subtle: radial-gradient(circle at top right, rgba(124, 58, 237, 0.1) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at bottom left, rgba(139, 92, 246, 0.1) 0%, transparent 50%);
|
||||||
|
--bg-gradient-mesh: linear-gradient(135deg, rgba(124, 58, 237, 0.05) 0%, rgba(139, 92, 246, 0.05) 50%, rgba(167, 139, 250, 0.05) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
@@ -138,4 +158,41 @@
|
|||||||
html {
|
html {
|
||||||
@apply font-sans;
|
@apply font-sans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Smooth scrolling */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: oklch(0.708 0 0);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: oklch(0.556 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glassmorphism utility */
|
||||||
|
.glass {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .glass {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user