← back to Watches
src/components/LuxuryLoadingStates.jsx
465 lines
/**
* LUXURY LOADING STATES
* Premium loading animations for the Omega Watch platform
*
* Features:
* - Watch mechanism-inspired animations
* - Skeleton loaders with shimmer
* - Progress indicators
* - Cinematic loading screens
*/
import React from 'react';
import { motion } from 'framer-motion';
// ============================================================================
// WATCH MECHANISM LOADER - Main loading animation
// ============================================================================
export const WatchMechanismLoader = ({ size = 'lg', message = 'Loading Omega Collection' }) => {
const sizes = {
sm: { container: 60, gear: 30, watch: '2xl' },
md: { container: 80, gear: 40, watch: '3xl' },
lg: { container: 120, gear: 60, watch: '5xl' },
};
const current = sizes[size];
return (
<div className="flex flex-col items-center justify-center space-y-6">
{/* Animated Watch Icon with rotating gears */}
<div className="relative" style={{ width: current.container, height: current.container }}>
{/* Outer ring - rotates slowly */}
<motion.div
className="absolute inset-0 rounded-full border-4 border-omega-red/30"
animate={{ rotate: 360 }}
transition={{
duration: 4,
repeat: Infinity,
ease: 'linear',
}}
>
<div className="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 w-2 h-2 bg-omega-red rounded-full" />
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 w-2 h-2 bg-omega-red rounded-full" />
<div className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 w-2 h-2 bg-omega-red rounded-full" />
<div className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 w-2 h-2 bg-omega-red rounded-full" />
</motion.div>
{/* Inner gear - rotates faster */}
<motion.div
className="absolute inset-4 rounded-full border-4 border-omega-gold/50"
animate={{ rotate: -360 }}
transition={{
duration: 2,
repeat: Infinity,
ease: 'linear',
}}
>
<div className="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 w-3 h-3 bg-omega-gold rounded-sm" />
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 w-3 h-3 bg-omega-gold rounded-sm" />
</motion.div>
{/* Center watch icon */}
<div className="absolute inset-0 flex items-center justify-center">
<motion.div
animate={{ scale: [1, 1.1, 1] }}
transition={{
duration: 2,
repeat: Infinity,
ease: 'easeInOut',
}}
className={`text-${current.watch}`}
>
⌚
</motion.div>
</div>
{/* Glow effect */}
<motion.div
className="absolute inset-0 rounded-full bg-omega-red/20 blur-xl"
animate={{ opacity: [0.3, 0.6, 0.3] }}
transition={{
duration: 2,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
</div>
{/* Loading message */}
{message && (
<div className="text-center space-y-2">
<motion.p
className="text-gray-700 dark:text-gray-300 text-lg font-semibold"
animate={{ opacity: [1, 0.5, 1] }}
transition={{
duration: 1.5,
repeat: Infinity,
ease: 'easeInOut',
}}
>
{message}
</motion.p>
{/* Loading dots */}
<div className="flex items-center justify-center space-x-2">
{[0, 1, 2].map((i) => (
<motion.div
key={i}
className="w-2 h-2 bg-omega-red rounded-full"
animate={{ y: [0, -8, 0] }}
transition={{
duration: 0.6,
repeat: Infinity,
delay: i * 0.2,
ease: 'easeInOut',
}}
/>
))}
</div>
</div>
)}
</div>
);
};
// ============================================================================
// SHIMMER SKELETON LOADER
// ============================================================================
export const ShimmerLoader = ({ width = '100%', height = '20px', className = '', variant = 'base' }) => {
const variants = {
base: 'bg-gray-200 dark:bg-gray-700',
gold: 'bg-gradient-to-r from-yellow-100 to-yellow-200 dark:from-yellow-900/20 dark:to-yellow-800/20',
premium: 'bg-gradient-to-r from-gray-100 via-gray-200 to-gray-100 dark:from-gray-800 dark:via-gray-700 dark:to-gray-800',
};
return (
<div
className={`relative overflow-hidden rounded-lg ${variants[variant]} ${className}`}
style={{ width, height }}
>
<motion.div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/40 dark:via-white/10 to-transparent"
animate={{ x: ['-100%', '200%'] }}
transition={{
repeat: Infinity,
duration: 1.5,
ease: 'linear',
}}
/>
</div>
);
};
// ============================================================================
// WATCH CARD SKELETON
// ============================================================================
export const WatchCardSkeleton = ({ count = 1 }) => {
return (
<>
{Array.from({ length: count }).map((_, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden"
>
{/* Image skeleton */}
<ShimmerLoader height="256px" variant="premium" />
{/* Content skeleton */}
<div className="p-6 space-y-4">
<ShimmerLoader height="24px" width="80%" variant="gold" />
<ShimmerLoader height="16px" width="60%" />
<div className="grid grid-cols-2 gap-4 pt-4">
<div className="space-y-2">
<ShimmerLoader height="12px" width="70%" />
<ShimmerLoader height="20px" width="90%" />
</div>
<div className="space-y-2">
<ShimmerLoader height="12px" width="70%" />
<ShimmerLoader height="20px" width="90%" />
</div>
</div>
<ShimmerLoader height="40px" width="100%" variant="base" className="mt-4" />
</div>
</motion.div>
))}
</>
);
};
// ============================================================================
// PROGRESS BAR with luxury styling
// ============================================================================
export const LuxuryProgressBar = ({ progress = 0, showPercentage = true, label = '' }) => {
return (
<div className="space-y-2">
{(label || showPercentage) && (
<div className="flex items-center justify-between text-sm">
{label && <span className="text-gray-700 dark:text-gray-300 font-medium">{label}</span>}
{showPercentage && (
<span className="text-omega-red font-bold">{Math.round(progress)}%</span>
)}
</div>
)}
<div className="relative h-3 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
{/* Background glow */}
<motion.div
className="absolute inset-0 bg-gradient-to-r from-omega-red/20 to-omega-gold/20"
animate={{ x: ['-100%', '100%'] }}
transition={{
repeat: Infinity,
duration: 2,
ease: 'linear',
}}
/>
{/* Progress fill */}
<motion.div
className="absolute inset-y-0 left-0 bg-gradient-to-r from-omega-red to-omega-gold rounded-full shadow-lg"
initial={{ width: 0 }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.5, ease: 'easeOut' }}
>
{/* Shine effect */}
<motion.div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent"
animate={{ x: ['-100%', '200%'] }}
transition={{
repeat: Infinity,
duration: 1.5,
ease: 'linear',
}}
/>
</motion.div>
</div>
</div>
);
};
// ============================================================================
// CIRCULAR PROGRESS INDICATOR
// ============================================================================
export const CircularProgress = ({ progress = 0, size = 100, strokeWidth = 8, showLabel = true }) => {
const radius = (size - strokeWidth) / 2;
const circumference = radius * 2 * Math.PI;
const offset = circumference - (progress / 100) * circumference;
return (
<div className="relative inline-flex items-center justify-center">
<svg width={size} height={size} className="transform -rotate-90">
{/* Background circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
stroke="currentColor"
strokeWidth={strokeWidth}
fill="none"
className="text-gray-200 dark:text-gray-700"
/>
{/* Progress circle */}
<motion.circle
cx={size / 2}
cy={size / 2}
r={radius}
stroke="url(#gradient)"
strokeWidth={strokeWidth}
fill="none"
strokeLinecap="round"
initial={{ strokeDashoffset: circumference }}
animate={{ strokeDashoffset: offset }}
style={{
strokeDasharray: circumference,
}}
transition={{ duration: 0.5, ease: 'easeOut' }}
/>
{/* Gradient definition */}
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#C8102E" />
<stop offset="100%" stopColor="#FFD700" />
</linearGradient>
</defs>
</svg>
{/* Center label */}
{showLabel && (
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-2xl font-bold text-gray-800 dark:text-white">
{Math.round(progress)}%
</span>
</div>
)}
</div>
);
};
// ============================================================================
// SPINNER VARIANTS
// ============================================================================
export const LuxurySpinner = ({ size = 'md', variant = 'primary' }) => {
const sizes = {
sm: 'w-4 h-4 border-2',
md: 'w-8 h-8 border-3',
lg: 'w-12 h-12 border-4',
xl: 'w-16 h-16 border-4',
};
const variants = {
primary: 'border-omega-red border-t-transparent',
gold: 'border-omega-gold border-t-transparent',
white: 'border-white border-t-transparent',
};
return (
<motion.div
className={`rounded-full ${sizes[size]} ${variants[variant]}`}
animate={{ rotate: 360 }}
transition={{
duration: 1,
repeat: Infinity,
ease: 'linear',
}}
/>
);
};
// ============================================================================
// PULSING DOT LOADER
// ============================================================================
export const PulsingDots = ({ count = 3, color = 'omega-red' }) => {
return (
<div className="flex items-center space-x-2">
{Array.from({ length: count }).map((_, i) => (
<motion.div
key={i}
className={`w-3 h-3 bg-${color} rounded-full`}
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1],
}}
transition={{
duration: 1,
repeat: Infinity,
delay: i * 0.2,
ease: 'easeInOut',
}}
/>
))}
</div>
);
};
// ============================================================================
// FULL PAGE CINEMATIC LOADER
// ============================================================================
export const CinematicPageLoader = ({ message = 'Loading Omega Collection' }) => {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-gradient-to-br from-omega-navy via-gray-900 to-black z-[9999] flex items-center justify-center"
>
{/* Animated background particles */}
<div className="absolute inset-0 overflow-hidden">
{Array.from({ length: 20 }).map((_, i) => (
<motion.div
key={i}
className="absolute w-1 h-1 bg-omega-gold/30 rounded-full"
style={{
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
}}
animate={{
y: [0, -30, 0],
opacity: [0, 1, 0],
}}
transition={{
duration: 3 + Math.random() * 2,
repeat: Infinity,
delay: Math.random() * 2,
}}
/>
))}
</div>
{/* Main loader */}
<div className="relative z-10">
<WatchMechanismLoader size="lg" message={message} />
</div>
{/* Bottom brand mark */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
className="absolute bottom-12 left-1/2 -translate-x-1/2 text-center"
>
<p className="text-white/60 text-sm tracking-widest uppercase">
Omega Watch Price History
</p>
<div className="mt-4 flex items-center justify-center space-x-1">
<div className="w-2 h-2 bg-omega-red rounded-full animate-pulse" />
<p className="text-white/40 text-xs">Excellence in Every Detail</p>
</div>
</motion.div>
</motion.div>
);
};
// ============================================================================
// DATA TABLE SKELETON
// ============================================================================
export const TableSkeleton = ({ rows = 5, columns = 4 }) => {
return (
<div className="space-y-3">
{/* Header */}
<div className="grid gap-4" style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}>
{Array.from({ length: columns }).map((_, i) => (
<ShimmerLoader key={i} height="24px" width="80%" variant="gold" />
))}
</div>
{/* Rows */}
{Array.from({ length: rows }).map((_, rowIndex) => (
<motion.div
key={rowIndex}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: rowIndex * 0.05 }}
className="grid gap-4"
style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}
>
{Array.from({ length: columns }).map((_, colIndex) => (
<ShimmerLoader
key={colIndex}
height="20px"
width={`${60 + Math.random() * 30}%`}
/>
))}
</motion.div>
))}
</div>
);
};
export default {
WatchMechanismLoader,
ShimmerLoader,
WatchCardSkeleton,
LuxuryProgressBar,
CircularProgress,
LuxurySpinner,
PulsingDots,
CinematicPageLoader,
TableSkeleton,
};