feat: Add Magic UI components including DotPattern, RainbowButton, ShineBorder, WordRotate, and BlurIn, along with corresponding Tailwind configuration updates.
This commit is contained in:
@@ -39,7 +39,7 @@ export async function POST(request: NextRequest) {
|
||||
const { analysis } = bodySchema.parse(body)
|
||||
|
||||
console.log(`🔍 Finding opportunities for: ${analysis.productName}`)
|
||||
|
||||
|
||||
// Sort queries by priority
|
||||
const sortedQueries = analysis.dorkQueries
|
||||
.sort((a, b) => {
|
||||
@@ -49,14 +49,14 @@ export async function POST(request: NextRequest) {
|
||||
.slice(0, 15) // Limit to top 15 queries
|
||||
|
||||
const allResults: SearchResult[] = []
|
||||
|
||||
|
||||
// Execute searches
|
||||
for (const query of sortedQueries) {
|
||||
try {
|
||||
console.log(` Searching: ${query.query.substring(0, 60)}...`)
|
||||
const results = await searchGoogle(query.query, 5)
|
||||
allResults.push(...results)
|
||||
|
||||
|
||||
// Small delay to avoid rate limiting
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
} catch (e) {
|
||||
@@ -85,7 +85,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Search error:', error)
|
||||
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to find opportunities' },
|
||||
{ status: 500 }
|
||||
@@ -136,15 +136,15 @@ async function searchDirect(query: string, num: number): Promise<SearchResult[]>
|
||||
|
||||
const html = await response.text()
|
||||
const results: SearchResult[] = []
|
||||
|
||||
|
||||
// Simple regex parsing
|
||||
const resultBlocks = html.match(/<div class="g"[^>]*>([\s\S]*?)<\/div>\s*<\/div>/g) || []
|
||||
|
||||
|
||||
for (const block of resultBlocks.slice(0, num)) {
|
||||
const titleMatch = block.match(/<h3[^>]*>(.*?)<\/h3>/)
|
||||
const linkMatch = block.match(/<a href="([^"]+)"/)
|
||||
const snippetMatch = block.match(/<div class="VwiC3b[^"]*"[^>]*>(.*?)<\/div>/)
|
||||
|
||||
|
||||
if (titleMatch && linkMatch) {
|
||||
results.push({
|
||||
title: titleMatch[1].replace(/<[^>]+>/g, ''),
|
||||
@@ -169,7 +169,7 @@ function getSource(url: string): string {
|
||||
}
|
||||
|
||||
async function analyzeOpportunities(
|
||||
results: SearchResult[],
|
||||
results: SearchResult[],
|
||||
analysis: EnhancedProductAnalysis
|
||||
): Promise<Opportunity[]> {
|
||||
const opportunities: Opportunity[] = []
|
||||
@@ -181,17 +181,17 @@ async function analyzeOpportunities(
|
||||
|
||||
// Calculate relevance score
|
||||
const content = (result.title + ' ' + result.snippet).toLowerCase()
|
||||
|
||||
|
||||
// Match keywords
|
||||
const matchedKeywords = analysis.keywords
|
||||
.filter(k => content.includes(k.term.toLowerCase()))
|
||||
.map(k => k.term)
|
||||
|
||||
|
||||
// Match problems
|
||||
const matchedProblems = analysis.problemsSolved
|
||||
.filter(p => content.includes(p.problem.toLowerCase()))
|
||||
.map(p => p.problem)
|
||||
|
||||
|
||||
// Calculate score
|
||||
const keywordScore = Math.min(matchedKeywords.length * 0.15, 0.6)
|
||||
const problemScore = Math.min(matchedProblems.length * 0.2, 0.4)
|
||||
@@ -210,7 +210,7 @@ async function analyzeOpportunities(
|
||||
}
|
||||
|
||||
// Find matching persona
|
||||
const matchedPersona = analysis.personas.find(p =>
|
||||
const matchedPersona = analysis.personas.find(p =>
|
||||
p.searchBehavior.some(b => content.includes(b.toLowerCase()))
|
||||
)?.name
|
||||
|
||||
|
||||
@@ -24,16 +24,23 @@
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
--radius: 0.5rem;
|
||||
--color-1: 0 100% 63%;
|
||||
--color-2: 270 100% 63%;
|
||||
--color-3: 210 100% 63%;
|
||||
--color-4: 195 100% 63%;
|
||||
--color-5: 90 100% 63%;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
}
|
||||
|
||||
:root {
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
@@ -44,6 +51,7 @@
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
@@ -54,4 +62,4 @@
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import { Montserrat } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import ConvexClientProvider from './ConvexClientProvider'
|
||||
import { ConvexAuthNextjsServerProvider } from "@convex-dev/auth/nextjs/server";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
const montserrat = Montserrat({
|
||||
subsets: ['latin'],
|
||||
weight: ['300', '400', '500', '600', '700'],
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Sanati - Find Product Opportunities',
|
||||
@@ -20,7 +23,7 @@ export default function RootLayout({
|
||||
return (
|
||||
<ConvexAuthNextjsServerProvider>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<body className={montserrat.className}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="dark"
|
||||
|
||||
274
app/page.tsx
274
app/page.tsx
@@ -1,121 +1,165 @@
|
||||
import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { ArrowRight, Search, Zap, Target, Sparkles } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DotPattern } from '@/components/magicui/dot-pattern'
|
||||
import { HeroShader } from '@/components/hero-shader'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export default function LandingPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Header */}
|
||||
<header className="border-b border-border">
|
||||
<div className="container mx-auto max-w-6xl px-4 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
||||
<Search className="h-4 w-4" />
|
||||
</div>
|
||||
<span className="font-semibold text-foreground">Sanati</span>
|
||||
</div>
|
||||
<nav className="flex items-center gap-4">
|
||||
<Link href="/dashboard" className="text-sm text-muted-foreground hover:text-foreground">
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link href="/auth">
|
||||
<Button size="sm">Get Started</Button>
|
||||
</Link>
|
||||
</nav>
|
||||
return (
|
||||
<div className="relative min-h-screen bg-background overflow-hidden font-sans">
|
||||
<DotPattern
|
||||
className={cn(
|
||||
"[mask-image:radial-gradient(1000px_circle_at_center,white,transparent)]",
|
||||
"opacity-50"
|
||||
)}
|
||||
/>
|
||||
|
||||
<HeroShader />
|
||||
|
||||
{/* Header */}
|
||||
<header className="border-b border-border/40 backdrop-blur-md sticky top-0 z-50">
|
||||
<div className="container mx-auto max-w-7xl px-4 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
||||
<Search className="h-4 w-4" />
|
||||
</div>
|
||||
<span className="font-semibold text-foreground tracking-tight">Sanati</span>
|
||||
</div>
|
||||
<nav className="flex items-center gap-4">
|
||||
<Link href="/dashboard" className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors">
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link href="/auth">
|
||||
<Button size="sm" className="font-medium">Get Started</Button>
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Header */}
|
||||
<header className="border-b border-border/40 backdrop-blur-md sticky top-0 z-50">
|
||||
<div className="container mx-auto max-w-7xl px-4 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
||||
<Search className="h-4 w-4" />
|
||||
</div>
|
||||
<span className="font-semibold text-foreground tracking-tight">Sanati</span>
|
||||
</div>
|
||||
<nav className="flex items-center gap-4">
|
||||
<Link href="/dashboard" className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors">
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link href="/auth">
|
||||
<Button size="sm" className="font-medium">Get Started</Button>
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Hero */}
|
||||
<section className="container mx-auto max-w-7xl px-4 min-h-[75vh] flex items-center relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center w-full">
|
||||
<div className="flex flex-col items-start text-left space-y-8">
|
||||
<div className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">
|
||||
<Sparkles className="mr-1 h-3 w-3" />
|
||||
AI-Powered Research
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl font-light tracking-tight sm:text-5xl lg:text-7xl text-foreground">
|
||||
Find Your Next
|
||||
<br />
|
||||
<span className="font-bold">Customers.</span>
|
||||
</h1>
|
||||
|
||||
<p className="max-w-xl text-lg text-muted-foreground font-light leading-relaxed">
|
||||
Sanati analyzes your product and finds people on Reddit, Hacker News, and forums
|
||||
who are actively expressing needs that your solution solves.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 pt-2">
|
||||
<Link href="/auth">
|
||||
<Button size="lg" className="gap-2 px-8 text-base">
|
||||
Start Finding Opportunities
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side is reserved for the shader visualization */}
|
||||
<div className="hidden lg:block h-full min-h-[400px]"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features */}
|
||||
<section className="border-t border-border/40 bg-muted/20 backdrop-blur-sm relative z-10">
|
||||
<div className="container mx-auto max-w-7xl px-4 py-24">
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div className="bg-card text-card-foreground p-6 rounded-xl border h-full w-full flex flex-col items-start text-left">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 mb-4">
|
||||
<Zap className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2">AI Analysis</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
We scrape your website and use GPT-4 to extract features, pain points, and keywords automatically.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-card text-card-foreground p-6 rounded-xl border h-full w-full flex flex-col items-start text-left">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 mb-4">
|
||||
<Search className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2">Smart Dorking</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Our system generates targeted Google dork queries to find high-intent posts across Reddit, HN, and more.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-card text-card-foreground p-6 rounded-xl border h-full w-full flex flex-col items-start text-left">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 mb-4">
|
||||
<Target className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2">Scored Leads</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Each opportunity is scored by relevance and comes with suggested engagement approaches.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="container mx-auto max-w-7xl px-4 py-24 relative z-10">
|
||||
<div className="rounded-3xl border border-border bg-gradient-to-b from-muted/50 to-muted/10 p-12 text-center relative overflow-hidden">
|
||||
<div className="relative z-10">
|
||||
<h2 className="text-3xl font-bold text-foreground mb-4">
|
||||
Ready to find your customers?
|
||||
</h2>
|
||||
<p className="text-muted-foreground mb-8 max-w-lg mx-auto">
|
||||
Stop guessing. Start finding people who are already looking for solutions like yours.
|
||||
</p>
|
||||
<Link href="/auth">
|
||||
<Button size="default" className="px-6">Get Started Free</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DotPattern
|
||||
className={cn(
|
||||
"[mask-image:radial-gradient(400px_circle_at_center,white,transparent)]",
|
||||
"opacity-50"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-border/40 backdrop-blur-sm relative z-10">
|
||||
<div className="container mx-auto max-w-7xl px-4 py-8">
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
Sanati — Built for indie hackers and founders
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Hero */}
|
||||
<section className="container mx-auto max-w-6xl px-4 py-24 lg:py-32">
|
||||
<div className="flex flex-col items-center text-center space-y-8">
|
||||
<Badge variant="secondary" className="px-4 py-1.5">
|
||||
<Sparkles className="mr-1 h-3 w-3" />
|
||||
AI-Powered Research
|
||||
</Badge>
|
||||
|
||||
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl text-foreground">
|
||||
Find Your Next Customers
|
||||
<br />
|
||||
<span className="text-muted-foreground">Before They Know They Need You</span>
|
||||
</h1>
|
||||
|
||||
<p className="max-w-2xl text-lg text-muted-foreground">
|
||||
Sanati analyzes your product and finds people on Reddit, Hacker News, and forums
|
||||
who are actively expressing needs that your solution solves.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/auth">
|
||||
<Button size="lg" className="gap-2">
|
||||
Start Finding Opportunities
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features */}
|
||||
<section className="border-t border-border bg-muted/50">
|
||||
<div className="container mx-auto max-w-6xl px-4 py-24">
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div className="space-y-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Zap className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground">AI Analysis</h3>
|
||||
<p className="text-muted-foreground">
|
||||
We scrape your website and use GPT-4 to extract features, pain points, and keywords automatically.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Search className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground">Smart Dorking</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Our system generates targeted Google dork queries to find high-intent posts across Reddit, HN, and more.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Target className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground">Scored Leads</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Each opportunity is scored by relevance and comes with suggested engagement approaches.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="container mx-auto max-w-6xl px-4 py-24">
|
||||
<div className="rounded-2xl border border-border bg-muted/50 p-12 text-center">
|
||||
<h2 className="text-3xl font-bold text-foreground mb-4">
|
||||
Ready to find your customers?
|
||||
</h2>
|
||||
<p className="text-muted-foreground mb-8 max-w-lg mx-auto">
|
||||
Stop guessing. Start finding people who are already looking for solutions like yours.
|
||||
</p>
|
||||
<Link href="/auth">
|
||||
<Button size="lg">Get Started Free</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-border">
|
||||
<div className="container mx-auto max-w-6xl px-4 py-8">
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
Sanati — Built for indie hackers and founders
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user