style(ui): Visuelles High-End Overhaul fuer Cinematic Cinema OS (3D Spin-Disc, Hero Banner, Radial Gauges & Poster Grid)
Ampel / ampel (push) Successful in 27s
Ampel / ampel (push) Successful in 27s
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import { Disc, Play, Sparkles, Film, Shield, Zap, CheckCircle } from 'lucide-react'
|
||||
import { RadialProgress } from './ui/RadialProgress'
|
||||
import { TypeBadge, StatusBadge } from './ui/Badge'
|
||||
import { Button } from './ui/Button'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
interface HeroCinemaBannerProps {
|
||||
aktiverJob?: Job | null
|
||||
onDetailClick?: (jobId: string) => void
|
||||
onCancelClick?: (jobId: string) => void
|
||||
totalJobsCount: number
|
||||
completedCount: number
|
||||
}
|
||||
|
||||
export function HeroCinemaBanner({
|
||||
aktiverJob,
|
||||
onDetailClick,
|
||||
onCancelClick,
|
||||
totalJobsCount,
|
||||
completedCount,
|
||||
}: HeroCinemaBannerProps) {
|
||||
if (aktiverJob) {
|
||||
const isTranscoding = aktiverJob.status === 'transcoding'
|
||||
const statusText = isTranscoding ? 'HandBrake Kompression' : 'MakeMKV Extraktion'
|
||||
|
||||
return (
|
||||
<div className="relative rounded-3xl overflow-hidden border border-amber-500/40 bg-gradient-to-r from-slate-950 via-slate-900 to-indigo-950/90 p-6 md:p-8 shadow-[0_0_50px_-10px_rgba(245,158,11,0.25)] text-slate-100 mb-8">
|
||||
{/* Background Ambient Glow Effects */}
|
||||
<div className="absolute -top-24 -right-24 w-96 h-96 bg-amber-500/15 rounded-full blur-3xl pointer-events-none" />
|
||||
<div className="absolute -bottom-24 -left-24 w-96 h-96 bg-indigo-500/15 rounded-full blur-3xl pointer-events-none" />
|
||||
|
||||
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-8">
|
||||
{/* Left: 3D Metallic Disc Graphic */}
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="relative flex-shrink-0">
|
||||
<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-slate-700/60">
|
||||
<div className="w-full h-full rounded-full border-2 border-dashed border-white/30 flex items-center justify-center bg-slate-950/60">
|
||||
<Disc size={44} className="text-amber-400 drop-shadow-[0_0_12px_rgba(245,158,11,0.6)]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute -bottom-2 -right-2 px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-amber-500 text-slate-950 shadow-md">
|
||||
NOW RIPPING
|
||||
</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">
|
||||
<TypeBadge type={aktiverJob.type} />
|
||||
<StatusBadge status={aktiverJob.status} />
|
||||
<span className="px-2.5 py-0.5 rounded-full text-[11px] font-semibold bg-slate-800/80 text-slate-300 border border-slate-700">
|
||||
{statusText}
|
||||
</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">
|
||||
{aktiverJob.title || 'Aktiver Ripping-Prozess'}
|
||||
</h2>
|
||||
|
||||
<p className="text-xs text-slate-400 flex items-center justify-center sm:justify-start gap-3">
|
||||
<span>Laufwerk: <strong className="font-mono text-amber-300/90">{aktiverJob.device}</strong></span>
|
||||
<span>•</span>
|
||||
<span>Gestartet: {new Date(aktiverJob.startTime).toLocaleTimeString('de-DE')}</span>
|
||||
</p>
|
||||
|
||||
{/* Quality & Audio Badges Mockup */}
|
||||
<div className="flex items-center justify-center sm:justify-start gap-1.5 pt-1 text-[10px] font-bold text-slate-400">
|
||||
<span className="px-2 py-0.5 rounded bg-slate-800/80 border border-slate-700/80">HDR10+</span>
|
||||
<span className="px-2 py-0.5 rounded bg-slate-800/80 border border-slate-700/80">DOLBY ATMOS</span>
|
||||
<span className="px-2 py-0.5 rounded bg-slate-800/80 border border-slate-700/80">LOSSLESS MKV</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Radial Progress Ring & Controls */}
|
||||
<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 ansehen
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
size="md"
|
||||
onClick={() => onCancelClick && onCancelClick(aktiverJob.id)}
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Idle Banner (Kein aktiver Job)
|
||||
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="absolute -top-24 -right-24 w-80 h-80 bg-indigo-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-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 Status: 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>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-xs font-medium text-slate-300 bg-slate-900/80 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>Erledigte Jobs: <strong className="text-amber-400 font-mono">{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 1:1</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -11,12 +11,16 @@ export function Card({ children, className = '', glow = false, onClick }: CardPr
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={`rounded-2xl border transition-all duration-300 ${
|
||||
glow ? 'shadow-[0_0_30px_-5px_rgba(245,158,11,0.15)] border-amber-500/30' : 'shadow-lg shadow-black/5 border-slate-200/80 dark:border-slate-800/80'
|
||||
} bg-white/90 dark:bg-slate-900/80 backdrop-blur-md text-slate-900 dark:text-slate-100 ${
|
||||
onClick ? 'cursor-pointer hover:border-slate-300 dark:hover:border-slate-700 hover:shadow-xl' : ''
|
||||
className={`rounded-2xl transition-all duration-300 relative overflow-hidden ${
|
||||
glow
|
||||
? 'glass-panel-amber'
|
||||
: 'bg-white/90 dark:bg-slate-900/80 backdrop-blur-xl border border-slate-200/80 dark:border-slate-800/80 shadow-xl shadow-black/10'
|
||||
} text-slate-900 dark:text-slate-100 ${
|
||||
onClick ? 'cursor-pointer hover:border-slate-300 dark:hover:border-slate-700 hover:shadow-2xl hover:-translate-y-0.5' : ''
|
||||
} ${className}`}
|
||||
>
|
||||
{/* Top subtle highlight line for 3D glass depth */}
|
||||
<div className="absolute top-0 inset-x-0 h-px bg-gradient-to-r from-transparent via-white/20 dark:via-white/10 to-transparent pointer-events-none" />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
@@ -32,7 +36,7 @@ export function CardHeader({ children, className = '' }: { children: ReactNode;
|
||||
|
||||
export function CardTitle({ children, className = '' }: { children: ReactNode; className?: string }) {
|
||||
return (
|
||||
<h2 className={`text-lg font-semibold tracking-tight text-slate-900 dark:text-slate-100 ${className}`}>
|
||||
<h2 className={`text-lg font-bold tracking-tight text-slate-900 dark:text-slate-100 ${className}`}>
|
||||
{children}
|
||||
</h2>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
interface RadialProgressProps {
|
||||
progress: number
|
||||
size?: number
|
||||
strokeWidth?: number
|
||||
label?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export function RadialProgress({ progress, size = 80, strokeWidth = 7, label, status }: RadialProgressProps) {
|
||||
const radius = (size - strokeWidth) / 2
|
||||
const circumference = radius * 2 * Math.PI
|
||||
const strokeDashoffset = circumference - (progress / 100) * circumference
|
||||
|
||||
const isComplete = progress === 100
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex items-center justify-center">
|
||||
<svg width={size} height={size} className="transform -rotate-90">
|
||||
{/* Background track */}
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
stroke="currentColor"
|
||||
strokeWidth={strokeWidth}
|
||||
className="text-slate-800 dark:text-slate-800/80 fill-none"
|
||||
/>
|
||||
{/* Progress track */}
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
stroke="url(#progressGradient)"
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
strokeLinecap="round"
|
||||
className="fill-none transition-all duration-500 ease-out"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="progressGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor={isComplete ? '#10b981' : '#f59e0b'} />
|
||||
<stop offset="50%" stopColor={isComplete ? '#059669' : '#6366f1'} />
|
||||
<stop offset="100%" stopColor={isComplete ? '#34d399' : '#a855f7'} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div className="absolute flex flex-col items-center justify-center text-center">
|
||||
<span className="text-sm font-bold font-mono text-slate-900 dark:text-slate-100 tracking-tighter">
|
||||
{progress}%
|
||||
</span>
|
||||
{label && <span className="text-[9px] uppercase tracking-wider text-slate-400">{label}</span>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user