← back to Watches
src/components/HeroCarousel.jsx
227 lines
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { FiChevronLeft, FiChevronRight, FiTrendingUp } from 'react-icons/fi';
// Local SVG watch illustrations - authentic Omega dial designs
const watchImages = {
'speedmaster-moonwatch': '/watches/speedmaster.svg',
'speedmaster-alaska': '/watches/speedmaster.svg',
'speedmaster': '/watches/speedmaster.svg',
'seamaster-300': '/watches/seamaster.svg',
'seamaster-ploprof': '/watches/seamaster.svg',
'seamaster-bond': '/watches/seamaster.svg',
'seamaster': '/watches/seamaster.svg',
'constellation': '/watches/constellation.svg',
'deville': '/watches/deville.svg',
'railmaster': '/watches/railmaster.svg',
'default': '/watches/speedmaster.svg'
};
function getWatchImage(watch) {
const model = watch.model?.toLowerCase() || '';
const series = watch.series?.toLowerCase() || '';
if (model.includes('moonwatch')) return watchImages['speedmaster-moonwatch'];
if (model.includes('alaska')) return watchImages['speedmaster-alaska'];
if (model.includes('ploprof')) return watchImages['seamaster-ploprof'];
if (model.includes('bond') || model.includes('diver 300')) return watchImages['seamaster-bond'];
if (model.includes('seamaster 300') || (series.includes('seamaster') && model.includes('300'))) return watchImages['seamaster-300'];
if (series.includes('speedmaster')) return watchImages['speedmaster'];
if (series.includes('seamaster')) return watchImages['seamaster'];
if (series.includes('constellation')) return watchImages['constellation'];
if (series.includes('de ville') || series.includes('deville')) return watchImages['deville'];
if (series.includes('railmaster')) return watchImages['railmaster'];
return watchImages['default'];
}
function HeroCarousel({ watches, onWatchSelect }) {
const [currentIndex, setCurrentIndex] = useState(0);
const [isAutoPlaying, setIsAutoPlaying] = useState(true);
// Get top 5 watches by appreciation
const featuredWatches = React.useMemo(() => {
if (!watches || watches.length === 0) return [];
return [...watches]
.map(watch => {
const first = watch.priceHistory[0]?.price || 1;
const last = watch.priceHistory[watch.priceHistory.length - 1]?.price || first;
const appreciation = ((last - first) / first) * 100;
return { ...watch, appreciation, currentPrice: last };
})
.sort((a, b) => b.appreciation - a.appreciation)
.slice(0, 5);
}, [watches]);
useEffect(() => {
if (!isAutoPlaying || featuredWatches.length === 0) return;
const interval = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % featuredWatches.length);
}, 5000);
return () => clearInterval(interval);
}, [isAutoPlaying, featuredWatches.length]);
const goToPrevious = () => {
setIsAutoPlaying(false);
setCurrentIndex((prev) => (prev - 1 + featuredWatches.length) % featuredWatches.length);
};
const goToNext = () => {
setIsAutoPlaying(false);
setCurrentIndex((prev) => (prev + 1) % featuredWatches.length);
};
if (featuredWatches.length === 0) return null;
const currentWatch = featuredWatches[currentIndex];
return (
<div className="relative h-[500px] md:h-[600px] overflow-hidden bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
{/* Background Pattern */}
<div className="absolute inset-0 opacity-10">
<div className="absolute inset-0" style={{
backgroundImage: `radial-gradient(circle at 25% 25%, rgba(251, 191, 36, 0.1) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(251, 191, 36, 0.05) 0%, transparent 50%)`
}} />
</div>
{/* Gold Accent Lines */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-amber-500 to-transparent opacity-60" />
<div className="absolute bottom-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-amber-500 to-transparent opacity-60" />
</div>
<div className="relative z-10 container mx-auto px-6 h-full flex items-center">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center w-full">
{/* Text Content */}
<AnimatePresence mode="wait">
<motion.div
key={`text-${currentIndex}`}
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50 }}
transition={{ duration: 0.5 }}
className="text-white"
>
<motion.div
className="inline-flex items-center gap-2 px-4 py-2 bg-amber-500/20 border border-amber-500/50 rounded-full mb-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<FiTrendingUp className="text-amber-400" />
<span className="text-amber-400 text-sm font-medium">
+{currentWatch.appreciation.toFixed(0)}% Appreciation
</span>
</motion.div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-4 leading-tight">
<span className="text-transparent bg-clip-text bg-gradient-to-r from-white to-gray-300">
{currentWatch.model}
</span>
</h2>
<p className="text-xl md:text-2xl text-amber-400 font-semibold mb-4">
{currentWatch.series} Collection
</p>
<p className="text-gray-300 text-lg mb-6 max-w-xl leading-relaxed">
{currentWatch.description?.slice(0, 150)}...
</p>
<div className="flex items-center gap-8 mb-8">
<div>
<p className="text-gray-400 text-sm uppercase tracking-wider">Current Value</p>
<p className="text-3xl font-bold text-white">
${currentWatch.currentPrice.toLocaleString()}
</p>
</div>
<div>
<p className="text-gray-400 text-sm uppercase tracking-wider">Year Introduced</p>
<p className="text-3xl font-bold text-white">
{currentWatch.yearIntroduced}
</p>
</div>
</div>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => onWatchSelect(currentWatch)}
className="px-8 py-4 bg-gradient-to-r from-amber-500 to-amber-600 hover:from-amber-600 hover:to-amber-700 text-black font-bold rounded-lg shadow-lg shadow-amber-500/25 transition-all"
>
View Price History
</motion.button>
</motion.div>
</AnimatePresence>
{/* Watch Image */}
<AnimatePresence mode="wait">
<motion.div
key={`image-${currentIndex}`}
initial={{ opacity: 0, scale: 0.8, rotateY: -20 }}
animate={{ opacity: 1, scale: 1, rotateY: 0 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.6 }}
className="hidden lg:flex justify-center items-center"
>
<div className="relative">
{/* Glow Effect */}
<div className="absolute inset-0 bg-amber-500/20 blur-3xl rounded-full transform scale-75" />
{/* Watch Image */}
<div className="relative z-10 w-80 h-80 flex items-center justify-center">
<img
src={getWatchImage(currentWatch)}
alt={currentWatch.model}
className="w-full h-full object-contain drop-shadow-2xl"
onError={(e) => { e.target.src = watchImages.default; }}
/>
</div>
{/* Frame Decoration */}
<div className="absolute -inset-4 border-2 border-amber-500/30 rounded-3xl" />
</div>
</motion.div>
</AnimatePresence>
</div>
</div>
{/* Navigation Arrows */}
<button
onClick={goToPrevious}
className="absolute left-4 top-1/2 -translate-y-1/2 p-3 bg-black/50 hover:bg-amber-500 text-white rounded-full transition-all z-20"
>
<FiChevronLeft size={24} />
</button>
<button
onClick={goToNext}
className="absolute right-4 top-1/2 -translate-y-1/2 p-3 bg-black/50 hover:bg-amber-500 text-white rounded-full transition-all z-20"
>
<FiChevronRight size={24} />
</button>
{/* Dots Navigation */}
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex gap-3 z-20">
{featuredWatches.map((_, index) => (
<button
key={index}
onClick={() => {
setIsAutoPlaying(false);
setCurrentIndex(index);
}}
className={`transition-all ${
index === currentIndex
? 'w-8 h-3 bg-amber-500 rounded-full'
: 'w-3 h-3 bg-white/50 hover:bg-white/80 rounded-full'
}`}
/>
))}
</div>
</div>
);
}
export default HeroCarousel;