fix(ui): Zentrierung fuer Anleitung & Einstellungen gefixt, zufaellige Mockup-Filme durch echte API-Daten ersetzt
Ampel / ampel (push) Successful in 28s
Ampel / ampel (push) Successful in 28s
This commit is contained in:
@@ -1,78 +1,201 @@
|
||||
import { Disc } from 'lucide-react'
|
||||
import { Disc, Film, Sparkles, CheckCircle, Shield, Zap } from 'lucide-react'
|
||||
import { RadialProgress } from './ui/RadialProgress'
|
||||
import { TypeBadge, StatusBadge } from './ui/Badge'
|
||||
import { Button } from './ui/Button'
|
||||
|
||||
export function HeroCinemaBanner() {
|
||||
return (
|
||||
<div className="relative rounded-3xl overflow-hidden border border-amber-500/30 bg-[#0e1322] shadow-[0_0_40px_-5px_rgba(245,158,11,0.18)] mb-8 p-3">
|
||||
{/* Top Ambient Glow */}
|
||||
<div className="absolute top-0 inset-x-0 h-px bg-gradient-to-r from-transparent via-amber-500/40 to-transparent pointer-events-none" />
|
||||
interface JobMeta {
|
||||
year?: number
|
||||
overview?: string
|
||||
poster_path?: string
|
||||
genres?: string[]
|
||||
runtime?: number
|
||||
type?: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
{/* 5-Panel Movie Carousel Wall (Matching SC1 Mockup 1:1) */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 h-44 md:h-52">
|
||||
|
||||
{/* Panel 1 */}
|
||||
<div className="relative rounded-2xl overflow-hidden group cursor-pointer border border-slate-800 hover:border-amber-500/50 transition-all">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1534447677768-be436bb09401?q=80&w=800&auto=format&fit=crop"
|
||||
alt="Sci-Fi"
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500 brightness-90"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-transparent opacity-80" />
|
||||
<div className="absolute bottom-3 left-3 right-3 text-xs font-bold text-slate-100 tracking-tight drop-shadow">
|
||||
Sci-Fi Cinema
|
||||
</div>
|
||||
</div>
|
||||
interface Job {
|
||||
id: string
|
||||
type: 'cd' | 'dvd' | 'bluray' | 'uhd'
|
||||
status: 'pending' | 'processing' | 'transcoding' | 'canceling' | 'completed' | 'failed'
|
||||
device: string
|
||||
startTime: string
|
||||
endTime?: string
|
||||
progress: number
|
||||
title?: string
|
||||
can_retry?: boolean
|
||||
meta?: JobMeta | null
|
||||
}
|
||||
|
||||
{/* Panel 2 */}
|
||||
<div className="relative rounded-2xl overflow-hidden group cursor-pointer border border-slate-800 hover:border-amber-500/50 transition-all">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1578632767115-351597cf2477?q=80&w=800&auto=format&fit=crop"
|
||||
alt="Blade Runner 2049"
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500 brightness-90"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-transparent opacity-80" />
|
||||
<div className="absolute bottom-3 left-3 right-3 text-xs font-bold text-amber-300 tracking-tight drop-shadow">
|
||||
Blade Runner 2049
|
||||
</div>
|
||||
</div>
|
||||
function posterUrl(meta?: JobMeta | null): string | null {
|
||||
const p = meta?.poster_path
|
||||
if (!p) return null
|
||||
return p.startsWith('http') ? p : `https://image.tmdb.org/t/p/w500${p}`
|
||||
}
|
||||
|
||||
{/* Center Panel 3: Spinning Optical Disc Showcase */}
|
||||
<div className="relative rounded-2xl overflow-hidden bg-gradient-to-br from-slate-900 via-[#131929] to-slate-950 border-2 border-amber-500/40 flex items-center justify-center p-4 shadow-inner group">
|
||||
<div className="w-32 h-32 md:w-36 md:h-36 rounded-full disc-texture shadow-2xl shadow-black flex items-center justify-center p-1.5 animate-disc-spin border-2 border-amber-400/60 group-hover:border-amber-300 transition-colors">
|
||||
<div className="w-full h-full rounded-full border-2 border-dashed border-white/40 flex items-center justify-center bg-slate-950/70">
|
||||
<Disc size={44} className="text-amber-400 drop-shadow-[0_0_16px_rgba(245,158,11,0.9)]" />
|
||||
interface HeroCinemaBannerProps {
|
||||
aktiverJob?: Job | null
|
||||
completedJobs?: Job[]
|
||||
onDetailClick?: (jobId: string) => void
|
||||
onCancelClick?: (jobId: string) => void
|
||||
}
|
||||
|
||||
export function HeroCinemaBanner({
|
||||
aktiverJob,
|
||||
completedJobs = [],
|
||||
onDetailClick,
|
||||
onCancelClick,
|
||||
}: HeroCinemaBannerProps) {
|
||||
// Fall 1: Aktiver Job (Rippt oder Komprimiert)
|
||||
if (aktiverJob) {
|
||||
const isTranscoding = aktiverJob.status === 'transcoding'
|
||||
const statusText = isTranscoding ? 'HandBrake Kompression' : 'MakeMKV Extraktion'
|
||||
const poster = posterUrl(aktiverJob.meta)
|
||||
|
||||
return (
|
||||
<div className="relative rounded-3xl overflow-hidden border-2 border-amber-500/50 bg-gradient-to-r from-slate-950 via-slate-900 to-indigo-950 p-6 md:p-8 shadow-[0_0_50px_-10px_rgba(245,158,11,0.3)] text-slate-100 mb-8">
|
||||
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-8">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="relative flex-shrink-0">
|
||||
{poster ? (
|
||||
<div className="relative">
|
||||
<img src={poster} alt="" className="w-28 h-40 rounded-2xl object-cover shadow-2xl border-2 border-amber-500/40" />
|
||||
<div className="absolute -bottom-3 -right-3 w-14 h-14 rounded-full disc-texture shadow-xl flex items-center justify-center animate-disc-spin border border-amber-400">
|
||||
<Disc size={20} className="text-amber-400" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-28 h-28 rounded-full disc-texture shadow-2xl flex items-center justify-center p-1.5 animate-disc-spin border-2 border-amber-500/60">
|
||||
<div className="w-full h-full rounded-full border-2 border-dashed border-white/40 flex items-center justify-center bg-slate-950/70">
|
||||
<Disc size={40} className="text-amber-400 drop-shadow-[0_0_15px_rgba(245,158,11,0.8)]" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-center sm:text-left">
|
||||
<div className="flex items-center justify-center sm:justify-start gap-2 flex-wrap">
|
||||
<span className="px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider bg-amber-500 text-slate-950 shadow-md">
|
||||
NOW RIPPING
|
||||
</span>
|
||||
<TypeBadge type={aktiverJob.type} />
|
||||
<StatusBadge status={aktiverJob.status} />
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl md:text-3xl font-extrabold tracking-tight bg-gradient-to-r from-white via-slate-100 to-amber-200 bg-clip-text text-transparent">
|
||||
{aktiverJob.title || 'Aktiver Ripping-Prozess'}
|
||||
</h2>
|
||||
|
||||
<p className="text-xs text-slate-400 font-mono">
|
||||
Laufwerk: <strong className="text-amber-400">{aktiverJob.device}</strong> · {statusText}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-2.5 px-3 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-widest bg-amber-500 text-slate-950 shadow-md">
|
||||
DISC ENGINE
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center gap-6 flex-shrink-0 w-full lg:w-auto justify-end">
|
||||
<RadialProgress progress={aktiverJob.progress} size={96} strokeWidth={8} label="PROGRESS" />
|
||||
|
||||
<div className="flex flex-col gap-2 w-full sm:w-auto">
|
||||
<Button variant="amber" size="md" onClick={() => onDetailClick && onDetailClick(aktiverJob.id)}>
|
||||
<Sparkles size={16} /> Details
|
||||
</Button>
|
||||
<Button variant="danger" size="md" onClick={() => onCancelClick && onCancelClick(aktiverJob.id)}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Panel 4 */}
|
||||
<div className="relative rounded-2xl overflow-hidden group cursor-pointer border border-slate-800 hover:border-amber-500/50 transition-all hidden md:block">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1518709268805-4e9042af9f23?q=80&w=800&auto=format&fit=crop"
|
||||
alt="Dune"
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500 brightness-90"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-transparent opacity-80" />
|
||||
<div className="absolute bottom-3 left-3 right-3 text-xs font-bold text-slate-100 tracking-tight drop-shadow">
|
||||
Dune Part II
|
||||
// Fall 2: Keine echten gerippten Medien vorhanden -> Standby Hero Banner (Keine Fake-Filme!)
|
||||
if (completedJobs.length === 0) {
|
||||
return (
|
||||
<div className="relative rounded-3xl overflow-hidden border border-amber-500/30 bg-gradient-to-r from-slate-950 via-[#0d121f] to-slate-950 p-6 md:p-8 shadow-[0_0_35px_-5px_rgba(245,158,11,0.15)] text-slate-100 mb-8">
|
||||
<div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="w-24 h-24 rounded-full disc-texture shadow-2xl flex items-center justify-center p-1 animate-disc-spin border-2 border-amber-500/40 flex-shrink-0">
|
||||
<div className="w-full h-full rounded-full border-2 border-dashed border-white/30 flex items-center justify-center bg-slate-950/70">
|
||||
<Disc size={36} className="text-amber-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-emerald-500/15 text-emerald-400 border border-emerald-500/30 flex items-center gap-1">
|
||||
<CheckCircle size={11} /> READY FOR DISC
|
||||
</span>
|
||||
<span className="text-xs text-slate-400 font-mono">System Status: Bereit</span>
|
||||
</div>
|
||||
<h2 className="text-2xl md:text-3xl font-extrabold tracking-tight bg-gradient-to-r from-white via-slate-100 to-amber-200 bg-clip-text text-transparent">
|
||||
Rippy Cinema Studio
|
||||
</h2>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
Automatische Disc-Erkennung aktiv. Lege eine 4K UHD, Blu-ray, DVD oder CD in dein Laufwerk ein.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-xs font-medium text-slate-300 bg-slate-900/90 px-4 py-3 rounded-2xl border border-slate-800 flex-shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap size={16} className="text-amber-400" />
|
||||
<span>Gerippte Discs: <strong className="text-amber-400 font-mono text-sm">0</strong></span>
|
||||
</div>
|
||||
<div className="h-4 w-px bg-slate-800" />
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={16} className="text-indigo-400" />
|
||||
<span>MakeMKV 1:1 Lossless</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Panel 5 */}
|
||||
<div className="relative rounded-2xl overflow-hidden group cursor-pointer border border-slate-800 hover:border-amber-500/50 transition-all hidden md:block">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1451187580459-43490279c0fa?q=80&w=800&auto=format&fit=crop"
|
||||
alt="Interstellar"
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500 brightness-90"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-transparent opacity-80" />
|
||||
<div className="absolute bottom-3 left-3 right-3 text-xs font-bold text-slate-100 tracking-tight drop-shadow">
|
||||
Interstellar
|
||||
</div>
|
||||
</div>
|
||||
// Fall 3: Echte gerippte Titel vorhanden -> Zeige ECHTE gerippte Discs in der Galerie (Maximal 5 echte Jobs)
|
||||
const displayJobs = completedJobs.slice(0, 5)
|
||||
|
||||
return (
|
||||
<div className="relative rounded-3xl overflow-hidden border border-amber-500/30 bg-[#0e1322] shadow-[0_0_40px_-5px_rgba(245,158,11,0.18)] mb-8 p-4">
|
||||
<div className="flex items-center justify-between px-2 mb-3">
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-amber-400 flex items-center gap-2">
|
||||
<Film size={14} /> ECHTE GERIPPTE MEDIEN ({completedJobs.length})
|
||||
</h3>
|
||||
<span className="text-[11px] text-slate-400 font-mono">Aus deiner Rippy-Datenbank</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
|
||||
{displayJobs.map((job) => {
|
||||
const poster = posterUrl(job.meta)
|
||||
return (
|
||||
<div
|
||||
key={job.id}
|
||||
onClick={() => onDetailClick && onDetailClick(job.id)}
|
||||
className="relative rounded-2xl overflow-hidden group cursor-pointer border border-slate-800 hover:border-amber-500/50 transition-all bg-slate-900 aspect-[2/3] flex flex-col justify-end p-3"
|
||||
>
|
||||
{poster ? (
|
||||
<img
|
||||
src={poster}
|
||||
alt={job.title || ''}
|
||||
className="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-slate-800 via-slate-900 to-slate-950 flex flex-col items-center justify-center p-3 text-center">
|
||||
<Disc size={32} className="text-amber-400 mb-2" />
|
||||
<span className="text-[10px] font-bold text-slate-400 uppercase">{job.type}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-slate-950/40 to-transparent" />
|
||||
|
||||
<div className="relative z-10 space-y-1">
|
||||
<TypeBadge type={job.type} />
|
||||
<h4 className="text-xs font-bold text-slate-100 truncate group-hover:text-amber-400 transition-colors">
|
||||
{job.title || 'Unbekannt'}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user