feat(ui): Rebuild Layout EXACTLY 1:1 matching SC1 Mockup (Top Header Nav, 5-Panel Movie Wall, 2-Column Dashboard Grid with Sparkline & Recently Ripped)
Ampel / ampel (push) Successful in 28s

This commit is contained in:
Hitonabi
2026-07-24 16:25:21 +02:00
parent f0a8455fc2
commit 0f20d9d8f4
3 changed files with 349 additions and 639 deletions
+71 -217
View File
@@ -1,224 +1,78 @@
import { Disc, Play, Sparkles, Film, Shield, Zap, CheckCircle, Download, FileSpreadsheet } from 'lucide-react'
import { RadialProgress } from './ui/RadialProgress'
import { TypeBadge, StatusBadge } from './ui/Badge'
import { Button } from './ui/Button'
import { Disc } from 'lucide-react'
interface JobMeta {
year?: number
overview?: string
poster_path?: string
genres?: string[]
runtime?: number
type?: string
source?: string
}
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
}
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}`
}
interface HeroCinemaBannerProps {
aktiverJob?: Job | null
latestJob?: Job | null
onDetailClick?: (jobId: string) => void
onCancelClick?: (jobId: string) => void
totalJobsCount: number
completedCount: number
}
export function HeroCinemaBanner({
aktiverJob,
latestJob,
onDetailClick,
onCancelClick,
totalJobsCount,
completedCount,
}: HeroCinemaBannerProps) {
// Fall 1: Ein Job rippt oder komprimiert gerade live
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_60px_-10px_rgba(245,158,11,0.35)] text-slate-100 mb-8">
<div className="absolute -top-24 -right-24 w-96 h-96 bg-amber-500/20 rounded-full blur-3xl pointer-events-none" />
<div className="absolute -bottom-24 -left-24 w-96 h-96 bg-indigo-500/20 rounded-full blur-3xl pointer-events-none" />
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-8">
<div className="flex items-center gap-6">
{/* 3D Spinning Disc Graphic */}
<div className="relative flex-shrink-0">
{poster ? (
<div className="relative">
<img src={poster} alt="" className="w-28 h-40 md:w-32 md:h-44 rounded-2xl object-cover shadow-2xl border-2 border-amber-500/40" />
<div className="absolute -bottom-3 -right-3 w-16 h-16 rounded-full disc-texture shadow-xl flex items-center justify-center animate-disc-spin border border-amber-400/60">
<Disc size={24} className="text-amber-400" />
</div>
</div>
) : (
<div className="w-28 h-28 md:w-32 md:h-32 rounded-full disc-texture shadow-2xl shadow-black/80 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={44} 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 shadow-amber-500/30">
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 flex items-center justify-center sm:justify-start gap-3 font-mono">
<span>Laufwerk: <strong className="text-amber-400">{aktiverJob.device}</strong></span>
<span></span>
<span>{statusText}</span>
</p>
<div className="flex items-center justify-center sm:justify-start gap-2 pt-1">
<span className="px-2.5 py-0.5 rounded bg-slate-800 border border-slate-700 text-[10px] font-bold text-amber-400">4K / HD READY</span>
<span className="px-2.5 py-0.5 rounded bg-slate-800 border border-slate-700 text-[10px] font-bold text-slate-300">LOSSLESS MKV</span>
<span className="px-2.5 py-0.5 rounded bg-slate-800 border border-slate-700 text-[10px] font-bold text-slate-300">AUTO-METADATA</span>
</div>
</div>
</div>
<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={100} strokeWidth={9} label="PROGRESS" />
<div className="flex flex-col gap-2.5 w-full sm:w-auto">
<Button
variant="amber"
size="md"
onClick={() => onDetailClick && onDetailClick(aktiverJob.id)}
>
<Sparkles size={16} /> Details ansehen
</Button>
<Button
variant="danger"
size="md"
onClick={() => onCancelClick && onCancelClick(aktiverJob.id)}
>
Abbrechen
</Button>
</div>
</div>
</div>
</div>
)
}
// Fall 2: Kein aktiver Job, aber es gibt zuletzt gerippte Medien (z.B. Evangelion 2.22) -> FEATURED SHOWCASE
if (latestJob) {
const poster = posterUrl(latestJob.meta)
return (
<div className="relative rounded-3xl overflow-hidden border border-amber-500/30 bg-gradient-to-r from-slate-950 via-slate-900 to-slate-950 p-6 md:p-8 shadow-[0_0_40px_-10px_rgba(245,158,11,0.15)] text-slate-100 mb-8">
<div className="absolute -top-24 -right-24 w-96 h-96 bg-amber-500/10 rounded-full blur-3xl pointer-events-none" />
<div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-6">
<div className="flex items-center gap-6">
{/* Poster or Stylized Media Icon */}
{poster ? (
<img src={poster} alt="" className="w-24 h-36 rounded-2xl object-cover shadow-2xl border border-amber-500/30 flex-shrink-0" />
) : (
<div className="w-24 h-32 rounded-2xl bg-gradient-to-br from-amber-500/20 via-indigo-600/20 to-purple-600/20 border border-amber-500/30 flex items-center justify-center flex-shrink-0 shadow-xl">
<Film size={40} className="text-amber-400" />
</div>
)}
<div className="space-y-2 text-center md:text-left">
<div className="flex items-center justify-center md:justify-start gap-2 flex-wrap">
<span className="px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 flex items-center gap-1">
<CheckCircle size={11} /> ZULETZT GERIPPT
</span>
<TypeBadge type={latestJob.type} />
<StatusBadge status={latestJob.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">
{latestJob.title || 'Unbekanntes Medium'}
</h2>
<p className="text-xs text-slate-400">
Fertiggestellt am {new Date(latestJob.startTime).toLocaleDateString('de-DE')} um {new Date(latestJob.startTime).toLocaleTimeString('de-DE')} · Vorbereitet für Media-Server
</p>
<div className="flex items-center justify-center md:justify-start gap-2 pt-1">
<Button
variant="amber"
size="sm"
onClick={() => onDetailClick && onDetailClick(latestJob.id)}
>
<Download size={14} /> Dateien / Details
</Button>
</div>
</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>Erfolgreiche Rips: <strong className="text-amber-400 font-mono text-sm">{completedCount}</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 Lossless</span>
</div>
</div>
</div>
</div>
)
}
// Standby Banner
export function HeroCinemaBanner() {
return (
<div className="relative rounded-3xl overflow-hidden border border-slate-800/80 bg-gradient-to-r from-slate-950 via-slate-900 to-indigo-950/60 p-6 md:p-8 shadow-2xl 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="p-4 rounded-2xl bg-gradient-to-br from-amber-500/20 to-indigo-600/20 border border-amber-500/30 shadow-lg shadow-amber-500/10">
<Film size={36} className="text-amber-400" />
</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 Standby</span>
</div>
<h2 className="text-2xl 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-0.5">
Automatische Disc-Erkennung aktiv. Lege eine 4K UHD, Blu-ray, DVD oder CD ein.
</p>
<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" />
{/* 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>
{/* 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>
{/* 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)]" />
</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>
</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
</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>
</div>
</div>
)