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>
    );
}