← 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,
};