This commit is contained in:
Timo Knuth
2026-01-20 15:54:16 +01:00
parent 9fa8045c26
commit 4733e1a1cc
10 changed files with 290 additions and 756 deletions

View File

@@ -264,8 +264,7 @@ function NoiseToSignalVisual() {
<motion.div
animate={{
opacity: phase === 0 ? 1 : 0,
scale: phase === 0 ? 1 : 0.98,
filter: phase === 0 ? 'blur(0px)' : 'blur(8px)'
scale: phase === 0 ? 1 : 0.98
}}
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
className="space-y-3"
@@ -705,110 +704,6 @@ export function Differentiators() {
)
}
// ============================================
// 5. SOCIAL PROOF - Testimonials (Prepared for Beta)
// ============================================
export function SocialProof() {
const testimonials = [
{
quote: "The noise filtering alone saves me 2 hours per week. Finally, monitoring that actually works.",
author: "[Beta User]",
role: "SEO Manager",
company: "[Company]",
useCase: "SEO Monitoring"
},
{
quote: "We catch competitor price changes within minutes. Game-changer for our pricing strategy.",
author: "[Beta User]",
role: "Growth Lead",
company: "[Company]",
useCase: "Competitor Intelligence"
},
{
quote: "Audit-proof history saved us during compliance review. Worth every penny.",
author: "[Beta User]",
role: "Compliance Officer",
company: "[Company]",
useCase: "Policy Tracking"
}
]
return (
<section className="py-32 bg-[hsl(var(--section-bg-2))] relative overflow-hidden">
{/* Background Pattern - Subtle dots for light theme */}
<div className="absolute inset-0 opacity-[0.03]" style={{
backgroundImage: `radial-gradient(hsl(var(--foreground)) 1px, transparent 1px)`,
backgroundSize: '24px 24px'
}} />
<div className="mx-auto max-w-7xl px-6 relative z-10">
{/* Section Header */}
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="text-center mb-20"
>
<motion.h2 variants={fadeInUp} className="text-4xl lg:text-5xl font-display font-bold text-foreground mb-6">
Built for teams who need results,{' '}
<span className="text-[hsl(var(--primary))]">not demos.</span>
</motion.h2>
</motion.div>
{/* Testimonial Cards - Light Theme */}
<div className="grid md:grid-cols-3 gap-8">
{testimonials.map((testimonial, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
whileHover={{ y: -4 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }}
className="relative group h-full"
>
{/* Card Container */}
<div className="h-full flex flex-col rounded-3xl bg-white border border-zinc-200 shadow-sm p-8 hover:shadow-xl transition-all duration-300">
{/* Quote Mark */}
<div className="text-5xl font-display text-zinc-300 leading-none mb-4">
"
</div>
{/* Quote */}
<p className="font-body text-base leading-relaxed mb-8 text-zinc-900 font-medium italic flex-grow">
{testimonial.quote}
</p>
{/* Attribution */}
<div className="flex items-start justify-between mt-auto pt-6 border-t border-zinc-100">
<div>
<p className="font-bold text-zinc-900 text-sm">{testimonial.author}</p>
<p className="text-xs text-zinc-500">{testimonial.role} at {testimonial.company}</p>
</div>
<div className="px-3 py-1 rounded-full bg-zinc-100 border border-zinc-200 text-[10px] font-bold uppercase tracking-wider text-zinc-600">
{testimonial.useCase}
</div>
</div>
</div>
</motion.div>
))}
</div>
{/* Note */}
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="text-center mt-12 text-sm text-white/60"
>
Join our waitlist to become a beta tester and get featured here.
</motion.p>
</div>
</section>
)
}
// ============================================
// 6. FINAL CTA - Get Started
// ============================================
@@ -827,7 +722,7 @@ export function FinalCTA() {
rotate: [0, 180, 360]
}}
transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
className="absolute top-1/4 -left-20 h-[500px] w-[500px] rounded-full bg-[hsl(var(--primary))] blur-[140px]"
className="absolute top-1/4 -left-20 h-[500px] w-[500px] rounded-full bg-[hsl(var(--primary))] blur-[60px]"
/>
<motion.div
animate={{
@@ -836,7 +731,7 @@ export function FinalCTA() {
rotate: [360, 180, 0]
}}
transition={{ duration: 15, repeat: Infinity, ease: "linear", delay: 2 }}
className="absolute bottom-1/4 -right-20 h-[500px] w-[500px] rounded-full bg-[hsl(var(--teal))] blur-[140px]"
className="absolute bottom-1/4 -right-20 h-[500px] w-[500px] rounded-full bg-[hsl(var(--teal))] blur-[60px]"
/>
<div className="mx-auto max-w-4xl px-6 text-center relative z-10">
@@ -868,15 +763,6 @@ export function FinalCTA() {
custom={3}
className="flex flex-wrap items-center justify-center gap-6 text-sm text-muted-foreground"
>
<div className="flex items-center gap-2">
<motion.div
animate={{ scale: [1, 1.2, 1] }}
transition={{ duration: 2, repeat: Infinity }}
className="w-2 h-2 rounded-full bg-green-500"
/>
<span className="font-semibold text-foreground">500+ joined this week</span>
</div>
<span>•</span>
<div className="flex items-center gap-2">
<Star className="h-4 w-4 fill-current text-[hsl(var(--primary))]" />
<span>Early access: <span className="font-semibold text-foreground">50% off for 6 months</span></span>

View File

@@ -0,0 +1,178 @@
'use client'
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Search, Loader2, Globe, AlertCircle, ArrowRight } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
interface PreviewData {
title: string
description: string
favicon: string
url: string
}
export function LiveSerpPreview() {
const [url, setUrl] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [data, setData] = useState<PreviewData | null>(null)
const [error, setError] = useState('')
const handleAnalyze = async (e: React.FormEvent) => {
e.preventDefault()
if (!url) return
setIsLoading(true)
setError('')
setData(null)
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3002'}/api/tools/meta-preview`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url })
})
if (!response.ok) throw new Error('Failed to fetch preview')
const result = await response.json()
setData(result)
} catch (err) {
setError('Could not analyze this URL. Please check if it represents a valid, publicly accessible website.')
} finally {
setIsLoading(false)
}
}
return (
<section className="py-24 bg-gradient-to-b from-background to-[hsl(var(--section-bg-2))] relative overflow-hidden">
{/* Background Gradients */}
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_0%,hsl(var(--primary))_0%,transparent_50%)] opacity-5" />
<div className="mx-auto max-w-4xl px-6 relative z-10">
<div className="text-center mb-12">
<div className="inline-flex items-center gap-2 rounded-full bg-secondary border border-border px-4 py-1.5 text-sm font-medium text-foreground mb-6">
<Search className="h-4 w-4" />
Free Tool
</div>
<h2 className="text-4xl font-display font-bold text-foreground mb-4">
See how Google sees you
</h2>
<p className="text-muted-foreground text-lg">
Enter your URL to get an instant SERP preview.
</p>
</div>
<div className="max-w-xl mx-auto space-y-8">
{/* Input Form */}
<form onSubmit={handleAnalyze} className="relative group">
<div className="absolute -inset-1 bg-gradient-to-r from-[hsl(var(--primary))] to-[hsl(var(--teal))] rounded-xl opacity-20 group-hover:opacity-40 blur transition duration-500" />
<div className="relative flex gap-2 p-2 bg-card border border-border rounded-xl shadow-xl">
<div className="relative flex-1">
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
<Globe className="h-4 w-4" />
</div>
<Input
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="website.com"
className="pl-9 h-12 bg-transparent border-none shadow-none focus-visible:ring-0 text-base"
/>
</div>
<Button
type="submit"
disabled={isLoading || !url}
className="h-12 px-6 bg-[hsl(var(--primary))] hover:bg-[hsl(var(--primary))]/90 text-white font-semibold rounded-lg transition-all"
>
{isLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
'Analyze'
)}
</Button>
</div>
</form>
{/* Error Message */}
<AnimatePresence>
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="flex items-center gap-2 p-4 rounded-lg bg-red-500/10 border border-red-500/20 text-red-500 text-sm"
>
<AlertCircle className="h-4 w-4" />
{error}
</motion.div>
)}
</AnimatePresence>
{/* Result Preview */}
<AnimatePresence mode="wait">
{data && (
<motion.div
key="result"
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.95 }}
className="space-y-6"
>
{/* Google Result Card */}
<div className="p-6 rounded-xl bg-white dark:bg-[#1a1c20] border border-border shadow-2xl">
<div className="flex items-center gap-3 mb-3">
<div className="p-1 rounded-full bg-gray-100 dark:bg-gray-800">
{data.favicon ? (
<img src={data.favicon} alt="Favicon" className="w-6 h-6 object-contain" />
) : (
<Globe className="w-6 h-6 text-gray-400" />
)}
</div>
<div className="flex flex-col">
<span className="text-sm text-[#202124] dark:text-[#dadce0] font-normal leading-tight">
{new URL(data.url).hostname}
</span>
<span className="text-xs text-[#5f6368] dark:text-[#bdc1c6] leading-tight">
{data.url}
</span>
</div>
</div>
<h3 className="text-xl text-[#1a0dab] dark:text-[#8ab4f8] font-normal hover:underline cursor-pointer mb-1 leading-snug break-words">
{data.title}
</h3>
<p className="text-sm text-[#4d5156] dark:text-[#bdc1c6] leading-normal">
{data.description}
</p>
</div>
{/* Upsell / CTA */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="text-center space-y-4"
>
<div className="inline-block p-4 rounded-2xl bg-[hsl(var(--primary))]/5 border border-[hsl(var(--primary))]/20">
<p className="text-sm font-medium text-foreground mb-3">
Want to know when this changes?
</p>
<Button
variant="outline"
className="border-[hsl(var(--primary))] text-[hsl(var(--primary))] hover:bg-[hsl(var(--primary))]/10"
onClick={() => document.getElementById('waitlist-form')?.scrollIntoView({ behavior: 'smooth' })}
>
Get notified on changes
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</section>
)
}

View File

@@ -1,179 +0,0 @@
'use client'
import { motion } from 'framer-motion'
import { useState, useEffect } from 'react'
import { Activity, TrendingUp, Zap, Shield } from 'lucide-react'
function AnimatedNumber({ value, suffix = '' }: { value: number; suffix?: string }) {
const [displayValue, setDisplayValue] = useState(0)
useEffect(() => {
const duration = 2000 // 2 seconds
const steps = 60
const increment = value / steps
const stepDuration = duration / steps
let currentStep = 0
const interval = setInterval(() => {
currentStep++
if (currentStep <= steps) {
setDisplayValue(Math.floor(increment * currentStep))
} else {
setDisplayValue(value)
clearInterval(interval)
}
}, stepDuration)
return () => clearInterval(interval)
}, [value])
return (
<span className="font-mono text-2xl lg:text-3xl font-bold text-[hsl(var(--teal))] tabular-nums">
{displayValue.toLocaleString()}{suffix}
</span>
)
}
function FluctuatingNumber({ base, variance }: { base: number; variance: number }) {
const [value, setValue] = useState(base)
useEffect(() => {
const interval = setInterval(() => {
const fluctuation = (Math.random() - 0.5) * variance
setValue(base + fluctuation)
}, 1500)
return () => clearInterval(interval)
}, [base, variance])
return (
<span className="font-mono text-2xl lg:text-3xl font-bold text-[hsl(var(--teal))] tabular-nums">
{Math.round(value)}ms
</span>
)
}
export function LiveStatsBar() {
const stats = [
{
icon: <Activity className="h-5 w-5" />,
label: 'Checks performed today',
value: 2847,
type: 'counter' as const
},
{
icon: <TrendingUp className="h-5 w-5" />,
label: 'Changes detected this hour',
value: 127,
type: 'counter' as const
},
{
icon: <Shield className="h-5 w-5" />,
label: 'Uptime',
value: '99.9%',
type: 'static' as const
},
{
icon: <Zap className="h-5 w-5" />,
label: 'Avg response time',
value: '< ',
type: 'fluctuating' as const,
base: 42,
variance: 10
}
]
return (
<section className="border-y border-border bg-gradient-to-r from-foreground/95 via-foreground to-foreground/95 dark:from-secondary dark:via-secondary dark:to-secondary py-8 overflow-hidden">
<div className="mx-auto max-w-7xl px-6">
{/* Desktop: Grid */}
<div className="hidden lg:grid lg:grid-cols-4 gap-8">
{stats.map((stat, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }}
className="flex flex-col items-center text-center gap-3"
>
{/* Icon */}
<motion.div
className="flex items-center justify-center w-12 h-12 rounded-full bg-[hsl(var(--teal))]/10 text-[hsl(var(--teal))]"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ duration: 0.2 }}
>
{stat.icon}
</motion.div>
{/* Value */}
<div>
{stat.type === 'counter' && typeof stat.value === 'number' && (
<AnimatedNumber value={stat.value} />
)}
{stat.type === 'static' && (
<span className="font-mono text-2xl lg:text-3xl font-bold text-[hsl(var(--teal))]">
{stat.value}
</span>
)}
{stat.type === 'fluctuating' && stat.base && stat.variance && (
<span className="font-mono text-2xl lg:text-3xl font-bold text-[hsl(var(--teal))]">
{stat.value}<FluctuatingNumber base={stat.base} variance={stat.variance} />
</span>
)}
</div>
{/* Label */}
<p className="text-xs font-medium text-white/90 uppercase tracking-wider">
{stat.label}
</p>
</motion.div>
))}
</div>
{/* Mobile: Horizontal Scroll */}
<div className="lg:hidden overflow-x-auto scrollbar-thin pb-2">
<div className="flex gap-8 min-w-max px-4">
{stats.map((stat, i) => (
<motion.div
key={i}
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }}
className="flex flex-col items-center text-center gap-3 min-w-[160px]"
>
{/* Icon */}
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-[hsl(var(--teal))]/10 text-[hsl(var(--teal))]">
{stat.icon}
</div>
{/* Value */}
<div>
{stat.type === 'counter' && typeof stat.value === 'number' && (
<AnimatedNumber value={stat.value} />
)}
{stat.type === 'static' && (
<span className="font-mono text-2xl font-bold text-[hsl(var(--teal))]">
{stat.value}
</span>
)}
{stat.type === 'fluctuating' && stat.base && stat.variance && (
<span className="font-mono text-2xl font-bold text-[hsl(var(--teal))]">
{stat.value}<FluctuatingNumber base={stat.base} variance={stat.variance} />
</span>
)}
</div>
{/* Label */}
<p className="text-[10px] font-medium text-white/90 uppercase tracking-wider">
{stat.label}
</p>
</motion.div>
))}
</div>
</div>
</div>
</section>
)
}

View File

@@ -1,255 +0,0 @@
'use client'
import { motion } from 'framer-motion'
import { useState } from 'react'
import { TrendingDown, DollarSign } from 'lucide-react'
export function PricingComparison() {
const [monitorCount, setMonitorCount] = useState(50)
// Pricing calculation logic
const calculatePricing = (monitors: number) => {
// Competitors: tiered pricing
let competitorMin, competitorMax
if (monitors <= 10) {
competitorMin = 29
competitorMax = 49
} else if (monitors <= 50) {
competitorMin = 79
competitorMax = 129
} else if (monitors <= 100) {
competitorMin = 129
competitorMax = 199
} else {
competitorMin = 199
competitorMax = 299
}
// Our pricing: simpler, fairer
let ourPrice
if (monitors <= 10) {
ourPrice = 19
} else if (monitors <= 50) {
ourPrice = 49
} else if (monitors <= 100) {
ourPrice = 89
} else {
ourPrice = 149
}
const competitorAvg = (competitorMin + competitorMax) / 2
const savings = competitorAvg - ourPrice
const savingsPercent = Math.round((savings / competitorAvg) * 100)
return {
competitorMin,
competitorMax,
competitorAvg,
ourPrice,
savings,
savingsPercent
}
}
const pricing = calculatePricing(monitorCount)
return (
<section className="py-32 bg-gradient-to-b from-[hsl(var(--section-bg-6))] to-[hsl(var(--section-bg-3))] relative overflow-hidden">
{/* Background Pattern - Enhanced Dot Grid */}
<div className="absolute inset-0 opacity-8">
<div className="absolute inset-0" style={{
backgroundImage: `radial-gradient(circle, hsl(var(--teal)) 1.5px, transparent 1.5px)`,
backgroundSize: '30px 30px'
}} />
</div>
<div className="mx-auto max-w-5xl px-6 relative z-10">
{/* Section Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<div className="inline-flex items-center gap-2 rounded-full bg-[hsl(var(--teal))]/10 border border-[hsl(var(--teal))]/20 px-4 py-1.5 text-sm font-medium text-[hsl(var(--teal))] mb-6">
<DollarSign className="h-4 w-4" />
Fair Pricing
</div>
<h2 className="text-4xl lg:text-5xl font-display font-bold text-foreground mb-6">
See how much you{' '}
<span className="text-[hsl(var(--teal))]">save</span>
</h2>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
Compare our transparent pricing with typical competitors. No hidden fees, no surprises.
</p>
</motion.div>
{/* Interactive Comparison Card */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="rounded-3xl border-2 border-border bg-card p-8 lg:p-12 shadow-2xl"
>
{/* Monitor Count Slider */}
<div className="mb-12">
<div className="flex items-center justify-between mb-4">
<label className="text-sm font-bold text-muted-foreground uppercase tracking-wider">
Number of Monitors
</label>
<motion.div
key={monitorCount}
initial={{ scale: 1.2 }}
animate={{ scale: 1 }}
className="text-4xl font-bold text-foreground font-mono"
>
{monitorCount}
</motion.div>
</div>
{/* Slider */}
<div className="relative">
<input
type="range"
min="5"
max="200"
step="5"
value={monitorCount}
onChange={(e) => setMonitorCount(Number(e.target.value))}
className="w-full h-3 bg-secondary rounded-full appearance-none cursor-pointer
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-6
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[hsl(var(--teal))]
[&::-webkit-slider-thumb]:shadow-lg [&::-webkit-slider-thumb]:cursor-grab
[&::-webkit-slider-thumb]:active:cursor-grabbing [&::-webkit-slider-thumb]:hover:scale-110
[&::-webkit-slider-thumb]:transition-transform
[&::-moz-range-thumb]:w-6 [&::-moz-range-thumb]:h-6
[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-[hsl(var(--teal))]
[&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:shadow-lg
[&::-moz-range-thumb]:cursor-grab [&::-moz-range-thumb]:active:cursor-grabbing"
/>
{/* Tick marks - positioned by percentage based on slider range (5-200) */}
<div className="relative mt-2 h-4">
<span className="absolute text-xs text-muted-foreground" style={{ left: '0%', transform: 'translateX(0)' }}>5</span>
<span className="absolute text-xs text-muted-foreground" style={{ left: `${((50 - 5) / (200 - 5)) * 100}%`, transform: 'translateX(-50%)' }}>50</span>
<span className="absolute text-xs text-muted-foreground" style={{ left: `${((100 - 5) / (200 - 5)) * 100}%`, transform: 'translateX(-50%)' }}>100</span>
<span className="absolute text-xs text-muted-foreground" style={{ left: '100%', transform: 'translateX(-100%)' }}>200</span>
</div>
</div>
</div>
{/* Price Comparison Bars */}
<div className="grid lg:grid-cols-2 gap-8 mb-8">
{/* Competitors */}
<motion.div
layout
className="space-y-4"
>
<div className="flex items-center justify-between">
<span className="text-sm font-bold text-muted-foreground uppercase tracking-wider">
Typical Competitors
</span>
</div>
{/* Bar */}
<motion.div
className="relative h-24 rounded-2xl bg-gradient-to-r from-red-500/10 to-red-500/20 border-2 border-red-500/30 overflow-hidden"
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<motion.div
className="absolute inset-0 bg-gradient-to-r from-red-500/20 to-red-500/40"
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
style={{ transformOrigin: 'left' }}
/>
<div className="relative h-full flex items-center justify-center">
<div className="text-center">
<motion.div
key={`comp-${pricing.competitorMin}-${pricing.competitorMax}`}
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className="text-4xl font-bold text-red-700 font-mono"
>
${pricing.competitorMin}-{pricing.competitorMax}
</motion.div>
<div className="text-xs font-medium text-red-600">per month</div>
</div>
</div>
</motion.div>
</motion.div>
{/* Us */}
<motion.div
layout
className="space-y-4"
>
<div className="flex items-center justify-between">
<span className="text-sm font-bold text-[hsl(var(--teal))] uppercase tracking-wider">
Our Pricing
</span>
</div>
{/* Bar */}
<motion.div
className="relative h-24 rounded-2xl bg-gradient-to-r from-[hsl(var(--teal))]/10 to-[hsl(var(--teal))]/20 border-2 border-[hsl(var(--teal))]/30 overflow-hidden"
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<motion.div
className="absolute inset-0 bg-gradient-to-r from-[hsl(var(--teal))]/20 to-[hsl(var(--teal))]/40"
initial={{ scaleX: 0 }}
animate={{ scaleX: pricing.ourPrice / pricing.competitorMax }}
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
style={{ transformOrigin: 'left' }}
/>
<div className="relative h-full flex items-center justify-center">
<div className="text-center">
<motion.div
key={`our-${pricing.ourPrice}`}
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className="text-5xl font-bold text-[hsl(var(--teal))] font-mono"
>
${pricing.ourPrice}
</motion.div>
<div className="text-xs font-medium text-[hsl(var(--teal))]">per month</div>
</div>
</div>
</motion.div>
</motion.div>
</div>
{/* Savings Badge */}
<motion.div
key={`savings-${pricing.savings}`}
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className="flex items-center justify-center gap-4 p-6 rounded-2xl bg-gradient-to-r from-[hsl(var(--primary))]/10 via-[hsl(var(--teal))]/10 to-[hsl(var(--burgundy))]/10 border-2 border-[hsl(var(--teal))]/30"
>
<TrendingDown className="h-8 w-8 text-[hsl(var(--teal))]" />
<div className="text-center">
<div className="text-sm font-medium text-muted-foreground">You save</div>
<div className="flex items-baseline gap-2">
<span className="text-4xl font-bold text-foreground">
${Math.round(pricing.savings)}
</span>
<span className="text-xl text-muted-foreground">/month</span>
<span className="ml-2 px-3 py-1 rounded-full bg-[hsl(var(--teal))]/20 text-sm font-bold text-[hsl(var(--teal))]">
{pricing.savingsPercent}% off
</span>
</div>
</div>
</motion.div>
{/* Fine Print */}
<p className="mt-6 text-center text-xs text-muted-foreground">
* Based on average pricing from Visualping, Distill.io, and similar competitors as of Jan 2026
</p>
</motion.div>
</div>
</section>
)
}

View File

@@ -1,8 +1,8 @@
'use client'
import { motion, AnimatePresence } from 'framer-motion'
import { useState, useEffect } from 'react'
import { Check, ArrowRight, Loader2, Sparkles } from 'lucide-react'
import { useState } from 'react'
import { Check, ArrowRight, Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
export function WaitlistForm() {
@@ -10,7 +10,6 @@ export function WaitlistForm() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
const [error, setError] = useState('')
const [queuePosition, setQueuePosition] = useState(0)
const [confetti, setConfetti] = useState<Array<{ id: number; x: number; y: number; rotation: number; color: string }>>([])
const validateEmail = (email: string) => {
@@ -62,7 +61,6 @@ export function WaitlistForm() {
const data = await response.json()
if (data.success) {
setQueuePosition(data.position || Math.floor(Math.random() * 500) + 430)
setIsSubmitting(false)
setIsSuccess(true)
triggerConfetti()
@@ -154,24 +152,6 @@ export function WaitlistForm() {
Check your inbox for confirmation
</motion.p>
{/* Queue Position */}
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.5, type: 'spring' }}
className="inline-flex items-center gap-3 rounded-full bg-gradient-to-r from-[hsl(var(--primary))]/10 to-[hsl(var(--teal))]/10 border border-[hsl(var(--teal))]/30 px-6 py-3"
>
<Sparkles className="h-5 w-5 text-[hsl(var(--primary))]" />
<div className="text-left">
<div className="text-xs font-medium text-muted-foreground">
Your position
</div>
<div className="text-2xl font-bold text-foreground">
#{queuePosition}
</div>
</div>
</motion.div>
{/* Bonus Badge */}
<motion.div
initial={{ opacity: 0, y: 10 }}