← back to Watches

src/components/LuxuryInteractions.jsx

472 lines

/**
 * LUXURY MICRO-INTERACTIONS LIBRARY
 * Premium interaction components for the Omega Watch platform
 *
 * Features:
 * - Haptic feedback simulation
 * - Sound effects (optional)
 * - Premium animations
 * - Gesture controls
 * - Parallax effects
 */

import React, { useState, useEffect, useRef } from 'react';
import { motion, useMotionValue, useTransform, useSpring } from 'framer-motion';

// ============================================================================
// HAPTIC FEEDBACK SIMULATOR
// ============================================================================
export const useHaptic = () => {
  const triggerHaptic = (type = 'light') => {
    // Web Vibration API (if supported)
    if ('vibrate' in navigator) {
      const patterns = {
        light: [10],
        medium: [15],
        heavy: [20],
        success: [10, 50, 10],
        error: [20, 100, 20],
        selection: [5],
      };
      navigator.vibrate(patterns[type] || patterns.light);
    }

    // Visual feedback as fallback
    document.body.classList.add('haptic-feedback');
    setTimeout(() => document.body.classList.remove('haptic-feedback'), 100);
  };

  return { triggerHaptic };
};

// ============================================================================
// LUXURY BUTTON with premium interactions
// ============================================================================
export const LuxuryButton = ({
  children,
  onClick,
  variant = 'primary',
  size = 'md',
  icon,
  loading = false,
  disabled = false,
  haptic = true,
  className = ''
}) => {
  const { triggerHaptic } = useHaptic();
  const [isPressed, setIsPressed] = useState(false);

  const variants = {
    primary: 'bg-gradient-to-r from-omega-red to-red-700 text-white shadow-lg hover:shadow-redGlow',
    secondary: 'bg-gradient-to-r from-omega-navy to-gray-800 text-white shadow-md hover:shadow-lg',
    gold: 'bg-gradient-to-r from-yellow-400 to-yellow-600 text-omega-navy shadow-md hover:shadow-goldGlow',
    outline: 'border-2 border-omega-red text-omega-red hover:bg-omega-red hover:text-white',
    ghost: 'text-omega-red hover:bg-red-50 dark:hover:bg-red-900/20',
  };

  const sizes = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-6 py-3 text-base',
    lg: 'px-8 py-4 text-lg',
    xl: 'px-10 py-5 text-xl',
  };

  const handleClick = (e) => {
    if (disabled || loading) return;

    if (haptic) triggerHaptic('light');

    setIsPressed(true);
    setTimeout(() => setIsPressed(false), 150);

    if (onClick) onClick(e);
  };

  return (
    <motion.button
      whileHover={{ scale: 1.02, y: -2 }}
      whileTap={{ scale: 0.98, y: 0 }}
      transition={{ type: 'spring', stiffness: 400, damping: 17 }}
      onClick={handleClick}
      disabled={disabled || loading}
      className={`
        ${variants[variant]}
        ${sizes[size]}
        ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
        ${isPressed ? 'ring-4 ring-omega-red/30' : ''}
        font-semibold rounded-xl transition-all duration-300
        flex items-center justify-center space-x-2
        relative overflow-hidden
        ${className}
      `}
    >
      {/* Shimmer effect on hover */}
      <motion.div
        className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent"
        initial={{ x: '-100%' }}
        whileHover={{ x: '100%' }}
        transition={{ duration: 0.6, ease: 'easeInOut' }}
      />

      {loading ? (
        <motion.div
          animate={{ rotate: 360 }}
          transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
          className="w-5 h-5 border-2 border-white border-t-transparent rounded-full"
        />
      ) : (
        <>
          {icon && <span>{icon}</span>}
          <span className="relative z-10">{children}</span>
        </>
      )}
    </motion.button>
  );
};

// ============================================================================
// PARALLAX CONTAINER
// ============================================================================
export const ParallaxContainer = ({ children, intensity = 10 }) => {
  const ref = useRef(null);
  const x = useMotionValue(0);
  const y = useMotionValue(0);

  const rotateX = useTransform(y, [-100, 100], [intensity, -intensity]);
  const rotateY = useTransform(x, [-100, 100], [-intensity, intensity]);

  const handleMouse = (event) => {
    if (!ref.current) return;

    const rect = ref.current.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    x.set(event.clientX - centerX);
    y.set(event.clientY - centerY);
  };

  const handleMouseLeave = () => {
    x.set(0);
    y.set(0);
  };

  return (
    <motion.div
      ref={ref}
      onMouseMove={handleMouse}
      onMouseLeave={handleMouseLeave}
      style={{
        rotateX,
        rotateY,
        transformStyle: 'preserve-3d',
      }}
      transition={{ type: 'spring', stiffness: 300, damping: 30 }}
    >
      {children}
    </motion.div>
  );
};

// ============================================================================
// MAGNETIC BUTTON - Follows cursor on hover
// ============================================================================
export const MagneticButton = ({ children, className = '', onClick }) => {
  const ref = useRef(null);
  const x = useMotionValue(0);
  const y = useMotionValue(0);

  const springConfig = { damping: 15, stiffness: 150 };
  const springX = useSpring(x, springConfig);
  const springY = useSpring(y, springConfig);

  const handleMouseMove = (event) => {
    if (!ref.current) return;

    const rect = ref.current.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    const distanceX = event.clientX - centerX;
    const distanceY = event.clientY - centerY;

    x.set(distanceX * 0.3);
    y.set(distanceY * 0.3);
  };

  const handleMouseLeave = () => {
    x.set(0);
    y.set(0);
  };

  return (
    <motion.button
      ref={ref}
      style={{ x: springX, y: springY }}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      onClick={onClick}
      className={className}
    >
      {children}
    </motion.button>
  );
};

// ============================================================================
// LUXURY CARD with 3D tilt effect
// ============================================================================
export const LuxuryCard = ({ children, className = '', onClick, glowColor = 'rgba(200, 16, 46, 0.3)' }) => {
  const ref = useRef(null);
  const [isHovered, setIsHovered] = useState(false);

  const x = useMotionValue(0);
  const y = useMotionValue(0);

  const rotateX = useTransform(y, [-100, 100], [10, -10]);
  const rotateY = useTransform(x, [-100, 100], [-10, 10]);

  const handleMouse = (event) => {
    if (!ref.current) return;

    const rect = ref.current.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    x.set(event.clientX - centerX);
    y.set(event.clientY - centerY);
  };

  const handleMouseLeave = () => {
    x.set(0);
    y.set(0);
    setIsHovered(false);
  };

  return (
    <motion.div
      ref={ref}
      onMouseMove={handleMouse}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={handleMouseLeave}
      onClick={onClick}
      style={{
        rotateX,
        rotateY,
        transformStyle: 'preserve-3d',
      }}
      transition={{ type: 'spring', stiffness: 300, damping: 30 }}
      className={`relative ${className}`}
    >
      {/* Glow effect on hover */}
      <motion.div
        className="absolute inset-0 rounded-xl blur-xl"
        style={{
          background: glowColor,
          opacity: isHovered ? 1 : 0,
        }}
        transition={{ duration: 0.3 }}
      />

      {/* Card content */}
      <div className="relative z-10">
        {children}
      </div>
    </motion.div>
  );
};

// ============================================================================
// ANIMATED NUMBER COUNTER
// ============================================================================
export const AnimatedNumber = ({ value, prefix = '', suffix = '', duration = 1000, decimals = 0 }) => {
  const [displayValue, setDisplayValue] = useState(0);

  useEffect(() => {
    let startTime;
    let animationFrame;

    const animate = (timestamp) => {
      if (!startTime) startTime = timestamp;
      const progress = Math.min((timestamp - startTime) / duration, 1);

      // Easing function for smooth animation
      const easeOutQuart = 1 - Math.pow(1 - progress, 4);
      const current = value * easeOutQuart;

      setDisplayValue(current);

      if (progress < 1) {
        animationFrame = requestAnimationFrame(animate);
      }
    };

    animationFrame = requestAnimationFrame(animate);

    return () => {
      if (animationFrame) cancelAnimationFrame(animationFrame);
    };
  }, [value, duration]);

  return (
    <span>
      {prefix}{displayValue.toFixed(decimals).toLocaleString()}{suffix}
    </span>
  );
};

// ============================================================================
// SHIMMER LOADING SKELETON
// ============================================================================
export const ShimmerSkeleton = ({ width = '100%', height = '20px', className = '' }) => {
  return (
    <div
      className={`relative overflow-hidden rounded-lg bg-gray-200 dark:bg-gray-700 ${className}`}
      style={{ width, height }}
    >
      <motion.div
        className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent"
        animate={{ x: ['-100%', '100%'] }}
        transition={{
          repeat: Infinity,
          duration: 1.5,
          ease: 'linear',
        }}
      />
    </div>
  );
};

// ============================================================================
// PREMIUM TOOLTIP
// ============================================================================
export const PremiumTooltip = ({ children, content, position = 'top' }) => {
  const [isVisible, setIsVisible] = useState(false);

  const positions = {
    top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
    bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
    left: 'right-full top-1/2 -translate-y-1/2 mr-2',
    right: 'left-full top-1/2 -translate-y-1/2 ml-2',
  };

  return (
    <div
      className="relative inline-block"
      onMouseEnter={() => setIsVisible(true)}
      onMouseLeave={() => setIsVisible(false)}
    >
      {children}

      <motion.div
        initial={{ opacity: 0, scale: 0.9 }}
        animate={{
          opacity: isVisible ? 1 : 0,
          scale: isVisible ? 1 : 0.9,
        }}
        transition={{ duration: 0.2 }}
        className={`
          absolute ${positions[position]}
          px-3 py-2 bg-omega-navy text-white text-sm rounded-lg
          shadow-xl whitespace-nowrap z-50
          pointer-events-none
        `}
        style={{ display: isVisible ? 'block' : 'none' }}
      >
        {content}

        {/* Arrow */}
        <div className="absolute w-2 h-2 bg-omega-navy transform rotate-45"
          style={{
            ...(position === 'top' && { bottom: '-4px', left: '50%', marginLeft: '-4px' }),
            ...(position === 'bottom' && { top: '-4px', left: '50%', marginLeft: '-4px' }),
            ...(position === 'left' && { right: '-4px', top: '50%', marginTop: '-4px' }),
            ...(position === 'right' && { left: '-4px', top: '50%', marginTop: '-4px' }),
          }}
        />
      </motion.div>
    </div>
  );
};

// ============================================================================
// CINEMATIC REVEAL ANIMATION
// ============================================================================
export const CinematicReveal = ({ children, delay = 0 }) => {
  return (
    <motion.div
      initial={{ opacity: 0, y: 50, rotateX: -15 }}
      animate={{ opacity: 1, y: 0, rotateX: 0 }}
      transition={{
        duration: 0.8,
        delay,
        ease: [0.25, 0.46, 0.45, 0.94], // Luxury easing
      }}
    >
      {children}
    </motion.div>
  );
};

// ============================================================================
// RIPPLE EFFECT (Material Design inspired, luxury version)
// ============================================================================
export const RippleEffect = ({ children, className = '', onClick }) => {
  const [ripples, setRipples] = useState([]);

  const addRipple = (event) => {
    const button = event.currentTarget;
    const rect = button.getBoundingClientRect();
    const size = Math.max(rect.width, rect.height);
    const x = event.clientX - rect.left - size / 2;
    const y = event.clientY - rect.top - size / 2;

    const newRipple = { x, y, size, id: Date.now() };
    setRipples((prev) => [...prev, newRipple]);

    setTimeout(() => {
      setRipples((prev) => prev.filter((r) => r.id !== newRipple.id));
    }, 600);

    if (onClick) onClick(event);
  };

  return (
    <button
      className={`relative overflow-hidden ${className}`}
      onClick={addRipple}
    >
      {children}

      {ripples.map((ripple) => (
        <motion.span
          key={ripple.id}
          className="absolute bg-white/30 rounded-full pointer-events-none"
          style={{
            left: ripple.x,
            top: ripple.y,
            width: ripple.size,
            height: ripple.size,
          }}
          initial={{ scale: 0, opacity: 1 }}
          animate={{ scale: 4, opacity: 0 }}
          transition={{ duration: 0.6, ease: 'easeOut' }}
        />
      ))}
    </button>
  );
};

export default {
  useHaptic,
  LuxuryButton,
  ParallaxContainer,
  MagneticButton,
  LuxuryCard,
  AnimatedNumber,
  ShimmerSkeleton,
  PremiumTooltip,
  CinematicReveal,
  RippleEffect,
};