Spaces:
Sleeping
Sleeping
File size: 5,488 Bytes
79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da 79fb5cc ef886da | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | import { motion } from 'framer-motion';
import { Loader2, AlertTriangle, CloudUpload, CheckCircle2 } from 'lucide-react';
import { useState } from 'react';
export function PageHeader({ title, subtitle, icon: Icon }) {
return (
<div className="mb-8">
<motion.div
initial={{ opacity: 0, x: -12 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4 }}
>
{Icon && (
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-cyan-500/20 to-purple-500/10 border border-white/[0.06] grid place-items-center mb-4">
<Icon size={22} className="text-cyan-400" />
</div>
)}
<h1 className="text-2xl sm:text-3xl font-extrabold tracking-tight">{title}</h1>
{subtitle && <p className="text-slate-400 mt-2 text-sm sm:text-base leading-relaxed max-w-xl">{subtitle}</p>}
</motion.div>
</div>
);
}
export function ResultBox({ children, className = '' }) {
return (
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, ease: [0.4, 0, 0.2, 1] }}
className={`mt-6 p-6 sm:p-7 rounded-2xl bg-gradient-to-br from-cyan-500/[0.06] to-purple-500/[0.04] border border-cyan-500/10 backdrop-blur-sm ${className}`}
>
{children}
</motion.div>
);
}
export function ErrorBox({ message }) {
if (!message) return null;
return (
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
className="mt-4 p-4 rounded-2xl bg-red-500/[0.08] border border-red-500/20 text-red-400 text-sm flex items-start gap-3"
>
<AlertTriangle size={18} className="mt-0.5 flex-shrink-0" />
<span>{message}</span>
</motion.div>
);
}
export function SubmitButton({ loading, children, onClick, type = 'submit' }) {
return (
<motion.button
type={type}
onClick={onClick}
disabled={loading}
whileTap={{ scale: 0.97 }}
className="btn-quantum w-full py-4 px-6 text-base font-bold rounded-2xl flex items-center justify-center gap-2.5 disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? (
<>
<Loader2 className="animate-spin" size={20} />
<span>Processing...</span>
</>
) : children}
</motion.button>
);
}
export function UploadZone({ accept, name, onChange, label, sublabel }) {
const [fileName, setFileName] = useState('');
const [isDragging, setIsDragging] = useState(false);
const handleChange = (e) => {
if (e.target.files[0]) {
setFileName(e.target.files[0].name);
}
onChange?.(e);
};
return (
<label
className="block cursor-pointer"
onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
onDragLeave={() => setIsDragging(false)}
onDrop={() => setIsDragging(false)}
>
<motion.div
whileHover={{ scale: 1.01 }}
className={`
border-2 border-dashed rounded-2xl p-10 text-center
transition-all duration-300
${isDragging
? 'border-cyan-400/50 bg-cyan-500/[0.06] shadow-lg shadow-cyan-500/5'
: fileName
? 'border-emerald-500/30 bg-emerald-500/[0.04]'
: 'border-white/[0.08] hover:border-cyan-500/20 hover:bg-white/[0.02]'
}
`}
>
{fileName ? (
<div className="flex flex-col items-center gap-2">
<CheckCircle2 size={32} className="text-emerald-400" />
<p className="text-sm font-semibold text-emerald-400">{fileName}</p>
<p className="text-xs text-slate-500">Click to change file</p>
</div>
) : (
<div className="flex flex-col items-center gap-3">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-cyan-500/10 to-purple-500/5 border border-white/[0.06] grid place-items-center">
<CloudUpload size={24} className="text-cyan-400" />
</div>
<div>
<p className="font-semibold text-sm text-slate-200">{label || 'Click to upload'}</p>
<p className="text-xs text-slate-500 mt-1">{sublabel || 'Drag and drop supported'}</p>
</div>
</div>
)}
</motion.div>
<input
type="file"
accept={accept}
name={name}
onChange={handleChange}
className="hidden"
/>
</label>
);
}
export function SectionLabel({ children }) {
return (
<p className="text-[11px] font-bold uppercase tracking-[0.15em] text-purple-400 mb-3 flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-purple-400 animate-pulse" />
{children}
</p>
);
}
|