|
| 1 | +'use client'; |
| 2 | + |
| 3 | +import Link from 'next/link'; |
| 4 | +import React, { useEffect, useState } from 'react'; |
| 5 | +import { ArrowRight } from 'lucide-react'; |
| 6 | +import { SiFlutter } from 'react-icons/si'; |
| 7 | +import { HeroVisual } from '@/components/hero-visual'; |
| 8 | +import { GridBackground } from '@/components/grid-background'; |
| 9 | +import { BlinkingGridBackground } from '@/components/blinking-grid'; |
| 10 | +import { FloatingOrbs } from '@/components/floating-orbs'; |
| 11 | +import { ScrambleText } from '@/components/scramble-text'; |
| 12 | +import { SCENARIOS } from '@/components/hero-scenarios'; |
| 13 | +import { clsx } from 'clsx'; |
| 14 | +import { twMerge } from 'tailwind-merge'; |
| 15 | +import { motion, AnimatePresence } from 'motion/react'; |
| 16 | +import { useIsMobile } from '@/hooks/use-mobile'; |
| 17 | + |
| 18 | +function cn(...inputs: (string | undefined | null | false)[]) { |
| 19 | + return twMerge(clsx(inputs)); |
| 20 | +} |
| 21 | + |
| 22 | +const CYCLE_DURATION = 5000; |
| 23 | + |
| 24 | +export function HeroSection() { |
| 25 | + const [activeIndex, setActiveIndex] = useState(0); |
| 26 | + const [isLocked, setIsLocked] = useState(false); |
| 27 | + const [mounted, setMounted] = useState(false); |
| 28 | + const isMobile = useIsMobile(); |
| 29 | + |
| 30 | + useEffect(() => { |
| 31 | + setMounted(true); |
| 32 | + }, []); |
| 33 | + |
| 34 | + useEffect(() => { |
| 35 | + if (isLocked) return; |
| 36 | + |
| 37 | + const timer = setInterval(() => { |
| 38 | + setActiveIndex((prev) => (prev + 1) % SCENARIOS.length); |
| 39 | + }, CYCLE_DURATION); |
| 40 | + |
| 41 | + return () => clearInterval(timer); |
| 42 | + }, [isLocked]); |
| 43 | + |
| 44 | + const activeScenario = SCENARIOS[activeIndex]; |
| 45 | + |
| 46 | + return ( |
| 47 | + <section |
| 48 | + className="relative w-full overflow-hidden min-h-screen flex flex-col justify-center" |
| 49 | + onClick={() => setIsLocked(false)} |
| 50 | + > |
| 51 | + {/* Fixed Background Layer */} |
| 52 | + <div className="absolute inset-0 z-0 pointer-events-none"> |
| 53 | + <GridBackground /> |
| 54 | + <div className="absolute inset-0 [mask-image:linear-gradient(to_bottom,black_0%,black_60%,transparent_80%)]"> |
| 55 | + <BlinkingGridBackground /> |
| 56 | + </div> |
| 57 | + </div> |
| 58 | + |
| 59 | + {/* Floating Orbs - only on desktop */} |
| 60 | + <FloatingOrbs /> |
| 61 | + |
| 62 | + {/* Ambient Lighting */} |
| 63 | + <div className="absolute top-[-10%] left-[-10%] w-[70%] h-[90%] bg-blue-300/30 dark:bg-blue-600/20 blur-[150px] dark:blur-[200px] rounded-full pointer-events-none" /> |
| 64 | + <div className="absolute bottom-[-10%] right-[-10%] w-[70%] h-[90%] bg-purple-300/30 dark:bg-purple-600/30 blur-[150px] dark:blur-[200px] rounded-full pointer-events-none" /> |
| 65 | + |
| 66 | + <div className="container relative z-10 px-4 md:px-6 mx-auto pt-32 pb-20"> |
| 67 | + <div className="text-center mb-16"> |
| 68 | + {mounted ? ( |
| 69 | + <motion.div |
| 70 | + initial={{ opacity: 0, y: 20 }} |
| 71 | + animate={{ opacity: 1, y: 0 }} |
| 72 | + className="inline-flex items-center rounded-full border border-blue-500/20 bg-blue-500/10 px-4 py-1.5 text-sm font-medium text-blue-600 dark:text-blue-300 backdrop-blur-xl mb-8 shadow-lg shadow-blue-500/10" |
| 73 | + > |
| 74 | + <span className="flex h-2.5 w-2.5 rounded-full bg-blue-400 mr-2.5 shadow-[0_0_10px_#3b82f6]"></span> |
| 75 | + Beta now available |
| 76 | + </motion.div> |
| 77 | + ) : ( |
| 78 | + <div className="inline-flex items-center rounded-full border border-blue-500/20 bg-blue-500/10 px-4 py-1.5 text-sm font-medium text-blue-600 dark:text-blue-300 backdrop-blur-xl mb-8 shadow-lg shadow-blue-500/10"> |
| 79 | + <span className="flex h-2.5 w-2.5 rounded-full bg-blue-400 mr-2.5 shadow-[0_0_10px_#3b82f6]"></span> |
| 80 | + Beta now available |
| 81 | + </div> |
| 82 | + )} |
| 83 | + |
| 84 | + <h1 className="text-6xl md:text-8xl font-black font-heading mb-8 text-slate-900 dark:text-white drop-shadow-sm px-4"> |
| 85 | + Visualize Your <br className="hidden md:block" /> |
| 86 | + <span className="inline-block py-2 pr-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-indigo-500 to-purple-600 dark:from-blue-400 dark:via-white dark:to-purple-400 uppercase tracking-tight"> |
| 87 | + <ScrambleText /> Flow |
| 88 | + </span> |
| 89 | + </h1> |
| 90 | + |
| 91 | + <p className="max-w-3xl mx-auto text-xl md:text-2xl text-slate-600 dark:text-slate-300 mb-12 leading-relaxed font-medium"> |
| 92 | + A high-performance, fully customizable node-based flow editor for{' '} |
| 93 | + <span className="inline-flex items-baseline font-bold text-blue-600 dark:text-blue-400 uppercase tracking-tight"> |
| 94 | + <SiFlutter className="w-5 h-5 md:w-6 md:h-6 mr-1" /> |
| 95 | + Flutter |
| 96 | + </span> |
| 97 | + . Build workflow editors and process automation tools with |
| 98 | + <span className="font-bold text-blue-600 dark:text-blue-400"> |
| 99 | + {' '} |
| 100 | + fluid precision |
| 101 | + </span> |
| 102 | + . |
| 103 | + </p> |
| 104 | + |
| 105 | + <div className="flex flex-col sm:flex-row gap-6 justify-center items-center"> |
| 106 | + <div className="relative group"> |
| 107 | + <div className="absolute -inset-[2px] rounded-full bg-gradient-to-r from-blue-600 via-purple-500 to-blue-600 opacity-75 blur-sm group-hover:opacity-100 group-hover:blur-md transition-all duration-200 animate-gradient-x" /> |
| 108 | + <div className="absolute -inset-4 rounded-full bg-blue-500/20 blur-xl opacity-0 group-hover:opacity-100 transition-opacity duration-200" /> |
| 109 | + <Link |
| 110 | + href="/docs/getting-started" |
| 111 | + className="relative inline-flex items-center justify-center h-14 px-10 rounded-full bg-blue-600 text-white font-black font-heading text-xl transition-all duration-150 hover:bg-blue-500 hover:scale-105 shadow-xl shadow-blue-500/30 active:scale-95" |
| 112 | + > |
| 113 | + Start Building |
| 114 | + <ArrowRight className="ml-2 h-6 w-6" /> |
| 115 | + </Link> |
| 116 | + </div> |
| 117 | + <a |
| 118 | + href="https://flow.demo.vyuh.tech" |
| 119 | + target="_blank" |
| 120 | + rel="noreferrer" |
| 121 | + className="inline-flex items-center justify-center h-14 px-10 rounded-full border border-slate-300 dark:border-blue-400/40 bg-white/50 dark:bg-white/5 text-slate-900 dark:text-blue-100 font-bold text-xl transition-all duration-150 hover:bg-white/80 dark:hover:bg-white/10 hover:border-blue-400 hover:shadow-lg hover:shadow-blue-500/10 backdrop-blur-xl active:scale-95" |
| 122 | + > |
| 123 | + Live Demo |
| 124 | + </a> |
| 125 | + </div> |
| 126 | + </div> |
| 127 | + |
| 128 | + {/* Interactive Demo Section */} |
| 129 | + <div className="grid grid-cols-1 lg:grid-cols-12 gap-10 lg:gap-16 items-center"> |
| 130 | + <div className="lg:col-span-4 flex flex-col gap-4"> |
| 131 | + {SCENARIOS.map((scenario, index) => { |
| 132 | + const isActive = activeIndex === index; |
| 133 | + return ( |
| 134 | + <button |
| 135 | + key={scenario.id} |
| 136 | + onClick={(e) => { |
| 137 | + e.stopPropagation(); |
| 138 | + setActiveIndex(index); |
| 139 | + setIsLocked(true); |
| 140 | + }} |
| 141 | + className={cn( |
| 142 | + 'relative text-left p-6 rounded-2xl transition-all duration-150 border overflow-hidden backdrop-blur-3xl group', |
| 143 | + isActive |
| 144 | + ? 'border-blue-500/60 shadow-xl shadow-blue-500/10 scale-[1.02] bg-white/[0.08] dark:bg-white/[0.04]' |
| 145 | + : 'border-slate-200/50 dark:border-white/5 bg-white/[0.02] dark:bg-white/[0.01] hover:bg-white/[0.06] hover:border-blue-300/30 text-slate-500' |
| 146 | + )} |
| 147 | + > |
| 148 | + {isActive && !isLocked && !isMobile && ( |
| 149 | + <motion.div |
| 150 | + key={`progress-${index}`} |
| 151 | + initial={{ x: '-100%' }} |
| 152 | + animate={{ x: '0%' }} |
| 153 | + transition={{ |
| 154 | + duration: CYCLE_DURATION / 1000, |
| 155 | + ease: 'linear', |
| 156 | + }} |
| 157 | + className="absolute inset-0 bg-blue-500/20 z-0 origin-left" |
| 158 | + /> |
| 159 | + )} |
| 160 | + {isActive && isLocked && ( |
| 161 | + <div className="absolute inset-0 bg-blue-500/[0.15] z-0" /> |
| 162 | + )} |
| 163 | + <div className="relative z-10"> |
| 164 | + <div |
| 165 | + className={cn( |
| 166 | + 'font-black font-heading text-lg mb-1 transition-all duration-300 tracking-widest uppercase', |
| 167 | + isActive |
| 168 | + ? 'text-blue-700 dark:text-blue-300' |
| 169 | + : 'text-slate-500 dark:text-slate-400 group-hover:text-slate-700 dark:group-hover:text-white' |
| 170 | + )} |
| 171 | + > |
| 172 | + {scenario.label} |
| 173 | + </div> |
| 174 | + <div |
| 175 | + className={cn( |
| 176 | + 'text-sm font-bold leading-relaxed transition-all duration-300', |
| 177 | + isActive |
| 178 | + ? 'text-slate-800 dark:text-blue-100/90' |
| 179 | + : 'text-slate-400 dark:text-slate-500' |
| 180 | + )} |
| 181 | + > |
| 182 | + {scenario.description} |
| 183 | + </div> |
| 184 | + </div> |
| 185 | + </button> |
| 186 | + ); |
| 187 | + })} |
| 188 | + </div> |
| 189 | + <div className="lg:col-span-8 relative h-[400px] lg:h-[600px]"> |
| 190 | + <div className="absolute inset-0 flex items-center justify-center"> |
| 191 | + <AnimatePresence mode="wait"> |
| 192 | + <motion.div |
| 193 | + key={activeIndex} |
| 194 | + initial={{ opacity: 0, scale: 0.95 }} |
| 195 | + animate={{ opacity: 1, scale: 1 }} |
| 196 | + exit={{ opacity: 0, scale: 1.05 }} |
| 197 | + transition={{ duration: 0.5 }} |
| 198 | + className="w-full h-full flex items-center justify-center" |
| 199 | + > |
| 200 | + <HeroVisual |
| 201 | + nodes={activeScenario.nodes} |
| 202 | + connections={activeScenario.connections} |
| 203 | + /> |
| 204 | + </motion.div> |
| 205 | + </AnimatePresence> |
| 206 | + </div> |
| 207 | + </div> |
| 208 | + </div> |
| 209 | + </div> |
| 210 | + </section> |
| 211 | + ); |
| 212 | +} |
0 commit comments