import React, { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { AutocompleteRequest, isLoggedIn } from '../../utils/api'; import { useTutorial } from '../../components/Tutorial/useTutorial'; import { TUTORIAL_KEYS } from '../../components/Tutorial/tutorialSteps'; import TutorialOverlay from '../../components/Tutorial/TutorialOverlay'; import BusinessNameInputModal from '../../components/BusinessNameInputModal'; import LoginPromptModal from '../../components/LoginPromptModal'; import SearchInputForm from '../../components/SearchInputForm'; import { SearchType } from '../../components/SearchInputForm'; // Orb configuration with movement zones to prevent overlap interface OrbConfig { id: string; size: number; initialX: number; initialY: number; color: string; minX: number; maxX: number; minY: number; maxY: number; } const orbConfigs: OrbConfig[] = [ { id: 'orb-1', size: 500, initialX: -10, initialY: -10, color: 'radial-gradient(circle, #C490FF 20%, #AE72F9 50%, rgba(94, 235, 195, 0.4) 100%)', minX: -30, maxX: 35, minY: -30, maxY: 40 }, { id: 'orb-2', size: 480, initialX: 70, initialY: -5, color: 'radial-gradient(circle, #5EEBC3 25%, rgba(174, 114, 249, 0.6) 70%, rgba(139, 92, 246, 0.3) 100%)', minX: 50, maxX: 110, minY: -30, maxY: 40 }, { id: 'orb-3', size: 420, initialX: 5, initialY: 35, color: 'radial-gradient(circle, rgba(148, 251, 224, 0.8) 15%, #AE72F9 55%, rgba(94, 235, 195, 0.3) 100%)', minX: -20, maxX: 45, minY: 20, maxY: 65 }, { id: 'orb-4', size: 400, initialX: 60, initialY: 40, color: 'radial-gradient(circle, rgba(220, 200, 255, 0.95) 10%, rgba(148, 251, 224, 0.85) 45%, rgba(174, 114, 249, 0.5) 100%)', minX: 40, maxX: 100, minY: 25, maxY: 70 }, { id: 'orb-5', size: 520, initialX: -8, initialY: 65, color: 'radial-gradient(circle, #B794F6 30%, rgba(148, 251, 224, 0.6) 65%, rgba(174, 114, 249, 0.3) 100%)', minX: -30, maxX: 40, minY: 50, maxY: 110 }, { id: 'orb-6', size: 450, initialX: 65, initialY: 70, color: 'radial-gradient(circle, rgba(180, 255, 235, 0.95) 15%, rgba(200, 160, 255, 0.8) 50%, rgba(94, 235, 195, 0.45) 100%)', minX: 45, maxX: 110, minY: 55, maxY: 110 }, ]; interface HeroSectionProps { onAnalyze?: (value: string, type?: SearchType) => void; onAutocomplete?: (data: AutocompleteRequest) => void; onManualInput?: (businessName: string, address: string, category: string) => void; onNext?: () => void; error?: string | null; scrollProgress?: number; } const HeroSection: React.FC = ({ onAnalyze, onAutocomplete, onManualInput, onNext, error: externalError, scrollProgress = 0 }) => { const { t } = useTranslation(); const [isManualModalOpen, setIsManualModalOpen] = useState(false); const [isLoginPromptOpen, setIsLoginPromptOpen] = useState(false); const orbRefs = useRef<(HTMLDivElement | null)[]>([]); const animationRefs = useRef([]); const tutorial = useTutorial(); // 첫 방문 시 랜딩 튜토리얼 시작 useEffect(() => { if (!tutorial.hasSeen(TUTORIAL_KEYS.LANDING)) { const timer = setTimeout(() => { tutorial.startTutorial(TUTORIAL_KEYS.LANDING); }, 800); return () => clearTimeout(timer); } }, []); // Orb 랜덤 이동 애니메이션 useEffect(() => { const moveOrb = (orb: HTMLDivElement, index: number) => { const config = orbConfigs[index]; let currentX = config.initialX; let currentY = config.initialY; let targetX = currentX; let targetY = currentY; let scale = 1; let targetScale = 1; let isFirstMove = true; const generateNewTarget = () => { const rangeX = config.maxX - config.minX; const rangeY = config.maxY - config.minY; if (isFirstMove) { const smallRangeX = rangeX * 0.3; const smallRangeY = rangeY * 0.3; targetX = currentX + (Math.random() - 0.5) * smallRangeX; targetY = currentY + (Math.random() - 0.5) * smallRangeY; targetX = Math.max(config.minX, Math.min(config.maxX, targetX)); targetY = Math.max(config.minY, Math.min(config.maxY, targetY)); isFirstMove = false; } else { targetX = config.minX + Math.random() * rangeX; targetY = config.minY + Math.random() * rangeY; } targetScale = 0.9 + Math.random() * 0.2; }; const animate = () => { const speed = 0.003; currentX += (targetX - currentX) * speed; currentY += (targetY - currentY) * speed; scale += (targetScale - scale) * speed; orb.style.left = `${currentX}%`; orb.style.top = `${currentY}%`; orb.style.transform = `scale(${scale})`; const distance = Math.sqrt(Math.pow(targetX - currentX, 2) + Math.pow(targetY - currentY, 2)); if (distance < 1) generateNewTarget(); animationRefs.current[index] = requestAnimationFrame(animate); }; generateNewTarget(); animate(); }; orbRefs.current.forEach((orb, index) => { if (orb) moveOrb(orb, index); }); return () => { animationRefs.current.forEach(id => cancelAnimationFrame(id)); }; }, []); return (
{/* Animated background orbs */}
{orbConfigs.map((config, index) => (
{ orbRefs.current[index] = el; }} className="hero-orb-random" style={{ width: `${config.size}px`, height: `${config.size}px`, background: config.color, left: `${config.initialX}%`, top: `${config.initialY}%`, }} /> ))}
ADO2 { if (isLoggedIn()) { setIsManualModalOpen(true); } else { setIsLoginPromptOpen(true); } }} error={externalError || null} />
{/* Footer Indicator */} {tutorial.isActive && !isManualModalOpen && ( )} {isManualModalOpen && ( setIsManualModalOpen(false)} onSubmit={(businessName, address, category) => { if (tutorial.isActive) tutorial.nextHint(); setIsManualModalOpen(false); onManualInput?.(businessName, address, category); }} /> )} {isLoginPromptOpen && ( setIsLoginPromptOpen(false)} /> )}
); }; export default HeroSection;