"use client"; import Link from "next/link"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { appConfig } from '@/config/app.config'; import { toast } from "sonner"; // Import shared components import { Connector } from "@/components/shared/layout/curvy-rect"; import HeroFlame from "@/components/shared/effects/flame/hero-flame"; import AsciiExplosion from "@/components/shared/effects/flame/ascii-explosion"; import { HeaderProvider } from "@/components/shared/header/HeaderContext"; // Import hero section components import HomeHeroBackground from "@/components/app/(home)/sections/hero/Background/Background"; import { BackgroundOuterPiece } from "@/components/app/(home)/sections/hero/Background/BackgroundOuterPiece"; import HomeHeroBadge from "@/components/app/(home)/sections/hero/Badge/Badge"; import HomeHeroPixi from "@/components/app/(home)/sections/hero/Pixi/Pixi"; import HomeHeroTitle from "@/components/app/(home)/sections/hero/Title/Title"; import HeroInputSubmitButton from "@/components/app/(home)/sections/hero-input/Button/Button"; // import Globe from "@/components/app/(home)/sections/hero-input/_svg/Globe"; // Import header components import HeaderBrandKit from "@/components/shared/header/BrandKit/BrandKit"; import HeaderWrapper from "@/components/shared/header/Wrapper/Wrapper"; import HeaderDropdownWrapper from "@/components/shared/header/Dropdown/Wrapper/Wrapper"; import GithubIcon from "@/components/shared/header/Github/_svg/GithubIcon"; import ButtonUI from "@/components/ui/shadcn/button" interface SearchResult { url: string; title: string; description: string; screenshot: string | null; markdown: string; } export default function HomePage() { const [url, setUrl] = useState(""); const [selectedStyle, setSelectedStyle] = useState("1"); const [selectedModel, setSelectedModel] = useState(appConfig.ai.defaultModel); const [isValidUrl, setIsValidUrl] = useState(false); const [showSearchTiles, setShowSearchTiles] = useState(false); const [searchResults, setSearchResults] = useState([]); const [isSearching, setIsSearching] = useState(false); const [hasSearched, setHasSearched] = useState(false); const [isFadingOut, setIsFadingOut] = useState(false); const [showSelectMessage, setShowSelectMessage] = useState(false); const [showInstructionsForIndex, setShowInstructionsForIndex] = useState(null); const [additionalInstructions, setAdditionalInstructions] = useState(''); const router = useRouter(); // Simple URL validation const validateUrl = (urlString: string) => { if (!urlString) return false; // Basic URL pattern - accepts domains with or without protocol const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/; return urlPattern.test(urlString.toLowerCase()); }; // Check if input is a URL (contains a dot) const isURL = (str: string): boolean => { const urlPattern = /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/.*)?$/; return urlPattern.test(str.trim()); }; const styles = [ { id: "1", name: "Glassmorphism", description: "Frosted glass effect" }, { id: "2", name: "Neumorphism", description: "Soft 3D shadows" }, { id: "3", name: "Brutalism", description: "Bold and raw" }, { id: "4", name: "Minimalist", description: "Clean and simple" }, { id: "5", name: "Dark Mode", description: "Dark theme design" }, { id: "6", name: "Gradient Rich", description: "Vibrant gradients" }, { id: "7", name: "3D Depth", description: "Dimensional layers" }, { id: "8", name: "Retro Wave", description: "80s inspired" }, ]; const models = appConfig.ai.availableModels.map(model => ({ id: model, name: appConfig.ai.modelDisplayNames[model] || model, })); const handleSubmit = async (selectedResult?: SearchResult) => { const inputValue = url.trim(); if (!inputValue) { toast.error("Please enter a URL or search term"); return; } // If it's a search result being selected, fade out and redirect if (selectedResult) { setIsFadingOut(true); // Wait for fade animation setTimeout(() => { sessionStorage.setItem('targetUrl', selectedResult.url); sessionStorage.setItem('selectedStyle', selectedStyle); sessionStorage.setItem('selectedModel', selectedModel); sessionStorage.setItem('autoStart', 'true'); if (selectedResult.markdown) { sessionStorage.setItem('siteMarkdown', selectedResult.markdown); } router.push('/generation'); }, 500); return; } // If it's a URL, go straight to generation if (isURL(inputValue)) { sessionStorage.setItem('targetUrl', inputValue); sessionStorage.setItem('selectedStyle', selectedStyle); sessionStorage.setItem('selectedModel', selectedModel); sessionStorage.setItem('autoStart', 'true'); router.push('/generation'); } else { // It's a search term, fade out if results exist, then search if (hasSearched && searchResults.length > 0) { setIsFadingOut(true); setTimeout(async () => { setSearchResults([]); setIsFadingOut(false); setShowSelectMessage(true); // Perform new search await performSearch(inputValue); setHasSearched(true); setShowSearchTiles(true); setShowSelectMessage(false); // Smooth scroll to carousel setTimeout(() => { const carouselSection = document.querySelector('.carousel-section'); if (carouselSection) { carouselSection.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 300); }, 500); } else { // First search, no fade needed setShowSelectMessage(true); setIsSearching(true); setHasSearched(true); setShowSearchTiles(true); // Scroll to carousel area immediately setTimeout(() => { const carouselSection = document.querySelector('.carousel-section'); if (carouselSection) { carouselSection.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 100); await performSearch(inputValue); setShowSelectMessage(false); setIsSearching(false); // Smooth scroll to carousel setTimeout(() => { const carouselSection = document.querySelector('.carousel-section'); if (carouselSection) { carouselSection.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 300); } } }; // Perform search when user types const performSearch = async (searchQuery: string) => { if (!searchQuery.trim() || isURL(searchQuery)) { setSearchResults([]); setShowSearchTiles(false); return; } setIsSearching(true); setShowSearchTiles(true); try { const response = await fetch('/api/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: searchQuery }), }); if (response.ok) { const data = await response.json(); setSearchResults(data.results || []); setShowSearchTiles(true); } } catch (error) { console.error('Search error:', error); } finally { setIsSearching(false); } }; return (
{/* Header/Navigation Section */}
{/* Hero Section */}

Re-imagine any website, in seconds.

e.preventDefault()} > Powered by Firecrawl.
{/* Mini Playground Input */}
{/* Hero Input Component */}
{/* Show different UI when search results are displayed */} {hasSearched && searchResults.length > 0 && !isFadingOut ? ( <> {/* Selection mode icon */} {/* Selection message */}
Select which site to clone from the results below
{/* Search again button */} ) : ( <> {isURL(url) ? ( // Scrape icon for URLs ) : ( // Search icon for search terms )} { const value = e.target.value; setUrl(value); setIsValidUrl(validateUrl(value)); // Reset search state when input changes if (value.trim() === "") { setShowSearchTiles(false); setHasSearched(false); setSearchResults([]); } }} onKeyDown={(e) => { if (e.key === "Enter" && !isSearching) { e.preventDefault(); handleSubmit(); } }} onFocus={() => { if (url.trim() && !isURL(url) && searchResults.length > 0) { setShowSearchTiles(true); } }} />
{ e.preventDefault(); if (!isSearching) { handleSubmit(); } }} className={isSearching ? 'pointer-events-none' : ''} > 0} buttonText={isURL(url) ? 'Scrape Site' : 'Search'} disabled={isSearching} />
)}
{/* Options Section - Only show when valid URL */}
{/* Style Selector */}
{styles.map((style, index) => ( ))}
{/* Model Selector Dropdown and Additional Instructions */}
{/* Model Dropdown */} {/* Additional Instructions */} sessionStorage.setItem('additionalInstructions', e.target.value)} />
{/* Full-width oval carousel section */} {showSearchTiles && hasSearched && (
{isSearching ? ( // Loading state with animated scrolling skeletons
{/* Edge fade overlays */}
{/* Duplicate skeleton tiles for continuous scroll */} {[...Array(10), ...Array(10)].map((_, index) => (
{/* Fake browser UI - 5x bigger */}
{/* Content skeleton - positioned just below nav bar */}
))}
) : searchResults.length > 0 ? ( // Actual results
{/* Edge fade overlays */}
{/* Duplicate results for infinite scroll */} {[...searchResults, ...searchResults].map((result, index) => (
{ if (showInstructionsForIndex === index) { setShowInstructionsForIndex(null); setAdditionalInstructions(''); } }} > {/* Hover overlay with clone buttons or instructions input */}
{showInstructionsForIndex === index ? ( /* Instructions input view - matching main input style exactly */
{/* Input area matching main search */}
{/* Instructions icon */}