Production ready

This commit is contained in:
2026-02-09 22:31:22 +01:00
parent fd6e7c44e1
commit 7814548e11
82 changed files with 3390 additions and 2026 deletions

View File

@@ -0,0 +1,112 @@
'use client'
import { motion } from 'framer-motion'
import { useEffect, useState } from 'react'
export function BackgroundGradient() {
return (
<div className="fixed inset-0 -z-30 overflow-hidden pointer-events-none">
<div
className="absolute inset-x-0 -top-40 -z-30 transform-gpu overflow-hidden blur-3xl sm:-top-80"
aria-hidden="true"
>
<div
className="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[hsl(var(--primary))] to-[hsl(var(--teal))] opacity-20 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]"
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
/>
</div>
<div
className="absolute inset-x-0 top-[calc(100%-13rem)] -z-30 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]"
aria-hidden="true"
>
<div
className="relative left-[calc(50%+3rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-[hsl(var(--burgundy))] to-[hsl(var(--primary))] opacity-20 sm:left-[calc(50%+36rem)] sm:w-[72.1875rem]"
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
/>
</div>
</div>
)
}
export function FloatingElements() {
return (
<div className="fixed inset-0 -z-20 pointer-events-none overflow-hidden">
{[...Array(6)].map((_, i) => (
<motion.div
key={i}
className="absolute h-64 w-64 rounded-full bg-gradient-to-br from-primary/5 to-transparent blur-3xl"
animate={{
x: [Math.random() * 100 + '%', Math.random() * 100 + '%'],
y: [Math.random() * 100 + '%', Math.random() * 100 + '%'],
scale: [1, 1.2, 1],
opacity: [0.3, 0.5, 0.3],
}}
transition={{
duration: 20 + Math.random() * 10,
repeat: Infinity,
ease: "linear",
}}
style={{
left: Math.random() * 100 + '%',
top: Math.random() * 100 + '%',
}}
/>
))}
</div>
)
}
export function InteractiveGrid() {
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setMousePos({ x: e.clientX, y: e.clientY })
}
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
}, [])
return (
<div className="fixed inset-0 -z-30 pointer-events-none">
<div
className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:40px_40px]"
/>
<div
className="absolute inset-0 bg-gradient-to-t from-background via-transparent to-transparent"
/>
<motion.div
className="absolute inset-0 bg-[radial-gradient(600px_at_var(--x)_var(--y),hsl(var(--primary)/0.08),transparent_80%)]"
style={{
// @ts-ignore
'--x': mousePos.x + 'px',
'--y': mousePos.y + 'px',
}}
/>
</div>
)
}
export function GlowEffect() {
return (
<div className="fixed inset-0 -z-20 pointer-events-none">
<div className="absolute top-0 left-1/4 w-96 h-96 bg-primary/10 rounded-full blur-[120px] mix-blend-screen" />
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-teal/10 rounded-full blur-[120px] mix-blend-screen" />
</div>
)
}
export function SectionDivider() {
return (
<div className="relative h-px w-full">
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-border to-transparent" />
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-primary/20 to-transparent blur-sm" />
</div>
)
}

View File

@@ -1,11 +1,25 @@
'use client'
import { motion } from 'framer-motion'
import { useState, useEffect } from 'react'
import { useState, useEffect, useMemo } from 'react'
import { Bell, ArrowDown } from 'lucide-react'
function resolveHsl(cssVar: string): string {
if (typeof window === 'undefined') return 'transparent'
const value = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim()
return value ? `hsl(${value})` : 'transparent'
}
export function CompetitorDemoVisual() {
const [phase, setPhase] = useState(0)
const [colors, setColors] = useState({ burgundy: '#993350', border: '#27272a' })
useEffect(() => {
setColors({
burgundy: resolveHsl('--burgundy'),
border: resolveHsl('--border'),
})
}, [])
useEffect(() => {
const interval = setInterval(() => {
@@ -15,7 +29,7 @@ export function CompetitorDemoVisual() {
}, [])
return (
<div className="relative h-full min-h-[200px] bg-gradient-to-br from-background via-background to-[hsl(var(--primary))]/5 rounded-xl p-4 overflow-hidden">
<div className="relative h-full bg-gradient-to-br from-background via-background to-[hsl(var(--primary))]/5 rounded-xl p-4 overflow-hidden">
{/* Browser Header */}
<div className="mb-3 flex items-center gap-2 px-2 py-1.5 rounded-md bg-secondary/50 border border-border">
<div className="flex gap-1">
@@ -36,9 +50,9 @@ export function CompetitorDemoVisual() {
<motion.div
className="p-4 rounded-xl border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 relative overflow-hidden shadow-xl"
animate={{
borderColor: phase === 1 ? 'hsl(var(--burgundy))' : '#27272a',
borderColor: phase === 1 ? colors.burgundy : colors.border,
boxShadow: phase === 1
? '0 0 20px hsl(var(--burgundy) / 0.2)'
? `0 0 20px ${colors.burgundy}33`
: '0 1px 3px rgba(0,0,0,0.5)'
}}
transition={{ duration: 0.5 }}
@@ -67,7 +81,7 @@ export function CompetitorDemoVisual() {
className="text-3xl font-bold"
animate={{
textDecoration: phase === 1 ? 'line-through' : 'none',
color: phase === 1 ? 'hsl(var(--burgundy))' : '#f4f4f5'
color: phase === 1 ? colors.burgundy : '#f4f4f5'
}}
>
$99

View File

@@ -6,14 +6,15 @@ import { Button } from '@/components/ui/button'
import {
Check, ArrowRight, Shield, Search, FileCheck, TrendingUp,
Target, Filter, Bell, Eye, Slack, Webhook, History,
Zap, Lock, ChevronRight, Star
Zap, Lock, ChevronRight, Star, Accessibility
} from 'lucide-react'
import { useState, useEffect } from 'react'
import { SEODemoVisual } from './SEODemoVisual'
import { CompetitorDemoVisual } from './CompetitorDemoVisual'
import { PolicyDemoVisual } from './PolicyDemoVisual'
import { WaitlistForm } from './WaitlistForm'
import { MagneticButton, SectionDivider } from './MagneticElements'
import { MagneticButton } from './MagneticElements'
import { BackgroundGradient, FloatingElements, InteractiveGrid, GlowEffect, SectionDivider } from './BackgroundEffects'
// Animation Variants
const fadeInUp: Variants = {
@@ -30,21 +31,12 @@ const fadeInUp: Variants = {
})
}
const scaleIn: Variants = {
hidden: { opacity: 0, scale: 0.95 },
visible: {
opacity: 1,
scale: 1,
transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] }
}
}
// ============================================
// 1. HERO SECTION - "Track competitor changes without the noise"
// 1. HERO SECTION
// ============================================
export function HeroSection() {
return (
<section id="hero" className="relative overflow-hidden pt-32 pb-24 lg:pt-40 lg:pb-32 bg-[hsl(var(--section-bg-1))]">
<section id="hero" className="relative overflow-hidden pt-32 pb-24 lg:pt-40 lg:pb-32 gradient-velvet">
{/* Background Elements */}
<div className="absolute inset-0 grain-texture" />
<div className="absolute right-0 top-20 -z-10 h-[600px] w-[600px] rounded-full bg-[hsl(var(--primary))] opacity-8 blur-[120px]" />
@@ -65,7 +57,7 @@ export function HeroSection() {
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[hsl(var(--teal))] opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-[hsl(var(--teal))]"></span>
</span>
For SEO & Growth Teams
Website Change Monitor for SEO & Growth Teams
</div>
</motion.div>
@@ -75,8 +67,8 @@ export function HeroSection() {
custom={1}
className="text-5xl lg:text-7xl font-display font-bold leading-[1.08] tracking-tight text-foreground"
>
Track competitor changes{' '}
<span className="text-[hsl(var(--primary))]">without the noise.</span>
Monitor website changes &{' '}
<span className="text-[hsl(var(--primary))]">price drops automatically.</span>
</motion.h1>
{/* Subheadline */}
@@ -85,7 +77,7 @@ export function HeroSection() {
custom={2}
className="text-xl lg:text-2xl text-muted-foreground font-body leading-relaxed max-w-2xl"
>
Less noise. More signal. Proof included.
Less noise. More signal. Visual proof included.
</motion.p>
{/* Feature Bullets */}
@@ -156,7 +148,44 @@ export function HeroSection() {
)
}
// Noise → Signal Animation Component - Enhanced
// ============================================
// 1b. TRUST SECTION - "As seen on..."
// ============================================
function TrustSectionDeprecated() {
const logos = [
{ name: 'SEO Clarity', color: 'text-muted-foreground' },
{ name: 'Search Engine Journal', color: 'text-muted-foreground' },
{ name: 'Moz', color: 'text-muted-foreground' },
{ name: 'Ahrefs', color: 'text-muted-foreground' },
{ name: 'Semrush', color: 'text-muted-foreground' }
]
return (
<section className="py-12 border-y border-border/50 bg-secondary/10">
<div className="mx-auto max-w-7xl px-6">
<p className="text-center text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground/80 mb-8">
The Essential Toolkit for Industry Leaders
</p>
<div className="flex flex-wrap justify-center items-center gap-x-12 gap-y-8 opacity-40 grayscale hover:grayscale-0 transition-all duration-700">
{logos.map((logo, i) => (
<motion.div
key={i}
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.8 }}
className={`text-xl font-display font-black tracking-tighter ${logo.color}`}
>
{logo.name}
</motion.div>
))}
</div>
</div>
</section>
)
}
// Noise → Signal Animation Component
function NoiseToSignalVisual() {
const [phase, setPhase] = useState(0)
const [isPaused, setIsPaused] = useState(false)
@@ -167,7 +196,6 @@ function NoiseToSignalVisual() {
const interval = setInterval(() => {
setPhase(p => {
const nextPhase = (p + 1) % 4
// Trigger particles when transitioning from phase 0 to 1
if (p === 0 && nextPhase === 1) {
triggerParticles()
}
@@ -189,7 +217,7 @@ function NoiseToSignalVisual() {
return (
<motion.div
className="relative aspect-[4/3] rounded-3xl border-2 border-border bg-card/50 backdrop-blur-sm shadow-2xl overflow-hidden cursor-pointer group"
className="relative aspect-[4/3] min-h-[320px] rounded-3xl border-2 border-border bg-card/50 backdrop-blur-sm shadow-2xl overflow-hidden cursor-pointer group"
style={{ perspective: '1000px' }}
whileHover={{ rotateY: 2, rotateX: -2, scale: 1.02 }}
transition={{ duration: 0.3 }}
@@ -249,7 +277,6 @@ function NoiseToSignalVisual() {
{/* Content Area */}
<div className="p-8 space-y-4 relative">
{/* Noise Counter */}
<motion.div
className="absolute top-4 left-4 px-3 py-1 rounded-full bg-background/80 backdrop-blur-sm border border-border text-xs font-mono font-semibold"
animate={{
@@ -266,176 +293,80 @@ function NoiseToSignalVisual() {
opacity: phase === 0 ? 1 : 0,
scale: phase === 0 ? 1 : 0.98
}}
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
transition={{ duration: 0.5 }}
className="space-y-3"
>
{/* Cookie Banner - with strikethrough */}
<motion.div
className="flex items-center justify-between p-3 rounded-lg bg-muted/30 border border-border/40 relative overflow-hidden"
animate={{
x: phase >= 1 ? -10 : 0,
opacity: phase >= 1 ? 0.3 : 1
}}
transition={{ duration: 0.4 }}
>
<div className="flex items-center justify-between p-3 rounded-lg bg-muted/30 border border-border/40 relative overflow-hidden">
<span className="text-xs text-muted-foreground">🍪 Cookie Banner</span>
<span className="text-xs text-red-500 font-semibold">
NOISE
</span>
{/* Strikethrough animation */}
{phase >= 1 && (
<motion.div
className="absolute inset-0 border-t-2 border-red-500 top-1/2"
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 0.3 }}
/>
)}
</motion.div>
{/* Enterprise Plan Card */}
<span className="text-xs text-red-500 font-semibold">NOISE</span>
</div>
<div className="p-4 rounded-lg bg-background border border-border">
<p className="text-sm font-semibold text-foreground mb-2">Enterprise Plan</p>
<p className="text-2xl font-bold text-[hsl(var(--burgundy))]">$99/mo</p>
</div>
{/* Timestamp - with strikethrough */}
<motion.div
className="flex items-center justify-between p-3 rounded-lg bg-muted/30 border border-border/40 relative overflow-hidden"
animate={{
x: phase >= 1 ? -10 : 0,
opacity: phase >= 1 ? 0.3 : 1
}}
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="flex items-center justify-between p-3 rounded-lg bg-muted/30 border border-border/40 relative overflow-hidden">
<span className="text-xs text-muted-foreground"> Last updated: 10:23 AM</span>
<span className="text-xs text-red-500 font-semibold">
NOISE
</span>
{/* Strikethrough animation */}
{phase >= 1 && (
<motion.div
className="absolute inset-0 border-t-2 border-red-500 top-1/2"
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 0.3, delay: 0.1 }}
/>
)}
</motion.div>
<span className="text-xs text-red-500 font-semibold">NOISE</span>
</div>
</motion.div>
{/* Phase 1-3: Filtered + Highlighted Signal */}
{/* Phase 1-3: Signal */}
{phase >= 1 && (
<motion.div
initial={{ opacity: 0, scale: 0.85, rotateX: -15 }}
animate={{
opacity: 1,
scale: 1,
rotateX: 0
}}
transition={{
duration: 0.6,
ease: [0.22, 1, 0.36, 1],
scale: { type: 'spring', stiffness: 300, damping: 20 }
}}
initial={{ opacity: 0, scale: 0.85 }}
animate={{ opacity: 1, scale: 1 }}
className="absolute inset-0 flex items-center justify-center p-8"
>
<motion.div
className="w-full p-6 rounded-2xl bg-white dark:bg-zinc-950 border-2 border-[hsl(var(--teal))] dark:border-zinc-800 shadow-2xl relative overflow-hidden"
animate={{
boxShadow: [
'0 20px 60px rgba(20, 184, 166, 0.1)',
'0 20px 80px rgba(20, 184, 166, 0.2)',
'0 20px 60px rgba(20, 184, 166, 0.1)'
]
}}
transition={{ duration: 2, repeat: Infinity }}
>
{/* Animated corner accent */}
<motion.div
className="absolute top-0 right-0 w-20 h-20 bg-[hsl(var(--teal))]/5 rounded-bl-full"
animate={{ scale: [1, 1.1, 1] }}
transition={{ duration: 2, repeat: Infinity }}
/>
<div className="relative z-10">
<div className="flex items-center justify-between mb-2">
<motion.span
className="text-xs font-bold uppercase tracking-wider text-[hsl(var(--teal))] dark:text-[hsl(var(--teal))]"
animate={{ opacity: [1, 0.7, 1] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
SIGNAL DETECTED
</motion.span>
<div className="flex items-center gap-1.5 text-xs font-medium text-[hsl(var(--teal))] dark:text-[hsl(var(--teal))]">
<Filter className="h-3 w-3" />
Filtered
</div>
<div className="w-full p-6 rounded-2xl bg-white dark:bg-zinc-950 border-2 border-[hsl(var(--teal))] shadow-2xl relative">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-bold uppercase tracking-wider text-[hsl(var(--teal))]"> SIGNAL DETECTED</span>
<div className="flex items-center gap-1.5 text-xs text-[hsl(var(--teal))]">
<Filter className="h-3 w-3" /> Filtered
</div>
<p className="text-sm font-semibold text-muted-foreground dark:text-zinc-400 mb-3">Enterprise Plan</p>
<div className="flex items-baseline gap-3">
<p className="text-3xl font-bold text-foreground dark:text-zinc-600/50">$99/mo</p>
</div>
<p className="text-sm font-semibold text-muted-foreground mb-3">Enterprise Plan</p>
<div className="flex items-baseline gap-3">
<p className="text-3xl font-bold text-foreground">$99/mo</p>
{phase >= 2 && (
<motion.p
initial={{ opacity: 0, x: -10, scale: 0.9 }}
animate={{
opacity: phase >= 2 ? 1 : 0,
x: phase >= 2 ? 0 : -10,
scale: phase >= 2 ? 1 : 0.9
}}
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
className="text-lg text-[hsl(var(--burgundy))] dark:text-red-500 font-bold flex items-center gap-1"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
className="text-3xl text-[hsl(var(--burgundy))] font-bold"
>
<span></span>
<motion.span
animate={{ scale: phase === 2 ? [1, 1.1, 1] : 1 }}
transition={{ duration: 0.5 }}
className="text-3xl"
>
$79/mo
</motion.span>
$79/mo
</motion.p>
</div>
{/* Alert badge */}
{phase >= 3 && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-4 inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[hsl(var(--burgundy))]/10 border border-[hsl(var(--burgundy))]/30 dark:bg-red-500/10 dark:border-red-500/20"
>
<Bell className="h-3 w-3 text-[hsl(var(--burgundy))] dark:text-red-500" />
<span className="text-[10px] font-bold text-[hsl(var(--burgundy))] dark:text-red-500 uppercase tracking-wider">
Alert Sent
</span>
</motion.div>
)}
</div>
</motion.div>
{phase >= 3 && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-4 inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[hsl(var(--burgundy))]/10 border border-[hsl(var(--burgundy))]/30"
>
<Bell className="h-3 w-3 text-[hsl(var(--burgundy))]" />
<span className="text-[10px] font-bold text-[hsl(var(--burgundy))] uppercase tracking-wider">Alert Sent</span>
</motion.div>
)}
</div>
</motion.div>
)}
{/* Phase Indicator */}
<div className="absolute bottom-4 right-4 flex gap-1.5">
{[0, 1, 2, 3].map(i => (
<motion.div
<div
key={i}
animate={{
width: phase === i ? 24 : 6,
backgroundColor: phase === i ? 'hsl(var(--teal))' : 'hsl(var(--border))'
}}
transition={{ duration: 0.3 }}
className="h-1.5 rounded-full"
className={`h-1.5 rounded-full transition-all duration-300 ${phase === i ? 'w-6 bg-[hsl(var(--teal))]' : 'w-1.5 bg-border'}`}
/>
))}
</div>
</div >
</motion.div >
</div>
</motion.div>
)
}
// ============================================
// 2. USE CASE SHOWCASE - SEO, Competitor, Policy
// 2. USE CASE SHOWCASE
// ============================================
export function UseCaseShowcase() {
const useCases = [
@@ -445,7 +376,6 @@ export function UseCaseShowcase() {
problem: 'Your rankings drop before you know why.',
example: 'Track when competitors update meta descriptions or add new content sections that outrank you.',
color: 'teal',
gradient: 'from-[hsl(var(--teal))]/10 to-transparent',
demoComponent: <SEODemoVisual />
},
{
@@ -454,7 +384,6 @@ export function UseCaseShowcase() {
problem: 'Competitor launches slip past your radar.',
example: 'Monitor pricing pages, product launches, and promotional campaigns in real-time.',
color: 'primary',
gradient: 'from-[hsl(var(--primary))]/10 to-transparent',
demoComponent: <CompetitorDemoVisual />
},
{
@@ -463,83 +392,55 @@ export function UseCaseShowcase() {
problem: 'Regulatory updates appear without warning.',
example: 'Track policy changes, terms updates, and legal text modifications with audit-proof history.',
color: 'burgundy',
gradient: 'from-[hsl(var(--burgundy))]/10 to-transparent',
demoComponent: <PolicyDemoVisual />
}
]
return (
<section className="py-32 bg-[hsl(var(--section-bg-3))] relative overflow-hidden">
{/* Background Decor - Enhanced Grid */}
<section className="py-32 relative overflow-hidden">
<div className="absolute inset-0 bg-[linear-gradient(to_right,hsl(var(--border))_1px,transparent_1px),linear-gradient(to_bottom,hsl(var(--border))_1px,transparent_1px)] bg-[size:4rem_4rem] opacity-30 [mask-image:radial-gradient(ellipse_80%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
<div className="mx-auto max-w-7xl px-6 relative z-10">
{/* Section Header */}
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
className="text-center mb-20"
>
<motion.div variants={fadeInUp} 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">
<Eye className="h-4 w-4" />
Who This Is For
<div className="text-center mb-20">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
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"
>
<Eye className="h-4 w-4" /> Who This Is For
</motion.div>
<motion.h2 variants={fadeInUp} custom={1} className="text-4xl lg:text-5xl font-display font-bold text-foreground mb-6">
Built for teams who need results,{' '}
<span className="text-muted-foreground">not demos.</span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-4xl lg:text-5xl font-display font-bold text-foreground mb-6"
>
Built for teams who need results, <span className="text-muted-foreground">not demos.</span>
</motion.h2>
</motion.div>
</div>
{/* Use Case Cards - Diagonal Cascade */}
<div className="grid md:grid-cols-3 gap-8 max-w-6xl mx-auto">
{useCases.map((useCase, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 40, rotateX: 10 }}
whileInView={{ opacity: 1, y: 0, rotateX: 0 }}
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.15, duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
whileHover={{ y: -12, scale: 1.02, transition: { duration: 0.3 } }}
className="group relative glass-card rounded-3xl shadow-xl hover:shadow-2xl transition-all overflow-hidden"
transition={{ delay: i * 0.15 }}
className="group relative glass-card rounded-3xl p-8 shadow-xl transition-all"
>
{/* Gradient Background */}
<div className={`absolute inset-0 rounded-3xl bg-gradient-to-br ${useCase.gradient} opacity-0 group-hover:opacity-100 transition-opacity`} />
<div className="relative z-10 p-8 space-y-6">
{/* Icon */}
<motion.div
whileHover={{ rotate: 5, scale: 1.1 }}
transition={{ duration: 0.2 }}
className={`inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-[hsl(var(--${useCase.color}))]/10 text-[hsl(var(--${useCase.color}))] border border-[hsl(var(--${useCase.color}))]/20`}
>
{useCase.icon}
</motion.div>
{/* Title */}
<h3 className="text-2xl font-display font-bold text-foreground">
{useCase.title}
</h3>
{/* Problem Statement */}
<p className="text-sm font-semibold text-muted-foreground">
{useCase.problem}
</p>
{/* Animated Demo Visual */}
<div className="!mt-6 rounded-xl overflow-hidden border border-border/50 shadow-inner">
{useCase.demoComponent}
</div>
{/* Example Scenario */}
<div className="pt-4 border-t border-border">
<p className="text-xs uppercase tracking-wider font-bold text-muted-foreground mb-2">
Example:
</p>
<p className="text-sm text-foreground leading-relaxed">
{useCase.example}
</p>
</div>
<div className={`inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-[hsl(var(--${useCase.color}))]/10 text-[hsl(var(--${useCase.color}))] border border-[hsl(var(--${useCase.color}))]/20 mb-6`}>
{useCase.icon}
</div>
<h3 className="text-2xl font-display font-bold text-foreground mb-4">{useCase.title}</h3>
<p className="text-sm font-semibold text-muted-foreground mb-6">{useCase.problem}</p>
<div className="rounded-xl overflow-hidden border border-border/50 shadow-inner mb-6 h-[280px]">
{useCase.demoComponent}
</div>
<div className="pt-4 border-t border-border">
<p className="text-xs uppercase tracking-wider font-bold text-muted-foreground mb-2">Example:</p>
<p className="text-sm text-foreground leading-relaxed">{useCase.example}</p>
</div>
</motion.div>
))}
@@ -550,7 +451,7 @@ export function UseCaseShowcase() {
}
// ============================================
// 3. HOW IT WORKS - 4 Stage Flow
// 3. HOW IT WORKS
// ============================================
export function HowItWorks() {
const stages = [
@@ -561,69 +462,33 @@ export function HowItWorks() {
]
return (
<section className="py-32 bg-gradient-to-b from-[hsl(var(--section-bg-4))] to-[hsl(var(--section-bg-5))] relative overflow-hidden">
{/* Subtle Diagonal Stripe Decoration */}
<div className="absolute inset-0 opacity-5" style={{ backgroundImage: 'repeating-linear-gradient(45deg, hsl(var(--primary)), hsl(var(--primary)) 2px, transparent 2px, transparent 40px)' }} />
<section className="py-32 relative overflow-hidden">
<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">
How it works
</motion.h2>
<motion.p variants={fadeInUp} custom={1} className="text-xl text-muted-foreground max-w-2xl mx-auto">
Four simple steps to never miss an important change again.
</motion.p>
</motion.div>
<div className="text-center mb-20">
<h2 className="text-4xl lg:text-5xl font-display font-bold text-foreground mb-6">How it works</h2>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">Four simple steps to never miss an important change again.</p>
</div>
{/* Horizontal Flow */}
<div className="relative">
{/* Connecting Line */}
<div className="absolute top-1/2 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-border to-transparent -translate-y-1/2 hidden lg:block" />
<div className="grid lg:grid-cols-4 gap-8 lg:gap-4">
{stages.map((stage, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }}
className="relative flex flex-col items-center text-center group"
>
{/* Large Number Background */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 text-8xl font-display font-bold text-border/20 pointer-events-none">
{String(i + 1).padStart(2, '0')}
</div>
{/* Circle Container */}
<div className="relative z-10 mb-6 flex h-20 w-20 items-center justify-center rounded-full border-2 border-border bg-card shadow-lg group-hover:shadow-2xl group-hover:border-[hsl(var(--primary))] group-hover:bg-[hsl(var(--primary))]/5 transition-all">
<div className="text-[hsl(var(--primary))]">
{stage.icon}
</div>
</div>
{/* Text */}
<h3 className="text-lg font-bold text-foreground mb-2">
{stage.title}
</h3>
<p className="text-sm text-muted-foreground max-w-[200px]">
{stage.desc}
</p>
{/* Arrow (not on last) */}
{i < stages.length - 1 && (
<div className="hidden lg:block absolute top-10 -right-4 text-border">
<ChevronRight className="h-6 w-6" />
</div>
)}
</motion.div>
))}
</div>
<div className="grid lg:grid-cols-4 gap-8">
{stages.map((stage, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1 }}
className="relative flex flex-col items-center text-center group"
>
<div className="absolute -top-4 left-1/2 -translate-x-1/2 text-8xl font-display font-bold text-border/20 pointer-events-none">
{String(i + 1).padStart(2, '0')}
</div>
<div className="relative z-10 mb-6 flex h-20 w-20 items-center justify-center rounded-full border-2 border-border bg-card shadow-lg transition-all group-hover:border-[hsl(var(--primary))]">
<div className="text-[hsl(var(--primary))]">{stage.icon}</div>
</div>
<h3 className="text-lg font-bold text-foreground mb-2">{stage.title}</h3>
<p className="text-sm text-muted-foreground max-w-[200px]">{stage.desc}</p>
</motion.div>
))}
</div>
</div>
</section>
@@ -631,7 +496,7 @@ export function HowItWorks() {
}
// ============================================
// 4. DIFFERENTIATORS - Why We're Better
// 4. DIFFERENTIATORS
// ============================================
export function Differentiators() {
const features = [
@@ -640,31 +505,19 @@ export function Differentiators() {
{ feature: 'Integrations', others: 'Email only', us: 'Slack, Webhooks, Teams', icon: <Slack className="h-5 w-5" /> },
{ feature: 'History & Proof', others: '7-30 days', us: 'Unlimited snapshots', icon: <History className="h-5 w-5" /> },
{ feature: 'Setup Time', others: '15+ min', us: '2 minutes', icon: <Zap className="h-5 w-5" /> },
{ feature: 'Pricing', others: '$29-99/mo', us: 'Fair pay-per-use', icon: <Shield className="h-5 w-5" /> }
{ feature: 'Pricing', others: '$29-99/mo', us: 'Free plan + fair scaling', icon: <Shield className="h-5 w-5" /> }
]
return (
<section className="py-32 bg-[hsl(var(--section-bg-5))] relative overflow-hidden">
{/* Radial Gradient Overlay */}
<div className="absolute inset-0 bg-[radial-gradient(circle_at_30%_50%,hsl(var(--teal))_0%,transparent_50%)] opacity-5" />
<section className="py-32 relative overflow-hidden">
<div className="mx-auto max-w-6xl 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">
Why we're{' '}
<span className="text-[hsl(var(--teal))]">different</span>
</motion.h2>
<motion.p variants={fadeInUp} custom={1} className="text-xl text-muted-foreground max-w-2xl mx-auto">
Not all monitoring tools are created equal. Here's what sets us apart.
</motion.p>
</motion.div>
<div className="text-center mb-20">
<h2 className="text-4xl lg:text-5xl font-display font-bold text-foreground mb-6">
{"Why we're"} <span className="text-[hsl(var(--teal))]">different</span>
</h2>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">{"Not all monitoring tools are created equal. Here's what sets us apart."}</p>
</div>
{/* Feature Cards Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{features.map((item, i) => (
<motion.div
@@ -672,20 +525,13 @@ export function Differentiators() {
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ delay: i * 0.05, duration: 0.4 }}
className="group relative glass-card rounded-2xl p-6 hover:border-[hsl(var(--teal))]/30 hover:shadow-xl transition-all hover:-translate-y-1"
transition={{ delay: i * 0.05 }}
className="glass-card rounded-2xl p-6 hover:border-[hsl(var(--teal))]/30 transition-all"
>
{/* Icon */}
<div className="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-[hsl(var(--teal))]/10 text-[hsl(var(--teal))] mb-4 group-hover:scale-110 transition-transform">
<div className="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-[hsl(var(--teal))]/10 text-[hsl(var(--teal))] mb-4">
{item.icon}
</div>
{/* Feature Name */}
<h3 className="text-lg font-bold text-foreground mb-4">
{item.feature}
</h3>
{/* Comparison */}
<h3 className="text-lg font-bold text-foreground mb-4">{item.feature}</h3>
<div className="space-y-3">
<div className="flex items-start gap-2">
<span className="text-xs uppercase tracking-wider font-bold text-muted-foreground flex-shrink-0 mt-0.5">Others:</span>
@@ -705,69 +551,36 @@ export function Differentiators() {
}
// ============================================
// 6. FINAL CTA - Get Started
// 6. FINAL CTA
// ============================================
export function FinalCTA() {
return (
<section className="relative overflow-hidden py-32">
{/* Animated Gradient Mesh Background - More dramatic */}
<div className="absolute inset-0 bg-gradient-to-br from-[hsl(var(--primary))]/30 via-[hsl(var(--burgundy))]/20 to-[hsl(var(--teal))]/30 opacity-70" />
<div className="absolute inset-0 gradient-velvet opacity-90" />
<div className="absolute inset-0 grain-texture" />
{/* Animated Orbs - Enhanced */}
<motion.div
animate={{
scale: [1, 1.3, 1],
opacity: [0.4, 0.6, 0.4],
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-[60px]"
/>
<motion.div
animate={{
scale: [1, 1.2, 1],
opacity: [0.4, 0.5, 0.4],
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-[60px]"
/>
<div className="mx-auto max-w-4xl px-6 text-center relative z-10">
<motion.div
initial="hidden"
whileInView="visible"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="space-y-8"
>
{/* Headline */}
<motion.h2 variants={fadeInUp} className="text-5xl lg:text-6xl font-display font-bold leading-tight text-foreground">
Stop missing the changes{' '}
<span className="text-[hsl(var(--primary))]">that matter.</span>
</motion.h2>
{/* Subheadline */}
<motion.p variants={fadeInUp} custom={1} className="text-xl lg:text-2xl text-muted-foreground max-w-2xl mx-auto">
<h2 className="text-5xl lg:text-6xl font-display font-bold leading-tight text-foreground">
Stop missing the changes <span className="text-[hsl(var(--primary))]">that matter.</span>
</h2>
<p className="text-xl lg:text-2xl text-muted-foreground max-w-2xl mx-auto">
Join the waitlist and be first to experience monitoring that actually works.
</motion.p>
{/* Waitlist Form */}
<motion.div variants={fadeInUp} custom={2} className="pt-4 max-w-lg mx-auto">
</p>
<div className="pt-4 max-w-lg mx-auto">
<WaitlistForm />
</motion.div>
{/* Social Proof Indicator */}
<motion.div
variants={fadeInUp}
custom={3}
className="flex flex-wrap items-center justify-center gap-6 text-sm text-muted-foreground"
>
</div>
<div className="flex flex-wrap items-center justify-center gap-6 text-sm text-muted-foreground">
<div className="flex items-center gap-2">
<Star className="h-4 w-4 fill-current text-[hsl(var(--primary))]" />
<span>Early access</span>
<span>Join the waitlist for early access</span>
</div>
</motion.div>
</div>
</motion.div>
</div>
</section>

View File

@@ -1,178 +1,182 @@
'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>
'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 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 */}
<div className="min-h-[260px]">
<AnimatePresence mode="wait">
{data && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="text-center space-y-4"
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"
>
<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?
{/* 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 ? (
// Dynamic external favicon URLs are not known at build time.
// eslint-disable-next-line @next/next/no-img-element
<img src={data.favicon} alt="Favicon" className="w-6 h-6 object-contain" width="24" height="24" />
) : (
<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>
<Button
variant="outline"
className="border-[hsl(var(--primary))] text-[hsl(var(--primary))] hover:bg-[hsl(var(--primary))]/10"
onClick={() => document.getElementById('hero')?.scrollIntoView({ behavior: 'smooth' })}
>
Get notified on changes
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</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('hero')?.scrollIntoView({ behavior: 'smooth' })}
>
Get notified on changes
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</div>
</motion.div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
)}
</AnimatePresence>
</div>
</div>
</div>
</section>
)
}
)
}

View File

@@ -4,8 +4,24 @@ import { motion } from 'framer-motion'
import { useState, useEffect } from 'react'
import { FileCheck, Check } from 'lucide-react'
function resolveHsl(cssVar: string): string {
if (typeof window === 'undefined') return 'transparent'
const value = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim()
return value ? `hsl(${value})` : 'transparent'
}
export function PolicyDemoVisual() {
const [phase, setPhase] = useState(0)
const [colors, setColors] = useState({ burgundy: '#993350', teal: '#2e6b6a', border: '#27272a', mutedFg: '#aba49d' })
useEffect(() => {
setColors({
burgundy: resolveHsl('--burgundy'),
teal: resolveHsl('--teal'),
border: resolveHsl('--border'),
mutedFg: resolveHsl('--muted-foreground'),
})
}, [])
useEffect(() => {
const interval = setInterval(() => {
@@ -15,7 +31,7 @@ export function PolicyDemoVisual() {
}, [])
return (
<div className="relative h-full min-h-[200px] bg-gradient-to-br from-background via-background to-[hsl(var(--burgundy))]/5 rounded-xl p-4 overflow-hidden">
<div className="relative h-full bg-gradient-to-br from-background via-background to-[hsl(var(--burgundy))]/5 rounded-xl p-4 overflow-hidden">
{/* Document Header */}
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -25,9 +41,9 @@ export function PolicyDemoVisual() {
<motion.div
className="px-2 py-0.5 rounded-full border text-[9px] font-bold"
animate={{
borderColor: phase === 1 ? 'hsl(var(--teal))' : 'hsl(var(--border))',
backgroundColor: phase === 1 ? 'hsl(var(--teal) / 0.1)' : 'transparent',
color: phase === 1 ? 'hsl(var(--teal))' : 'hsl(var(--muted-foreground))'
borderColor: phase === 1 ? colors.teal : colors.border,
backgroundColor: phase === 1 ? `${colors.teal}1a` : 'rgba(0,0,0,0)',
color: phase === 1 ? colors.teal : colors.mutedFg
}}
transition={{ duration: 0.5 }}
>
@@ -39,9 +55,9 @@ export function PolicyDemoVisual() {
<motion.div
className="space-y-2 p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 overflow-hidden"
animate={{
borderColor: phase === 1 ? 'hsl(var(--burgundy))' : '#27272a',
borderColor: phase === 1 ? colors.burgundy : colors.border,
boxShadow: phase === 1
? '0 0 20px hsl(var(--burgundy) / 0.2)'
? `0 0 20px ${colors.burgundy}33`
: '0 1px 3px rgba(0,0,0,0.2)'
}}
transition={{ duration: 0.5 }}
@@ -63,10 +79,10 @@ export function PolicyDemoVisual() {
>
<motion.p
animate={{
backgroundColor: phase === 1 ? 'hsl(var(--burgundy) / 0.1)' : 'transparent',
backgroundColor: phase === 1 ? `${colors.burgundy}1a` : 'rgba(0,0,0,0)',
paddingLeft: phase === 1 ? '4px' : '0px',
paddingRight: phase === 1 ? '4px' : '0px',
color: phase === 1 ? 'hsl(var(--burgundy))' : 'inherit',
color: phase === 1 ? colors.burgundy : 'inherit',
fontWeight: phase === 1 ? 600 : 400
}}
transition={{ duration: 0.4 }}

View File

@@ -4,8 +4,23 @@ import { motion } from 'framer-motion'
import { useState, useEffect } from 'react'
import { TrendingDown, TrendingUp } from 'lucide-react'
function resolveHsl(cssVar: string): string {
if (typeof window === 'undefined') return 'transparent'
const value = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim()
return value ? `hsl(${value})` : 'transparent'
}
export function SEODemoVisual() {
const [phase, setPhase] = useState(0)
const [colors, setColors] = useState({ burgundy: '#993350', border: '#27272a', background: '#0c0b09' })
useEffect(() => {
setColors({
burgundy: resolveHsl('--burgundy'),
border: resolveHsl('--border'),
background: resolveHsl('--background'),
})
}, [])
useEffect(() => {
const interval = setInterval(() => {
@@ -18,7 +33,7 @@ export function SEODemoVisual() {
const newMeta = "Best enterprise software for teams of all sizes. Try free for 30 days. Now with AI-powered analytics and real-time collaboration."
return (
<div className="relative h-full min-h-[200px] bg-gradient-to-br from-background via-background to-[hsl(var(--teal))]/5 rounded-xl p-4 overflow-hidden">
<div className="relative h-full bg-gradient-to-br from-background via-background to-[hsl(var(--teal))]/5 rounded-xl p-4 overflow-hidden">
{/* SERP Result */}
<div className="space-y-4">
{/* Ranking Indicator */}
@@ -29,8 +44,8 @@ export function SEODemoVisual() {
<motion.div
className="flex items-center gap-1 px-2 py-1 rounded-full bg-background border border-border"
animate={{
borderColor: phase === 0 ? 'hsl(var(--border))' : 'hsl(var(--burgundy))',
backgroundColor: phase === 0 ? 'hsl(var(--background))' : 'hsl(var(--burgundy) / 0.1)'
borderColor: phase === 0 ? colors.border : colors.burgundy,
backgroundColor: phase === 0 ? colors.background : `${colors.burgundy}1a`
}}
transition={{ duration: 0.5 }}
>
@@ -59,10 +74,10 @@ export function SEODemoVisual() {
<motion.div
className="space-y-2 p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950"
animate={{
borderColor: phase === 0 ? '#27272a' : 'hsl(var(--burgundy))',
borderColor: phase === 0 ? colors.border : colors.burgundy,
boxShadow: phase === 0
? '0 1px 3px rgba(0,0,0,0.2)'
: '0 0 20px hsl(var(--burgundy) / 0.2)'
: `0 0 20px ${colors.burgundy}33`
}}
transition={{ duration: 0.5 }}
>
@@ -86,8 +101,8 @@ export function SEODemoVisual() {
>
<motion.span
animate={{
backgroundColor: phase === 1 ? 'hsl(var(--burgundy) / 0.1)' : 'transparent',
color: phase === 1 ? 'hsl(var(--burgundy))' : 'inherit'
backgroundColor: phase === 1 ? `${colors.burgundy}1a` : 'rgba(0,0,0,0)',
color: phase === 1 ? colors.burgundy : 'inherit'
}}
transition={{ duration: 0.5 }}
className="inline-block rounded px-0.5"

View File

@@ -4,6 +4,7 @@ import { motion, AnimatePresence } from 'framer-motion'
import { useState } from 'react'
import { Check, ArrowRight, Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { MagneticButton } from './MagneticElements'
interface WaitlistFormProps {
id?: string
@@ -138,14 +139,14 @@ export function WaitlistForm({ id }: WaitlistFormProps) {
</motion.div>
{/* Success Message */}
<motion.h3
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="mb-3 text-3xl font-display font-bold text-foreground"
>
You're on the list!
</motion.h3>
<motion.h3
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="mb-3 text-3xl font-display font-bold text-foreground"
>
{"You're on the list!"}
</motion.h3>
<motion.p
initial={{ opacity: 0 }}
@@ -203,24 +204,26 @@ export function WaitlistForm({ id }: WaitlistFormProps) {
</motion.div>
{/* Submit Button */}
<Button
type="submit"
disabled={isSubmitting || !email}
size="lg"
className="h-14 rounded-full bg-[hsl(var(--burgundy))] px-8 text-white hover:bg-[hsl(var(--burgundy))]/90 shadow-2xl shadow-[hsl(var(--burgundy))]/30 transition-all hover:scale-105 disabled:hover:scale-100 disabled:opacity-50 disabled:cursor-not-allowed font-bold text-base group whitespace-nowrap"
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
Joining...
</>
) : (
<>
Reserve Your Spot
<ArrowRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform" />
</>
)}
</Button>
<MagneticButton strength={0.3}>
<Button
type="submit"
disabled={isSubmitting || !email}
size="lg"
className="h-14 rounded-full bg-[hsl(var(--burgundy))] px-8 text-white hover:bg-[hsl(var(--burgundy))]/90 shadow-2xl shadow-[hsl(var(--burgundy))]/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed font-bold text-base group whitespace-nowrap"
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
Joining...
</>
) : (
<>
Reserve Your Spot
<ArrowRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform" />
</>
)}
</Button>
</MagneticButton>
</div>
{/* Error Message - Visibility Improved */}