tahamajs/IE / Projects /front /src /components /layout /LoadingScreen.tsx
tahamajs's picture
download
raw
18.5 kB
// src/components/layout/LoadingScreen.tsx
import { useEffect, useState, useRef, useMemo } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
Atom, FlaskConical, BookOpen, Microscope, Cpu, Sparkles,
Brain, Rocket, Zap, Globe, Database, Cloud, Shield,
Lock, Key, Fingerprint, QrCode, Scan, Eye, EyeOff,
Loader2, RefreshCw, CheckCircle, XCircle, AlertCircle,
Activity, BarChart, PieChart, TrendingUp, Users, MessageSquare,
FileText, FolderOpen, Star, Heart, Share2, Download, Upload,
Camera, Image, Video, Music, Code, Terminal, GitBranch,
Coffee, Gift, PartyPopper, Rocket as RocketIcon
} from "lucide-react";
interface LoadingScreenProps {
onFinish?: () => void;
duration?: number;
skipAnimation?: boolean;
backgroundImage?: string;
theme?: "light" | "dark" | "auto";
}
const LOADING_MESSAGES = [
{ threshold: 0, message: "Initializing quantum circuits…", icon: Atom },
{ threshold: 10, message: "Warping through research papers…", icon: BookOpen },
{ threshold: 20, message: "Loading your digital lab…", icon: FlaskConical },
{ threshold: 30, message: "Calibrating AI models…", icon: Brain },
{ threshold: 40, message: "Fetching latest preprints…", icon: Rocket },
{ threshold: 50, message: "Preparing your workspace…", icon: Sparkles },
{ threshold: 60, message: "Analyzing research trends…", icon: TrendingUp },
{ threshold: 70, message: "Connecting to research networks…", icon: Globe },
{ threshold: 80, message: "Optimizing your experience…", icon: Activity },
{ threshold: 90, message: "Almost ready…", icon: Zap },
];
const QUOTES = [
{ text: "The important thing is not to stop questioning.", author: "Albert Einstein" },
{ text: "Research is creating new knowledge.", author: "Neil Armstrong" },
{ text: "Curiosity is the engine of achievement.", author: "Ken Robinson" },
{ text: "The joy of discovery is certainly the liveliest.", author: "Marie Curie" },
{ text: "Imagination is more important than knowledge.", author: "Albert Einstein" },
{ text: "Science is a way of thinking much more than it is a body of knowledge.", author: "Carl Sagan" },
{ text: "The most beautiful thing we can experience is the mysterious.", author: "Albert Einstein" },
{ text: "Nothing in life is to be feared, it is only to be understood.", author: "Marie Curie" },
{ text: "The science of today is the technology of tomorrow.", author: "Edward Teller" },
{ text: "Innovation distinguishes between a leader and a follower.", author: "Steve Jobs" },
{ text: "The only source of knowledge is experience.", author: "Albert Einstein" },
{ text: "Success is not final, failure is not fatal.", author: "Winston Churchill" },
{ text: "The future belongs to those who believe in the beauty of their dreams.", author: "Eleanor Roosevelt" },
{ text: "Do what you can, with what you have, where you are.", author: "Theodore Roosevelt" },
{ text: "The secret of getting ahead is getting started.", author: "Mark Twain" },
];
const PARTICLES_COUNT = 80;
export function LoadingScreen({
onFinish,
duration = 3000,
skipAnimation = false,
backgroundImage,
theme = "auto"
}: LoadingScreenProps) {
const [progress, setProgress] = useState(0);
const [currentQuote, setCurrentQuote] = useState(QUOTES[0]);
const [showComplete, setShowComplete] = useState(false);
const [particles, setParticles] = useState<Array<{ x: number; y: number; delay: number; duration: number; size: number; color?: string }>>([]);
const animationFrameRef = useRef<number | undefined>(undefined);
const startTimeRef = useRef<number | undefined>(undefined);
const quoteIntervalRef = useRef<NodeJS.Timeout | undefined>(undefined);
const [isDark, setIsDark] = useState(false);
// Check theme
useEffect(() => {
if (theme === "dark") setIsDark(true);
else if (theme === "light") setIsDark(false);
else if (theme === "auto") {
setIsDark(window.matchMedia("(prefers-color-scheme: dark)").matches);
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}
}, [theme]);
// Initialize particles
useEffect(() => {
const colors = ["#6366f1", "#ec4899", "#f59e0b", "#10b981", "#3b82f6"];
const newParticles = Array.from({ length: PARTICLES_COUNT }, () => ({
x: Math.random() * 100,
y: Math.random() * 100,
delay: Math.random() * 10,
duration: Math.random() * 8 + 4,
size: Math.random() * 4 + 1,
color: colors[Math.floor(Math.random() * colors.length)],
}));
setParticles(newParticles);
}, []);
// Quote rotation
useEffect(() => {
let quoteIndex = 0;
quoteIntervalRef.current = setInterval(() => {
quoteIndex = (quoteIndex + 1) % QUOTES.length;
setCurrentQuote(QUOTES[quoteIndex]);
}, 4000);
return () => {
if (quoteIntervalRef.current) clearInterval(quoteIntervalRef.current);
};
}, []);
// Progress animation
useEffect(() => {
if (skipAnimation) {
setProgress(100);
setTimeout(() => {
setShowComplete(true);
setTimeout(() => onFinish?.(), 500);
}, 500);
return;
}
startTimeRef.current = performance.now();
const updateProgress = (now: number) => {
const elapsed = now - startTimeRef.current!;
const newProgress = Math.min(100, (elapsed / duration) * 100);
setProgress(newProgress);
if (newProgress < 100) {
animationFrameRef.current = requestAnimationFrame(updateProgress);
} else {
setShowComplete(true);
setTimeout(() => onFinish?.(), 600);
}
};
animationFrameRef.current = requestAnimationFrame(updateProgress);
return () => {
if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
if (quoteIntervalRef.current) clearInterval(quoteIntervalRef.current);
};
}, [duration, onFinish, skipAnimation]);
const currentMessageData = useMemo(() => {
for (let i = LOADING_MESSAGES.length - 1; i >= 0; i--) {
if (progress >= LOADING_MESSAGES[i].threshold) return LOADING_MESSAGES[i];
}
return LOADING_MESSAGES[0];
}, [progress]);
const radius = 45;
const circumference = 2 * Math.PI * radius;
const strokeDashoffset = circumference - (progress / 100) * circumference;
return (
<motion.div
className="fixed inset-0 z-[9999] flex flex-col items-center justify-center overflow-hidden"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0, scale: 1.02 }}
transition={{ duration: 0.5 }}
>
{/* Background */}
{backgroundImage ? (
<div className="absolute inset-0 bg-cover bg-center bg-no-repeat" style={{ backgroundImage: `url(${backgroundImage})` }} />
) : (
<div className="absolute inset-0 bg-gradient-to-br from-slate-950 via-indigo-950 to-purple-950">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-primary/20 via-transparent to-transparent animate-pulse" />
<div className="absolute inset-0 bg-grid-pattern opacity-10" />
</div>
)}
{/* Animated Grid Pattern */}
<div className="absolute inset-0 opacity-10">
<div className="absolute inset-0" style={{ backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fillRule='evenodd'%3E%3Cg fill='%239C92AC' fillOpacity='0.4'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")` }} />
</div>
{/* Floating Particles */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
{particles.map((particle, i) => (
<motion.div
key={i}
className="absolute rounded-full"
style={{
left: `${particle.x}%`,
top: `${particle.y}%`,
width: particle.size,
height: particle.size,
backgroundColor: particle.color,
boxShadow: `0 0 ${particle.size * 2}px ${particle.color}`,
}}
animate={{
y: [null, -20, 0],
opacity: [0, 0.6, 0],
scale: [1, 1.5, 1],
}}
transition={{
duration: particle.duration,
repeat: Infinity,
delay: particle.delay,
ease: "easeInOut",
}}
/>
))}
</div>
{/* Main Content */}
<div className="relative z-10 flex flex-col items-center max-w-md px-6 text-center">
{/* Logo Animation */}
<div className="relative mb-8">
<motion.div
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 12, ease: "linear" }}
className="relative"
>
<Atom className="w-32 h-32 text-primary/90 drop-shadow-glow" strokeWidth={1.5} />
</motion.div>
{/* Orbiting Particles */}
{[...Array(6)].map((_, i) => (
<motion.div
key={i}
className="absolute top-1/2 left-1/2"
style={{ x: "-50%", y: "-50%" }}
animate={{
rotate: [0, 360],
translateX: [0, 85, 0, -85, 0],
translateY: [0, 55, 0, -55, 0],
}}
transition={{
duration: 5 + i,
repeat: Infinity,
ease: "linear",
}}
>
{i === 0 && <Sparkles className="w-4 h-4 text-yellow-400 drop-shadow-glow-yellow" />}
{i === 1 && <RocketIcon className="w-4 h-4 text-pink-400 drop-shadow-glow-pink" />}
{i === 2 && <Brain className="w-4 h-4 text-purple-400 drop-shadow-glow-purple" />}
{i === 3 && <Zap className="w-4 h-4 text-blue-400 drop-shadow-glow-blue" />}
{i === 4 && <Star className="w-4 h-4 text-orange-400 drop-shadow-glow-orange" />}
{i === 5 && <Heart className="w-4 h-4 text-red-400 drop-shadow-glow-red" />}
</motion.div>
))}
{/* Progress Circle */}
<svg className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-36 h-36 -rotate-90">
<circle cx="72" cy="72" r={radius} fill="none" stroke="rgba(255,255,255,0.1)" strokeWidth="5" />
<motion.circle
cx="72" cy="72" r={radius} fill="none" stroke="url(#gradient)" strokeWidth="5" strokeLinecap="round"
strokeDasharray={circumference} strokeDashoffset={strokeDashoffset}
initial={false}
/>
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#6366f1" />
<stop offset="30%" stopColor="#8b5cf6" />
<stop offset="60%" stopColor="#ec4899" />
<stop offset="100%" stopColor="#f59e0b" />
</linearGradient>
</defs>
</svg>
{/* Inner Icon */}
<motion.div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
animate={{ scale: [1, 1.1, 1], rotate: [0, 360] }}
transition={{ duration: 3, repeat: Infinity, ease: "easeInOut" }}
>
{currentMessageData.icon && <currentMessageData.icon className="w-10 h-10 text-white/80 drop-shadow-glow" />}
</motion.div>
</div>
{/* Title */}
<motion.h1
className="text-4xl md:text-5xl font-extrabold bg-gradient-to-r from-primary via-secondary to-primary bg-clip-text text-transparent bg-300% animate-gradient mb-2"
animate={{ backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"] }}
transition={{ repeat: Infinity, duration: 6, ease: "linear" }}
>
IE Research Platform
</motion.h1>
<motion.p
className="text-sm text-indigo-200/70 mb-6"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
>
Where ideas transform into discoveries
</motion.p>
{/* Animated Message */}
<AnimatePresence mode="wait">
<motion.div
key={currentMessageData.message}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -15 }}
className="flex items-center gap-2 text-base text-indigo-200/80 font-mono"
>
<Loader2 className="w-4 h-4 animate-spin" />
<span>{currentMessageData.message}</span>
</motion.div>
</AnimatePresence>
{/* Progress Bar */}
<div className="w-80 mt-6">
<div className="flex justify-between text-xs text-indigo-300/60 mb-1">
<span>Loading System</span>
<span className="font-mono">{Math.round(progress)}%</span>
</div>
<div className="h-2 bg-white/10 rounded-full overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-primary via-secondary to-primary bg-300% animate-gradient rounded-full"
style={{ width: `${progress}%` }}
transition={{ duration: 0.3 }}
/>
</div>
</div>
{/* Icons Row */}
<div className="flex gap-6 mt-8 text-indigo-300/60">
{[FlaskConical, BookOpen, Microscope, Cpu, Globe, Database, Shield, Lock].map((Icon, idx) => (
<motion.div
key={idx}
animate={{ y: [0, -10, 0] }}
transition={{
duration: 1.5,
repeat: Infinity,
delay: idx * 0.2,
ease: "easeInOut",
}}
>
<Icon className="w-5 h-5" />
</motion.div>
))}
</div>
{/* Quote */}
<AnimatePresence mode="wait">
<motion.div
key={currentQuote.text}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -15 }}
className="mt-8 max-w-sm"
>
<p className="text-xs text-indigo-200/50 italic leading-relaxed">
"{currentQuote.text}"
</p>
<p className="text-[10px] text-indigo-300/40 mt-2">
— {currentQuote.author}
</p>
</motion.div>
</AnimatePresence>
{/* Version Info */}
<motion.div
className="absolute bottom-6 left-0 right-0 text-center"
initial={{ opacity: 0 }}
animate={{ opacity: 0.5 }}
transition={{ delay: 0.8 }}
>
<p className="text-[10px] text-indigo-400/40 font-mono">
Version 2.0.0 | Build 2026.06 | © IE Research Platform
</p>
</motion.div>
</div>
{/* Completion Animation Overlay */}
<AnimatePresence>
{showComplete && (
<motion.div
className="absolute inset-0 flex items-center justify-center premium-glass bg-black/80 backdrop-blur-3xl z-20"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
className="text-center"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: "spring", damping: 15 }}
>
<motion.div
className="w-20 h-20 mx-auto mb-4 rounded-full bg-gradient-to-r from-primary to-secondary flex items-center justify-center"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring", delay: 0.2 }}
>
<PartyPopper className="w-12 h-12 text-white" />
</motion.div>
<motion.h2
className="text-2xl font-bold text-white mb-2"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
Ready to Research!
</motion.h2>
<motion.p
className="text-gray-300"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
>
Your workspace is ready
</motion.p>
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* CSS Styles */}
<style>{`
.drop-shadow-glow {
filter: drop-shadow(0 0 10px rgba(99, 102, 241, 0.5));
}
.drop-shadow-glow-yellow {
filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.5));
}
.drop-shadow-glow-pink {
filter: drop-shadow(0 0 8px rgba(236, 72, 153, 0.5));
}
.drop-shadow-glow-purple {
filter: drop-shadow(0 0 8px rgba(139, 92, 246, 0.5));
}
.drop-shadow-glow-blue {
filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.5));
}
.drop-shadow-glow-orange {
filter: drop-shadow(0 0 8px rgba(249, 115, 22, 0.5));
}
.drop-shadow-glow-red {
filter: drop-shadow(0 0 8px rgba(239, 68, 68, 0.5));
}
@keyframes gradient {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.animate-gradient {
background-size: 300% 300%;
animation: gradient 6s ease infinite;
}
.bg-grid-pattern {
background-image: linear-gradient(rgba(99, 102, 241, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(99, 102, 241, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
}
`}</style>
</motion.div>
);
}
// Default export for lazy loading
export default LoadingScreen;
// Also export as named export for consistency
export { LoadingScreen as LoadingScreenComponent };

Xet Storage Details

Size:
18.5 kB
·
Xet hash:
3af3081f240258c6739fda9c8cb27afff576838e546a53ab7e145d2a9e2a582b

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.