import React, { useEffect, useRef, useState } from 'react'; import TripleStarsIcon from '../icons/TripleStarsIcon'; /* ─── Constants matching BuySmartButton ─── */ const PINK = '#8F0060'; const BLUE = '#0494F7'; const ROTATION_DURATION = 4000; const INTRO_STEP_DURATION = 1500; const SIZES = { small: { expandedWidth: 160, minimizedWidth: 56, height: 56, outerRadius: 18, innerRadius: 15, border: 3, logoSize: 34, fontSize: 12, introFontSize: 13, introIconSize: 32, chevronSize: 12, chevronWidth: 16, textMaxWidth: 110, rotatingBgSize: 350, }, medium: { expandedWidth: 200, minimizedWidth: 72, height: 72, outerRadius: 24, innerRadius: 20, border: 4, logoSize: 44, fontSize: 14, introFontSize: 15, introIconSize: 40, chevronSize: 14, chevronWidth: 20, textMaxWidth: 140, rotatingBgSize: 450, }, big: { expandedWidth: 250, minimizedWidth: 88, height: 88, outerRadius: 30, innerRadius: 25, border: 5, logoSize: 54, fontSize: 16, introFontSize: 17, introIconSize: 48, chevronSize: 16, chevronWidth: 24, textMaxWidth: 180, rotatingBgSize: 550, }, }; /* ─── Chevron icons matching BuySmartButton ─── */ const ChevronRight = ({ size = 14, color = '#888', }: { size?: number; color?: string; }) => ( ); const ChevronLeft = ({ size = 14, color = '#888', }: { size?: number; color?: string; }) => ( ); /* ─── Intro icons ─── */ const InfoIcon = ({ color, size = 40 }: { color: string; size?: number }) => ( ); const DiscountIcon = ({ color, size = 40, }: { color: string; size?: number; }) => ( ); const HeartIcon = ({ color, size = 40 }: { color: string; size?: number }) => ( ); type IntroItem = { color: string; text: string; Icon: React.FC<{ color: string; size?: number }>; }; const INTRO_ITEMS: IntroItem[] = [ { color: '#0494F7', text: 'Product details', Icon: InfoIcon }, { color: '#00AF5B', text: 'Discounts', Icon: DiscountIcon }, { color: '#FF2828', text: 'Your favorites', Icon: HeartIcon }, ]; const hexToRgbString = (hex: string): string => { const clean = hex.replace('#', ''); const r = parseInt(clean.slice(0, 2), 16); const g = parseInt(clean.slice(2, 4), 16); const b = parseInt(clean.slice(4, 6), 16); return `${r}, ${g}, ${b}`; }; /* ─── Props ─── */ type Props = { position: string; image: string | File | null; popupBorderColorLeft: string; popupBorderColorRight: string; size?: 'small' | 'medium' | 'big'; engagementText?: string; expanded?: boolean; skipIntro?: boolean; }; export const BlobVideo = ({ image, position, popupBorderColorLeft, popupBorderColorRight, size = 'medium', engagementText = 'Buy smarter with AI', expanded, skipIntro = false, }: Props) => { const primaryColor = popupBorderColorLeft || PINK; const secondaryColor = popupBorderColorRight || BLUE; const metrics = SIZES[size] || SIZES.medium; const isOnLeftSide = position === 'left'; const [introIndex, setIntroIndex] = useState( skipIntro ? INTRO_ITEMS.length : 0 ); const [introDone, setIntroDone] = useState(skipIntro); const [isExpanded, setIsExpanded] = useState(expanded ?? true); const [isHovered, setIsHovered] = useState(false); useEffect(() => { if (expanded !== undefined) { setIsExpanded(expanded); } }, [expanded]); useEffect(() => { if (skipIntro) { setIntroDone(true); setIntroIndex(INTRO_ITEMS.length); } else { setIntroDone(false); setIntroIndex(0); } }, [skipIntro]); const rotatingRef = useRef(null); const wrapperRef = useRef(null); const rafRef = useRef(null); const startTimeRef = useRef(null); /* ─── Intro animation cycle (auto, runs once) ─── */ useEffect(() => { if (introDone) return; if (introIndex >= INTRO_ITEMS.length) { setIntroDone(true); return; } const timer = setTimeout(() => { setIntroIndex(prev => prev + 1); }, INTRO_STEP_DURATION); return () => clearTimeout(timer); }, [introIndex, introDone]); /* ─── Rotating gradient + dynamic shadow ─── */ useEffect(() => { if (!introDone) return; let running = true; const animate = (timestamp: number) => { if (!running) return; const rotEl = rotatingRef.current; const wrapEl = wrapperRef.current; if (rotEl && wrapEl) { if (!startTimeRef.current) startTimeRef.current = timestamp; const elapsed = timestamp - startTimeRef.current; const deg = ((elapsed % ROTATION_DURATION) / ROTATION_DURATION) * 360; rotEl.style.transform = `rotate(${deg}deg)`; const shadowAngleDeg = deg + 180; const rad = (shadowAngleDeg * Math.PI) / 180; const sx = Math.cos(rad) * 8; const sy = Math.sin(rad) * 10 + 6; const t = (deg % 360) / 360; const blend = (Math.sin(t * Math.PI * 2) + 1) / 2; const pRgb = hexToRgbString(primaryColor).split(', ').map(Number); const sRgb = hexToRgbString(secondaryColor).split(', ').map(Number); const r = Math.round(pRgb[0] * blend + sRgb[0] * (1 - blend)); const g = Math.round(pRgb[1] * blend + sRgb[1] * (1 - blend)); const b = Math.round(pRgb[2] * blend + sRgb[2] * (1 - blend)); wrapEl.style.boxShadow = `${sx.toFixed(1)}px ${sy.toFixed( 1 )}px 24px rgba(${r}, ${g}, ${b}, 0.24), ` + `0px 4px 16px rgba(${r}, ${g}, ${b}, 0.08)`; } rafRef.current = requestAnimationFrame(animate); }; rafRef.current = requestAnimationFrame(animate); return () => { running = false; if (rafRef.current) cancelAnimationFrame(rafRef.current); startTimeRef.current = null; }; }, [introDone, primaryColor, secondaryColor]); /* ─── Derived state ─── */ const showFinal = introDone; const safeIndex = Math.min(introIndex, INTRO_ITEMS.length - 1); const currentItem = INTRO_ITEMS[safeIndex]; const currentRgb = hexToRgbString(currentItem.color); const currentWidth = !showFinal || isExpanded ? metrics.expandedWidth : metrics.minimizedWidth; /* ─── Chevron: only visible on hover, after intro completes ─── */ const showChevron = isHovered && showFinal; const handleToggle = (e: React.MouseEvent) => { e.stopPropagation(); setIsExpanded(prev => !prev); }; // Direction matches BuySmartButton: // Left side: expanded → ChevronLeft (collapse toward left), collapsed → ChevronRight (expand toward right) // Right side: expanded → ChevronRight (collapse toward right), collapsed → ChevronLeft (expand toward left) const chevronIcon = isOnLeftSide ? ( isExpanded ? ( ) : ( ) ) : isExpanded ? ( ) : ( ); const noDragStyle: React.CSSProperties = { userSelect: 'none', WebkitUserSelect: 'none', }; /* ─── Chevron element ─── */ const chevronEl = (
{chevronIcon}
); /* ─── Icon element ─── */ const iconEl = (
{image ? ( logo ) : ( )}
); /* ─── Text element ─── */ const textEl = (
{engagementText}
); return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} style={{ position: 'relative', display: 'grid', gridTemplateColumns: '1fr', gridTemplateRows: '1fr', width: currentWidth, height: metrics.height, borderRadius: metrics.outerRadius, cursor: 'pointer', transition: 'width 0.3s cubic-bezier(0.4, 0, 0.2, 1)', willChange: 'width', ...noDragStyle, }} > {/* ─── Layer 1: Intro animation (auto-plays, fades out after sequence) ─── */}
{/* Intro icon crossfade */}
{INTRO_ITEMS.map((item, i) => (
))}
{/* Intro text slide */}
{INTRO_ITEMS.map((item, i) => ( {item.text} ))}
{/* ─── Layer 2: Final state with rotating gradient + chevron control ─── */}
{/* Rotating conic gradient background */}
{/* Glass morphism inner container with chevron + icon + text */}
{chevronEl} {iconEl} {textEl}
); };