feat: add feature sections, comparison cards, and CTA components for landing page
This commit is contained in:
47
components/marketing/CTASection.tsx
Normal file
47
components/marketing/CTASection.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { ArrowRight } from '@phosphor-icons/react'
|
||||
import { Button } from '@ciphera-net/ui'
|
||||
import { initiateOAuthFlow } from '@/lib/api/oauth'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function CTASection() {
|
||||
return (
|
||||
<section className="py-20 lg:py-32">
|
||||
<div className="container mx-auto px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="relative overflow-hidden rounded-xl border border-white/[0.06] bg-neutral-900/80 px-6 py-20 sm:px-10 sm:py-24 max-w-6xl mx-auto"
|
||||
>
|
||||
{/* Atmosphere inside card */}
|
||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-brand-orange/5 rounded-full blur-[150px]" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 text-center max-w-2xl mx-auto">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-white mb-4">
|
||||
Start tracking with privacy.
|
||||
</h2>
|
||||
<p className="text-lg text-neutral-300 mb-10">
|
||||
Join thousands of developers who respect their users' privacy while getting the insights they need.
|
||||
</p>
|
||||
<div className="flex flex-row gap-3 justify-center flex-wrap">
|
||||
<Button onClick={() => initiateOAuthFlow()} variant="primary" className="px-6 py-3 shadow-lg shadow-brand-orange/20 gap-2">
|
||||
Try Pulse Free <ArrowRight weight="bold" className="w-4 h-4" />
|
||||
</Button>
|
||||
<Link href="/pricing">
|
||||
<Button variant="secondary" className="px-6 py-3">
|
||||
View Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
111
components/marketing/ComparisonCards.tsx
Normal file
111
components/marketing/ComparisonCards.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import Image from 'next/image'
|
||||
import { Check, X } from '@phosphor-icons/react'
|
||||
|
||||
const pulseFeatures = [
|
||||
{ label: 'No cookies required', has: true },
|
||||
{ label: 'GDPR compliant by default', has: true },
|
||||
{ label: 'No consent banner needed', has: true },
|
||||
{ label: 'Open source client', has: true },
|
||||
{ label: 'Script under 2KB', has: true },
|
||||
{ label: 'Swiss infrastructure', has: true },
|
||||
{ label: 'No cross-site tracking', has: true },
|
||||
{ label: 'Free tier available', has: true },
|
||||
{ label: 'Real-time dashboard', has: true },
|
||||
]
|
||||
|
||||
const gaFeatures = [
|
||||
{ label: 'Requires cookies', has: false },
|
||||
{ label: 'GDPR requires configuration', has: false },
|
||||
{ label: 'Consent banner required', has: false },
|
||||
{ label: 'Closed source', has: false },
|
||||
{ label: 'Script over 45KB', has: false },
|
||||
{ label: 'US infrastructure', has: false },
|
||||
{ label: 'Cross-site tracking', has: false },
|
||||
{ label: 'Free tier available', has: true },
|
||||
{ label: 'Real-time dashboard', has: true },
|
||||
]
|
||||
|
||||
export default function ComparisonCards() {
|
||||
return (
|
||||
<section className="py-20 lg:py-32 border-t border-white/[0.04]">
|
||||
<div className="container mx-auto px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold text-white leading-tight mb-4">
|
||||
How Pulse compares.
|
||||
</h2>
|
||||
<p className="text-lg text-neutral-400 max-w-2xl mx-auto">
|
||||
Privacy-first analytics doesn't mean less insight. See how Pulse stacks up.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
{/* Pulse — highlighted */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.1 }}
|
||||
className="rounded-xl border border-brand-orange/20 bg-neutral-900/80 p-8 relative overflow-hidden"
|
||||
>
|
||||
<div className="absolute top-0 left-0 right-0 h-[3px] bg-brand-orange" />
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Image src="/pulse_icon_no_margins.png" alt="Pulse" width={40} height={40} className="rounded-lg" unoptimized />
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-white">Pulse</h3>
|
||||
<p className="text-xs text-brand-orange">Privacy-first analytics</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="space-y-4">
|
||||
{pulseFeatures.map((f) => (
|
||||
<li key={f.label} className="flex items-center gap-3">
|
||||
<Check weight="bold" className="w-5 h-5 text-brand-orange shrink-0" />
|
||||
<span className="text-neutral-300 text-sm">{f.label}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
|
||||
{/* Google Analytics — muted */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
className="rounded-xl border border-white/[0.08] bg-neutral-900/80 p-8"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 rounded-lg bg-neutral-800 flex items-center justify-center text-lg">
|
||||
📊
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-white">Google Analytics</h3>
|
||||
<p className="text-xs text-neutral-500">Traditional tracking</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="space-y-4">
|
||||
{gaFeatures.map((f) => (
|
||||
<li key={f.label} className="flex items-center gap-3">
|
||||
{f.has ? (
|
||||
<Check weight="bold" className="w-5 h-5 text-green-500 shrink-0" />
|
||||
) : (
|
||||
<X weight="bold" className="w-5 h-5 text-red-500 shrink-0" />
|
||||
)}
|
||||
<span className="text-neutral-400 text-sm">{f.label}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
267
components/marketing/FeatureSections.tsx
Normal file
267
components/marketing/FeatureSections.tsx
Normal file
@@ -0,0 +1,267 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import Image from 'next/image'
|
||||
import { Check } from '@phosphor-icons/react'
|
||||
|
||||
// Section wrapper component for reuse
|
||||
function FeatureSection({
|
||||
id,
|
||||
heading,
|
||||
description,
|
||||
features,
|
||||
mockup,
|
||||
reverse = false,
|
||||
}: {
|
||||
id: string
|
||||
heading: string
|
||||
description: string
|
||||
features: string[]
|
||||
mockup: React.ReactNode
|
||||
reverse?: boolean
|
||||
}) {
|
||||
return (
|
||||
<section id={id} className="container mx-auto px-6 scroll-mt-28">
|
||||
<div className={`grid lg:grid-cols-2 gap-12 lg:gap-20 items-center`}>
|
||||
{/* Text */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className={reverse ? 'lg:order-last' : ''}
|
||||
>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold text-white leading-tight mb-6">
|
||||
{heading}
|
||||
</h2>
|
||||
<p className="text-lg text-neutral-400 leading-relaxed mb-6">
|
||||
{description}
|
||||
</p>
|
||||
<ul className="space-y-3 mb-8">
|
||||
{features.map((item) => (
|
||||
<li key={item} className="flex gap-3 text-neutral-300">
|
||||
<Check weight="bold" className="w-5 h-5 text-brand-orange mt-0.5 shrink-0" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
|
||||
{/* Mockup container */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className={`relative ${reverse ? 'lg:order-first' : ''}`}
|
||||
>
|
||||
<div className="absolute -inset-8 bg-brand-orange/8 rounded-[2.5rem] blur-3xl" />
|
||||
<div className="relative rounded-3xl overflow-hidden border border-white/[0.08] bg-neutral-900/80">
|
||||
{mockup}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FeatureSections() {
|
||||
return (
|
||||
<div className="py-20 lg:py-32 space-y-28">
|
||||
{/* Section 1: Dashboard — text left, mockup right */}
|
||||
<FeatureSection
|
||||
id="dashboard"
|
||||
heading="Your traffic, at a glance."
|
||||
description="Get a clear, real-time overview of your website's performance without the clutter of traditional analytics tools."
|
||||
features={[
|
||||
'Live visitor count with real-time updates',
|
||||
'Hourly, daily, weekly, and monthly trends',
|
||||
'Referrer sources and UTM campaign tracking',
|
||||
'Country-level geographic breakdown',
|
||||
]}
|
||||
mockup={
|
||||
<div className="p-6 sm:p-10 flex items-center justify-center min-h-[400px]">
|
||||
<Image
|
||||
src="/dashboard-preview-v2.png"
|
||||
alt="Pulse analytics dashboard"
|
||||
width={560}
|
||||
height={400}
|
||||
className="w-full h-auto rounded-xl"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Section 2: Visitors — mockup left, text right */}
|
||||
<FeatureSection
|
||||
id="visitors"
|
||||
heading="Everything you need to know about your visitors."
|
||||
description="Understand where your traffic comes from, what content resonates, and how visitors interact with your site — all without compromising their privacy."
|
||||
features={[
|
||||
'Top pages ranked by views and unique visitors',
|
||||
'Referrer breakdown with source attribution',
|
||||
'Browser, OS, and device analytics',
|
||||
'Peak hours heatmap for optimal publishing',
|
||||
]}
|
||||
reverse
|
||||
mockup={
|
||||
<div className="p-6 sm:p-10 flex items-center justify-center min-h-[400px]">
|
||||
<div className="w-full space-y-4">
|
||||
{/* Mini mockup: top pages bars */}
|
||||
{[
|
||||
{ page: '/blog/privacy-guide', pct: 85 },
|
||||
{ page: '/docs/getting-started', pct: 65 },
|
||||
{ page: '/pricing', pct: 45 },
|
||||
{ page: '/about', pct: 30 },
|
||||
].map((item) => (
|
||||
<div key={item.page} className="space-y-1.5">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-neutral-300 font-medium">{item.page}</span>
|
||||
<span className="text-neutral-500">{item.pct}%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-neutral-800 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-brand-orange rounded-full" style={{ width: `${item.pct}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Section 3: Funnels — text left, mockup right */}
|
||||
<FeatureSection
|
||||
id="funnels"
|
||||
heading="See where visitors drop off."
|
||||
description="Build custom conversion funnels to understand your user journey. Identify bottlenecks and optimize your conversion flow."
|
||||
features={[
|
||||
'Multi-step funnels with conversion rates',
|
||||
'Drop-off analysis between each step',
|
||||
'Conversion trends over time',
|
||||
'Breakdown by device, country, or referrer',
|
||||
'Configurable conversion window (up to 90 days)',
|
||||
]}
|
||||
mockup={
|
||||
<div className="p-6 sm:p-10 flex items-center justify-center min-h-[400px]">
|
||||
{/* Simple funnel visualization */}
|
||||
<div className="w-full max-w-[300px] mx-auto space-y-2">
|
||||
{[
|
||||
{ label: 'Landing Page', value: '2,847', pct: 100, color: 'bg-brand-orange' },
|
||||
{ label: 'Sign Up Page', value: '1,423', pct: 50, color: 'bg-brand-orange/80' },
|
||||
{ label: 'Onboarding', value: '856', pct: 30, color: 'bg-brand-orange/60' },
|
||||
{ label: 'Activated', value: '412', pct: 14.5, color: 'bg-brand-orange/40' },
|
||||
].map((step, i) => (
|
||||
<div key={step.label} className="text-center">
|
||||
<div
|
||||
className={`${step.color} rounded-lg py-3 mx-auto transition-all`}
|
||||
style={{ width: `${step.pct}%` }}
|
||||
>
|
||||
<span className="text-white text-sm font-semibold">{step.value}</span>
|
||||
</div>
|
||||
<p className="text-xs text-neutral-400 mt-1">{step.label}</p>
|
||||
{i < 3 && (
|
||||
<p className="text-xs text-neutral-600 my-1">↓ {Math.round((1 - [1, 0.5, 0.3, 0.145][i + 1] / [1, 0.5, 0.3, 0.145][i]) * 100)}% drop-off</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<p className="text-center text-sm text-neutral-300 mt-4 font-medium">Overall conversion: <span className="text-brand-orange">14.5%</span></p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Section 4: Reports — mockup left, text right */}
|
||||
<FeatureSection
|
||||
id="reports"
|
||||
heading="Reports delivered to your inbox."
|
||||
description="Get automated summaries of your site's performance without logging into a dashboard. Stay informed effortlessly."
|
||||
features={[
|
||||
'Daily, weekly, or monthly email summaries',
|
||||
'Key metrics with period-over-period comparison',
|
||||
'Top pages, referrers, and country breakdown',
|
||||
'Webhook delivery for custom integrations',
|
||||
'Multiple recipients per report',
|
||||
]}
|
||||
reverse
|
||||
mockup={
|
||||
<div className="p-6 sm:p-10 flex items-center justify-center min-h-[400px]">
|
||||
{/* Email mockup */}
|
||||
<div className="w-full max-w-[360px] mx-auto bg-neutral-800/50 rounded-xl border border-neutral-700/40 p-5 space-y-4">
|
||||
<div className="flex items-center gap-3 pb-3 border-b border-neutral-700/40">
|
||||
<div className="w-8 h-8 rounded-lg bg-brand-orange/20 flex items-center justify-center">
|
||||
<span className="text-brand-orange text-xs font-bold">P</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-white">Pulse Daily Report</p>
|
||||
<p className="text-xs text-neutral-500">yoursite.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{[
|
||||
{ label: 'Visitors', value: '1,247', change: '+12%' },
|
||||
{ label: 'Pageviews', value: '3,891', change: '+8%' },
|
||||
{ label: 'Bounce Rate', value: '42%', change: '-3%' },
|
||||
{ label: 'Avg Duration', value: '2m 34s', change: '+15%' },
|
||||
].map((stat) => (
|
||||
<div key={stat.label} className="bg-neutral-900/50 rounded-lg p-3">
|
||||
<p className="text-xs text-neutral-500">{stat.label}</p>
|
||||
<p className="text-lg font-bold text-white">{stat.value}</p>
|
||||
<p className={`text-xs ${stat.change.startsWith('+') ? 'text-green-400' : 'text-brand-orange'}`}>{stat.change}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-neutral-600 text-center">Delivered every day at 09:00</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Section 5: Script — text left, code block right */}
|
||||
<FeatureSection
|
||||
id="script"
|
||||
heading="One script tag. That's it."
|
||||
description="No npm packages, no build steps, no configuration files. Add a single line to your HTML and start collecting privacy-respecting analytics instantly."
|
||||
features={[
|
||||
'Under 2KB gzipped — 20x smaller than Google Analytics',
|
||||
'Async loading with defer — never blocks rendering',
|
||||
'Works with any framework or static site',
|
||||
]}
|
||||
mockup={
|
||||
<div className="p-0">
|
||||
{/* Code block with browser chrome */}
|
||||
<div className="flex items-center px-4 py-3 bg-neutral-800 border-b border-neutral-800">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500/20" />
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500/20" />
|
||||
<div className="w-3 h-3 rounded-full bg-green-500/20" />
|
||||
</div>
|
||||
<span className="ml-4 text-xs text-neutral-400 font-mono">index.html</span>
|
||||
</div>
|
||||
<pre className="p-6 overflow-x-auto">
|
||||
<code className="font-mono text-sm text-neutral-300">
|
||||
<span className="text-neutral-500">{'<!-- Add before </head> -->'}</span>{'\n'}
|
||||
<span className="text-blue-400">{'<'}</span>
|
||||
<span className="text-blue-400">script</span>{'\n'}
|
||||
{' '}<span className="text-sky-300">defer</span>{'\n'}
|
||||
{' '}<span className="text-sky-300">data-domain</span>=<span className="text-orange-300">"yoursite.com"</span>{'\n'}
|
||||
{' '}<span className="text-sky-300">src</span>=<span className="text-orange-300">"https://pulse.ciphera.net/js/script.js"</span>{'\n'}
|
||||
<span className="text-blue-400">{'>'}</span>
|
||||
<span className="text-blue-400">{'</'}</span>
|
||||
<span className="text-blue-400">script</span>
|
||||
<span className="text-blue-400">{'>'}</span>
|
||||
</code>
|
||||
</pre>
|
||||
<div className="flex items-center gap-4 px-6 py-3 border-t border-neutral-800 text-xs text-neutral-500">
|
||||
<span>1.6 KB gzipped</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500" />
|
||||
Non-blocking, async
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user