'use client'; import * as React from 'react'; import Link from 'next/link'; import { motion, AnimatePresence } from 'motion/react'; import gsap from 'gsap'; import FloatingChatInput from './FloatingChatInput'; import MessageBubble from './MessageBubble'; import { useChatSession } from '@/lib/use-chat-session'; export default function HeroSection() { const bloomRef = React.useRef(null); const threadRef = React.useRef(null); const outsideTouchYRef = React.useRef(null); // Inline conversation — runs the chat right here on `/` instead of routing // away to the dedicated /chat page. const { messages, isTyping, sendMessage, reset } = useChatSession(); const active = messages.length > 0 || isTyping; // Follow-up input shown inside the chat view (separate from the big hero input). const [chatInput, setChatInput] = React.useState(''); const handleChatSubmit = (e: React.FormEvent) => { e.preventDefault(); const text = chatInput.trim(); if (!text || isTyping) return; setChatInput(''); sendMessage(text); }; // Auto-scroll the *thread container only* (not the window) to the latest // message. Using scrollIntoView here would scroll the whole page and push // the header off-screen. React.useEffect(() => { const el = threadRef.current; if (active && el) { el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }); } }, [messages, isTyping, active]); const handleChatWheel = React.useCallback( (e: React.WheelEvent) => { const el = threadRef.current; if (!active || !el || el.contains(e.target as Node)) return; if (el.scrollHeight <= el.clientHeight) return; el.scrollTop += e.deltaY; e.preventDefault(); }, [active] ); const handleChatTouchStart = React.useCallback( (e: React.TouchEvent) => { const el = threadRef.current; if (!active || !el || el.contains(e.target as Node)) { outsideTouchYRef.current = null; return; } outsideTouchYRef.current = e.touches[0]?.clientY ?? null; }, [active] ); const handleChatTouchMove = React.useCallback((e: React.TouchEvent) => { const el = threadRef.current; const previousY = outsideTouchYRef.current; const currentY = e.touches[0]?.clientY; if (!el || previousY === null || currentY === undefined) return; if (el.scrollHeight <= el.clientHeight) return; el.scrollTop += previousY - currentY; outsideTouchYRef.current = currentY; e.preventDefault(); }, []); // GSAP-driven entrance + breathing on the bloom container. // The two pseudo-element layers keep drifting via CSS, so this composes on // top of the liquid effect rather than replacing it. React.useEffect(() => { const bloom = bloomRef.current; if (!bloom) return; const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (reduced) { // Skip animation; just reveal it in its resting state. gsap.set(bloom, { opacity: 1, clearProps: 'transform,filter' }); return; } const ctx = gsap.context(() => { const tl = gsap.timeline(); // 1. Cinematic bottom-to-top power-up on load. tl.fromTo( bloom, { scaleY: 0.1, scaleX: 0.85, y: 100, opacity: 0, filter: 'blur(20px) brightness(0.4)', }, { scaleY: 1, scaleX: 1, y: 0, opacity: 1, filter: 'blur(0px) brightness(1)', duration: 2.4, ease: 'power4.out', } ); // 2. Organic breathing loop once the entrance settles. tl.to(bloom, { scaleY: 1.02, scaleX: 1.01, y: -8, filter: 'blur(0px) brightness(1.04)', duration: 6, ease: 'sine.inOut', repeat: -1, yoyo: true, }); }, bloom); return () => ctx.revert(); }, []); // Drive CSS variables so the gradient's bright core follows the cursor. // (We set variables, not transforms, so this never fights the GSAP tweens.) React.useEffect(() => { const handleMouseMove = (e: MouseEvent) => { const bloom = bloomRef.current; if (!bloom) return; const x = e.clientX / window.innerWidth - 0.5; const y = e.clientY / window.innerHeight - 0.5; bloom.style.setProperty('--bloom-mx', String(x)); bloom.style.setProperty('--bloom-my', String(y)); }; window.addEventListener('mousemove', handleMouseMove); return () => window.removeEventListener('mousemove', handleMouseMove); }, []); return (
{ outsideTouchYRef.current = null; } : undefined} className={`relative min-h-screen flex flex-col items-center overflow-hidden transition-colors duration-700 ${ active ? 'bg-white' : '' }`} > {/* Radial multi-gradient bloom rising from the bottom-center. Starts hidden; GSAP fades/sweeps it in (see effect above). In chat mode it drops lower to sit just above the input. */}
{/* White wash behind the headline — only while in landing (idle) mode. */} {!active &&
} {active ? ( /* ─────────────── Inline chat view ─────────────── */ {/* Minimalist navbar — controls only, no logo */}
{/* New chat — subtle text+icon button */} {/* Admin — subtle outlined pill */} Admin
{/* Thread — only this scrolls. The inner wrapper is min-h-full with justify-end so a few messages rest just above the input, while a long conversation still scrolls naturally from the top. */}
{messages.map((item) => ( ))} {/* Thinking indicator — minimal, document-style (no avatar/bubble) */} {isTyping && ( Searching your knowledge base )}
{/* Follow-up input pinned to the bottom — dark, matching the home input */}
setChatInput(e.target.value)} disabled={isTyping} placeholder="Ask a follow-up question..." className="flex-1 bg-transparent text-[15px] text-white placeholder-white/40 focus:outline-none py-2 disabled:opacity-60" />

Query Bot prioritizes your custom Q&A over document matches and cites its sources.

) : ( /* ─────────────── Landing (idle) view ─────────────── */ {/* ── Headline content (sits on the white wash) ── */}
{/* Large Headline */} Chat with your{' '} documents {/* Subtitle */} Upload PDFs, Word docs, and spreadsheets. Add custom Q&A. Get fast, grounded answers with sources. {/* Premium Dual CTA Pill Container */}
Build your knowledge base
{/* ── Floating AI Chat Input (sits in the dark, above the bloom) ── */} {/* Spacer so the bloom has room to breathe below the input */}
)}
); }