feat: dark-only cleanup for marketing pages and authenticated landing view

This commit is contained in:
Usman Baig
2026-03-21 19:39:01 +01:00
parent 7bf7e5cc3d
commit 3710f081a6
13 changed files with 205 additions and 205 deletions

View File

@@ -15,23 +15,23 @@ function ComparisonTable({ title, competitors }: { title: string, competitors: {
return ( return (
<div className="mb-16"> <div className="mb-16">
<h2 className="text-2xl font-bold mb-6 text-neutral-900 dark:text-white">{title}</h2> <h2 className="text-2xl font-bold mb-6 text-white">{title}</h2>
<div className="overflow-hidden rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm"> <div className="overflow-hidden rounded-2xl border border-neutral-800 bg-neutral-900/50 backdrop-blur-sm">
<table className="w-full text-left border-collapse"> <table className="w-full text-left border-collapse">
<thead> <thead>
<tr className="border-b border-neutral-200 dark:border-neutral-800"> <tr className="border-b border-neutral-800">
<th className="p-4 sm:p-6 text-sm font-medium text-neutral-500">Feature</th> <th className="p-4 sm:p-6 text-sm font-medium text-neutral-500">Feature</th>
{competitors.map((comp) => ( {competitors.map((comp) => (
<th key={comp.name} className={`p-4 sm:p-6 text-sm font-bold ${comp.isPulse ? 'text-brand-orange' : 'text-neutral-900 dark:text-white'}`}> <th key={comp.name} className={`p-4 sm:p-6 text-sm font-bold ${comp.isPulse ? 'text-brand-orange' : 'text-white'}`}>
{comp.name} {comp.name}
</th> </th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-neutral-200 dark:divide-neutral-800"> <tbody className="divide-y divide-neutral-800">
{allFeatures.map((feature) => ( {allFeatures.map((feature) => (
<tr key={feature} className="hover:bg-neutral-50/50 dark:hover:bg-neutral-800/50 transition-colors"> <tr key={feature} className="hover:bg-neutral-800/50 transition-colors">
<td className="p-4 sm:p-6 text-neutral-900 dark:text-white font-medium text-sm sm:text-base">{feature}</td> <td className="p-4 sm:p-6 text-white font-medium text-sm sm:text-base">{feature}</td>
{competitors.map((comp) => { {competitors.map((comp) => {
const val = comp.features[feature] const val = comp.features[feature]
return ( return (
@@ -41,7 +41,7 @@ function ComparisonTable({ title, competitors }: { title: string, competitors: {
) : val === false ? ( ) : val === false ? (
<XIcon className="w-5 h-5 text-red-500" /> <XIcon className="w-5 h-5 text-red-500" />
) : ( ) : (
<span className={comp.isPulse ? 'text-green-500 font-medium' : 'text-neutral-600 dark:text-neutral-400'}>{val}</span> <span className={comp.isPulse ? 'text-green-500 font-medium' : 'text-neutral-400'}>{val}</span>
)} )}
</td> </td>
) )
@@ -60,9 +60,9 @@ export default function AboutPage() {
<div className="relative min-h-screen flex flex-col overflow-hidden"> <div className="relative min-h-screen flex flex-col overflow-hidden">
{/* * --- ATMOSPHERE (Background) --- */} {/* * --- ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -74,10 +74,10 @@ export default function AboutPage() {
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="text-center mb-16" className="text-center mb-16"
> >
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white mb-6"> <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white mb-6">
Why Pulse? Why Pulse?
</h1> </h1>
<p className="text-xl text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto leading-relaxed"> <p className="text-xl text-neutral-400 max-w-2xl mx-auto leading-relaxed">
We built Pulse because we were tired of complex, invasive analytics tools. We built Pulse because we were tired of complex, invasive analytics tools.
Here is how we stack up against the giants. Here is how we stack up against the giants.
</p> </p>
@@ -87,9 +87,9 @@ export default function AboutPage() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }} transition={{ duration: 0.5, delay: 0.1 }}
className="prose prose-neutral dark:prose-invert max-w-none mb-16" className="prose prose-invert max-w-none mb-16"
> >
<p className="text-lg text-neutral-600 dark:text-neutral-400"> <p className="text-lg text-neutral-400">
Most analytics tools are overkill. They track everything, slow down your site, and require annoying cookie banners. Most analytics tools are overkill. They track everything, slow down your site, and require annoying cookie banners.
Pulse is different. We focus on the metrics that actually mattervisitors, pageviews, and sourceswhile respecting user privacy. Pulse is different. We focus on the metrics that actually mattervisitors, pageviews, and sourceswhile respecting user privacy.
</p> </p>
@@ -162,10 +162,10 @@ export default function AboutPage() {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="mt-8 p-6 bg-neutral-100 dark:bg-neutral-800/50 rounded-xl border border-neutral-200 dark:border-neutral-800" className="mt-8 p-6 bg-neutral-800/50 rounded-xl border border-neutral-800"
> >
<h3 className="text-xl font-bold mb-2 text-neutral-900 dark:text-white">What about Plausible?</h3> <h3 className="text-xl font-bold mb-2 text-white">What about Plausible?</h3>
<p className="text-neutral-600 dark:text-neutral-400 text-sm"> <p className="text-neutral-400 text-sm">
We love Plausible! They paved the way for privacy-friendly analytics. We love Plausible! They paved the way for privacy-friendly analytics.
Pulse offers a similar philosophy but with a focus on even deeper integration with the Ciphera ecosystem Pulse offers a similar philosophy but with a focus on even deeper integration with the Ciphera ecosystem
and more flexible pricing for developers. and more flexible pricing for developers.

View File

@@ -109,9 +109,9 @@ export default function FeaturesPage() {
<div className="relative min-h-screen flex flex-col overflow-hidden"> <div className="relative min-h-screen flex flex-col overflow-hidden">
{/* * --- ATMOSPHERE (Background) --- */} {/* * --- ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -128,11 +128,11 @@ export default function FeaturesPage() {
<span className="w-1.5 h-1.5 rounded-full bg-brand-orange animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-brand-orange animate-pulse" />
Product Tour Product Tour
</span> </span>
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white mb-6"> <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white mb-6">
Everything you need. <br /> Everything you need. <br />
<span className="gradient-text">Nothing you don&apos;t.</span> <span className="gradient-text">Nothing you don&apos;t.</span>
</h1> </h1>
<p className="text-xl text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto leading-relaxed"> <p className="text-xl text-neutral-400 max-w-2xl mx-auto leading-relaxed">
Pulse gives you meaningful analytics without the complexity, the cookies, or the privacy trade-offs. Pulse gives you meaningful analytics without the complexity, the cookies, or the privacy trade-offs.
</p> </p>
</motion.div> </motion.div>
@@ -151,10 +151,10 @@ export default function FeaturesPage() {
<div className="w-12 h-12 rounded-xl bg-brand-orange/10 flex items-center justify-center mb-6 text-brand-orange group-hover:scale-110 transition-transform duration-300"> <div className="w-12 h-12 rounded-xl bg-brand-orange/10 flex items-center justify-center mb-6 text-brand-orange group-hover:scale-110 transition-transform duration-300">
<feature.icon className="w-6 h-6" /> <feature.icon className="w-6 h-6" />
</div> </div>
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-3"> <h3 className="text-xl font-bold text-white mb-3">
{feature.title} {feature.title}
</h3> </h3>
<p className="text-neutral-600 dark:text-neutral-400 leading-relaxed"> <p className="text-neutral-400 leading-relaxed">
{feature.description} {feature.description}
</p> </p>
</motion.div> </motion.div>
@@ -170,10 +170,10 @@ export default function FeaturesPage() {
className="mb-28" className="mb-28"
> >
<div className="text-center mb-14"> <div className="text-center mb-14">
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white mb-4"> <h2 className="text-2xl font-bold text-white mb-4">
Powerful analytics, <span className="gradient-text">simplified</span> Powerful analytics, <span className="gradient-text">simplified</span>
</h2> </h2>
<p className="text-lg text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto"> <p className="text-lg text-neutral-400 max-w-2xl mx-auto">
Everything from real-time dashboards to conversion funnels without the bloat. Everything from real-time dashboards to conversion funnels without the bloat.
</p> </p>
</div> </div>
@@ -192,10 +192,10 @@ export default function FeaturesPage() {
{cap.icon} {cap.icon}
</div> </div>
<div> <div>
<h3 className="font-bold text-neutral-900 dark:text-white mb-1"> <h3 className="font-bold text-white mb-1">
{cap.title} {cap.title}
</h3> </h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed"> <p className="text-sm text-neutral-400 leading-relaxed">
{cap.description} {cap.description}
</p> </p>
</div> </div>
@@ -210,14 +210,14 @@ export default function FeaturesPage() {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="mb-28 p-10 md:p-14 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-2xl" className="mb-28 p-10 md:p-14 bg-neutral-900/50 backdrop-blur-sm border border-neutral-800 rounded-2xl"
> >
<div className="grid md:grid-cols-2 gap-10 items-center"> <div className="grid md:grid-cols-2 gap-10 items-center">
<div> <div>
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white mb-4"> <h2 className="text-2xl font-bold text-white mb-4">
Content that <span className="gradient-text">performs</span> Content that <span className="gradient-text">performs</span>
</h2> </h2>
<p className="text-neutral-600 dark:text-neutral-400 leading-relaxed mb-6"> <p className="text-neutral-400 leading-relaxed mb-6">
See which pages drive the most traffic, where visitors enter your site, and where they leave. Use data to double down on what works. See which pages drive the most traffic, where visitors enter your site, and where they leave. Use data to double down on what works.
</p> </p>
<ul className="space-y-3"> <ul className="space-y-3">
@@ -228,7 +228,7 @@ export default function FeaturesPage() {
'Referral sources — where traffic comes from', 'Referral sources — where traffic comes from',
'Browser, OS & device breakdowns', 'Browser, OS & device breakdowns',
].map((item) => ( ].map((item) => (
<li key={item} className="flex items-start gap-3 text-sm text-neutral-600 dark:text-neutral-400"> <li key={item} className="flex items-start gap-3 text-sm text-neutral-400">
<svg className="w-5 h-5 text-brand-orange shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor"> <svg className="w-5 h-5 text-brand-orange shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" /> <path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg> </svg>
@@ -250,17 +250,17 @@ export default function FeaturesPage() {
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.4, delay: i * 0.1 }} transition={{ duration: 0.4, delay: i * 0.1 }}
className="p-4 bg-neutral-50 dark:bg-neutral-800/50 rounded-xl" className="p-4 bg-neutral-800/50 rounded-xl"
> >
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-neutral-900 dark:text-white truncate mr-4"> <span className="text-sm font-medium text-white truncate mr-4">
{page.label} {page.label}
</span> </span>
<span className="text-sm text-neutral-500 dark:text-neutral-400 shrink-0"> <span className="text-sm text-neutral-400 shrink-0">
{page.views} views {page.views} views
</span> </span>
</div> </div>
<div className="h-1.5 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden"> <div className="h-1.5 bg-neutral-700 rounded-full overflow-hidden">
<motion.div <motion.div
initial={{ width: 0 }} initial={{ width: 0 }}
whileInView={{ width: `${page.pct}%` }} whileInView={{ width: `${page.pct}%` }}
@@ -284,10 +284,10 @@ export default function FeaturesPage() {
className="mb-28" className="mb-28"
> >
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white mb-4"> <h2 className="text-2xl font-bold text-white mb-4">
Built for trust Built for trust
</h2> </h2>
<p className="text-lg text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto"> <p className="text-lg text-neutral-400 max-w-2xl mx-auto">
Open source, Swiss hosted, and designed to keep your visitors&apos; data where it belongs. Open source, Swiss hosted, and designed to keep your visitors&apos; data where it belongs.
</p> </p>
</div> </div>
@@ -306,8 +306,8 @@ export default function FeaturesPage() {
<path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" /> <path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg> </svg>
<div> <div>
<span className="font-semibold text-neutral-900 dark:text-white text-sm">{signal.label}</span> <span className="font-semibold text-white text-sm">{signal.label}</span>
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-0.5">{signal.detail}</p> <p className="text-xs text-neutral-400 mt-0.5">{signal.detail}</p>
</div> </div>
</motion.div> </motion.div>
))} ))}
@@ -340,10 +340,10 @@ export default function FeaturesPage() {
className="mb-28" className="mb-28"
> >
<div className="text-center mb-14"> <div className="text-center mb-14">
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white mb-4"> <h2 className="text-2xl font-bold text-white mb-4">
Up and running in <span className="gradient-text">3 minutes</span> Up and running in <span className="gradient-text">3 minutes</span>
</h2> </h2>
<p className="text-lg text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto"> <p className="text-lg text-neutral-400 max-w-2xl mx-auto">
No SDKs to install, no build steps, no configuration files. No SDKs to install, no build steps, no configuration files.
</p> </p>
</div> </div>
@@ -366,15 +366,15 @@ export default function FeaturesPage() {
{s.step} {s.step}
</div> </div>
<div> <div>
<h3 className="font-bold text-neutral-900 dark:text-white text-sm"> <h3 className="font-bold text-white text-sm">
{s.title} {s.title}
</h3> </h3>
<p className="text-xs text-neutral-500 dark:text-neutral-400"> <p className="text-xs text-neutral-400">
{s.desc} {s.desc}
</p> </p>
</div> </div>
{i < 2 && ( {i < 2 && (
<ArrowRightIcon className="w-5 h-5 text-neutral-300 dark:text-neutral-600 shrink-0 hidden md:block" /> <ArrowRightIcon className="w-5 h-5 text-neutral-600 shrink-0 hidden md:block" />
)} )}
</motion.div> </motion.div>
))} ))}
@@ -389,10 +389,10 @@ export default function FeaturesPage() {
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="text-center mb-20" className="text-center mb-20"
> >
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white mb-4"> <h2 className="text-2xl font-bold text-white mb-4">
Ready to see it in action? Ready to see it in action?
</h2> </h2>
<p className="text-neutral-600 dark:text-neutral-400 mb-8 max-w-lg mx-auto"> <p className="text-neutral-400 mb-8 max-w-lg mx-auto">
Start for free. No credit card required. Cancel anytime. Start for free. No credit card required. Cancel anytime.
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center"> <div className="flex flex-col sm:flex-row gap-4 justify-center items-center">

View File

@@ -9,26 +9,26 @@ export default function InstallationPage() {
{/* * --- 1. ATMOSPHERE (Background) --- */} {/* * --- 1. ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
{/* * Bottom-right Neutral Glow */} {/* * Bottom-right Neutral Glow */}
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
{/* * Grid Pattern with Radial Mask */} {/* * Grid Pattern with Radial Mask */}
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
<div className="flex-grow w-full max-w-4xl mx-auto px-4 pt-20 pb-10 z-10"> <div className="flex-grow w-full max-w-4xl mx-auto px-4 pt-20 pb-10 z-10">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white mb-6"> <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white mb-6">
Installation Installation
</h1> </h1>
<p className="text-xl text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto leading-relaxed"> <p className="text-xl text-neutral-400 max-w-2xl mx-auto leading-relaxed">
Get up and running with Pulse in seconds. Get up and running with Pulse in seconds.
</p> </p>
</div> </div>
<div className="w-full text-center"> <div className="w-full text-center">
<h2 className="text-2xl font-bold mb-8 text-neutral-900 dark:text-white">Add the snippet</h2> <h2 className="text-2xl font-bold mb-8 text-white">Add the snippet</h2>
<p className="text-neutral-500 mb-8">Just add this snippet to your &lt;head&gt; tag in your layout or index file.</p> <p className="text-neutral-500 mb-8">Just add this snippet to your &lt;head&gt; tag in your layout or index file.</p>
<div className="max-w-2xl mx-auto bg-neutral-900 rounded-xl overflow-hidden shadow-2xl text-left border border-neutral-800"> <div className="max-w-2xl mx-auto bg-neutral-900 rounded-xl overflow-hidden shadow-2xl text-left border border-neutral-800">
@@ -57,9 +57,9 @@ export default function InstallationPage() {
</div> </div>
<div className="w-full mt-16 text-center"> <div className="w-full mt-16 text-center">
<h2 className="text-2xl font-bold mb-4 text-neutral-900 dark:text-white">Custom events (goals)</h2> <h2 className="text-2xl font-bold mb-4 text-white">Custom events (goals)</h2>
<p className="text-neutral-500 mb-6 max-w-xl mx-auto"> <p className="text-neutral-500 mb-6 max-w-xl mx-auto">
Track custom events (e.g. signup, purchase) with <code className="px-1.5 py-0.5 rounded bg-neutral-200 dark:bg-neutral-700 text-sm font-mono">pulse.track(&apos;event_name&apos;)</code>. Use letters, numbers, and underscores only. Define goals in your site Settings Goals & Events to see counts in the dashboard. Track custom events (e.g. signup, purchase) with <code className="px-1.5 py-0.5 rounded bg-neutral-700 text-sm font-mono">pulse.track(&apos;event_name&apos;)</code>. Use letters, numbers, and underscores only. Define goals in your site Settings Goals & Events to see counts in the dashboard.
</p> </p>
<div className="max-w-2xl mx-auto bg-neutral-900 rounded-xl overflow-hidden shadow-2xl text-left border border-neutral-800"> <div className="max-w-2xl mx-auto bg-neutral-900 rounded-xl overflow-hidden shadow-2xl text-left border border-neutral-800">
<div className="flex items-center px-4 py-3 bg-neutral-800 border-b border-neutral-800"> <div className="flex items-center px-4 py-3 bg-neutral-800 border-b border-neutral-800">

View File

@@ -8,9 +8,9 @@ export default function NextJsIntegrationPage() {
<div className="relative min-h-screen flex flex-col overflow-hidden"> <div className="relative min-h-screen flex flex-col overflow-hidden">
{/* * --- ATMOSPHERE (Background) --- */} {/* * --- ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -25,22 +25,22 @@ export default function NextJsIntegrationPage() {
</Link> </Link>
<div className="flex items-center gap-4 mb-8"> <div className="flex items-center gap-4 mb-8">
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl"> <div className="p-3 bg-neutral-800 rounded-xl">
<svg viewBox="0 0 128 128" className="w-10 h-10 dark:invert"> <svg viewBox="0 0 128 128" className="w-10 h-10 invert">
<path d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64 64-28.7 64-64S99.3 0 64 0zm27.6 93.9c-.8.9-2.2 1-3.1.2L42.8 52.8V88c0 1.3-1.1 2.3-2.3 2.3h-7.4c-1.3 0-2.3-1.1-2.3-2.3V40c0-1.3 1.1-2.3 2.3-2.3h7.4c1 0 1.9.6 2.2 1.5l48.6 44.8V40c0-1.3 1.1-2.3 2.3-2.3h7.4c1.3 0 2.3 1.1 2.3 2.3v48c0 1.3-1.1 2.3-2.3 2.3h-6.8c-.9 0-1.7-.5-2.1-1.3z" /> <path d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64 64-28.7 64-64S99.3 0 64 0zm27.6 93.9c-.8.9-2.2 1-3.1.2L42.8 52.8V88c0 1.3-1.1 2.3-2.3 2.3h-7.4c-1.3 0-2.3-1.1-2.3-2.3V40c0-1.3 1.1-2.3 2.3-2.3h7.4c1 0 1.9.6 2.2 1.5l48.6 44.8V40c0-1.3 1.1-2.3 2.3-2.3h7.4c1.3 0 2.3 1.1 2.3 2.3v48c0 1.3-1.1 2.3-2.3 2.3h-6.8c-.9 0-1.7-.5-2.1-1.3z" />
</svg> </svg>
</div> </div>
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white"> <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white">
Next.js Integration Next.js Integration
</h1> </h1>
</div> </div>
<div className="prose prose-neutral dark:prose-invert max-w-none"> <div className="prose prose-invert max-w-none">
<p className="lead text-xl text-neutral-600 dark:text-neutral-400"> <p className="lead text-xl text-neutral-400">
The best way to add Pulse to your Next.js application is using the built-in <code>next/script</code> component. The best way to add Pulse to your Next.js application is using the built-in <code>next/script</code> component.
</p> </p>
<hr className="my-8 border-neutral-200 dark:border-neutral-800" /> <hr className="my-8 border-neutral-800" />
<h3>Using App Router (Recommended)</h3> <h3>Using App Router (Recommended)</h3>
<p> <p>

View File

@@ -93,9 +93,9 @@ export default function IntegrationsPage() {
<div className="relative min-h-screen flex flex-col overflow-hidden"> <div className="relative min-h-screen flex flex-col overflow-hidden">
{/* * --- ATMOSPHERE (Background) --- */} {/* * --- ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -109,14 +109,14 @@ export default function IntegrationsPage() {
> >
{/* * --- Title with count badge --- */} {/* * --- Title with count badge --- */}
<div className="flex items-center justify-center gap-3 mb-6"> <div className="flex items-center justify-center gap-3 mb-6">
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white"> <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white">
Integrations Integrations
</h1> </h1>
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold bg-brand-orange/10 text-brand-orange border border-brand-orange/20"> <span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold bg-brand-orange/10 text-brand-orange border border-brand-orange/20">
{integrations.length}+ {integrations.length}+
</span> </span>
</div> </div>
<p className="text-xl text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto leading-relaxed mb-8"> <p className="text-xl text-neutral-400 max-w-2xl mx-auto leading-relaxed mb-8">
Connect Pulse with {integrations.length}+ frameworks and platforms in minutes. Connect Pulse with {integrations.length}+ frameworks and platforms in minutes.
</p> </p>
@@ -143,12 +143,12 @@ export default function IntegrationsPage() {
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
placeholder="Search integrations..." placeholder="Search integrations..."
className="w-full pl-12 pr-16 py-3 bg-white/70 dark:bg-neutral-900/70 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-xl text-neutral-900 dark:text-white placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-brand-orange/50 focus:border-brand-orange/50 transition-all" className="w-full pl-12 pr-16 py-3 bg-neutral-900/70 backdrop-blur-sm border border-neutral-800 rounded-xl text-white placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-brand-orange/50 focus:border-brand-orange/50 transition-all"
/> />
{query ? ( {query ? (
<button <button
onClick={() => setQuery('')} onClick={() => setQuery('')}
className="absolute inset-y-0 right-0 flex items-center pr-4 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-300 transition-colors" className="absolute inset-y-0 right-0 flex items-center pr-4 text-neutral-400 hover:text-neutral-600 hover:text-neutral-300 transition-colors"
aria-label="Clear search" aria-label="Clear search"
> >
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor"> <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
@@ -157,7 +157,7 @@ export default function IntegrationsPage() {
</button> </button>
) : ( ) : (
<div className="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none">
<kbd className="hidden sm:inline-flex items-center px-1.5 py-0.5 rounded text-xs font-mono font-medium bg-neutral-200/80 dark:bg-neutral-700/80 text-neutral-500 dark:text-neutral-400 border border-neutral-300 dark:border-neutral-600"> <kbd className="hidden sm:inline-flex items-center px-1.5 py-0.5 rounded text-xs font-mono font-medium bg-neutral-700/80 text-neutral-400 border border-neutral-600">
/ /
</kbd> </kbd>
</div> </div>
@@ -169,7 +169,7 @@ export default function IntegrationsPage() {
<motion.p <motion.p
initial={{ opacity: 0, y: -5 }} initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="text-sm text-neutral-500 dark:text-neutral-400 mt-3" className="text-sm text-neutral-400 mt-3"
> >
{totalResults} {totalResults === 1 ? 'integration' : 'integrations'} found {totalResults} {totalResults === 1 ? 'integration' : 'integrations'} found
{query && <> for &ldquo;{query}&rdquo;</>} {query && <> for &ldquo;{query}&rdquo;</>}
@@ -189,7 +189,7 @@ export default function IntegrationsPage() {
className={`px-4 py-1.5 rounded-full text-sm font-medium transition-all ${ className={`px-4 py-1.5 rounded-full text-sm font-medium transition-all ${
activeCategory === 'all' activeCategory === 'all'
? 'bg-brand-orange text-white shadow-sm' ? 'bg-brand-orange text-white shadow-sm'
: 'bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-700' : 'bg-neutral-800 text-neutral-400 hover:bg-neutral-700'
}`} }`}
> >
All All
@@ -201,7 +201,7 @@ export default function IntegrationsPage() {
className={`px-4 py-1.5 rounded-full text-sm font-medium transition-all ${ className={`px-4 py-1.5 rounded-full text-sm font-medium transition-all ${
activeCategory === cat activeCategory === cat
? 'bg-brand-orange text-white shadow-sm' ? 'bg-brand-orange text-white shadow-sm'
: 'bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-700' : 'bg-neutral-800 text-neutral-400 hover:bg-neutral-700'
}`} }`}
> >
{categoryLabels[cat]} {categoryLabels[cat]}
@@ -226,7 +226,7 @@ export default function IntegrationsPage() {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
className="text-lg font-semibold text-neutral-500 dark:text-neutral-400 mb-6 tracking-wide uppercase flex items-center gap-2" className="text-lg font-semibold text-neutral-400 mb-6 tracking-wide uppercase flex items-center gap-2"
> >
<svg className="w-5 h-5 text-brand-orange" fill="currentColor" viewBox="0 0 20 20"> <svg className="w-5 h-5 text-brand-orange" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 1l2.39 4.84 5.34.78-3.87 3.77.91 5.33L10 13.27l-4.77 2.5.91-5.33L2.27 6.67l5.34-.78L10 1z" /> <path d="M10 1l2.39 4.84 5.34.78-3.87 3.77.91 5.33L10 13.27l-4.77 2.5.91-5.33L2.27 6.67l5.34-.78L10 1z" />
@@ -245,12 +245,12 @@ export default function IntegrationsPage() {
> >
<Link <Link
href={`/integrations/${integration!.id}`} href={`/integrations/${integration!.id}`}
className="group flex items-center gap-3 p-4 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-xl hover:border-brand-orange/50 dark:hover:border-brand-orange/50 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg h-full" className="group flex items-center gap-3 p-4 bg-neutral-900/50 backdrop-blur-sm border border-neutral-800 rounded-xl hover:border-brand-orange/50 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg h-full"
> >
<div className="p-2 bg-neutral-100 dark:bg-neutral-800 rounded-lg shrink-0 group-hover:scale-110 transition-transform duration-300 [&_svg]:w-6 [&_svg]:h-6"> <div className="p-2 bg-neutral-800 rounded-lg shrink-0 group-hover:scale-110 transition-transform duration-300 [&_svg]:w-6 [&_svg]:h-6">
{integration!.icon} {integration!.icon}
</div> </div>
<span className="font-semibold text-neutral-900 dark:text-white text-sm"> <span className="font-semibold text-white text-sm">
{integration!.name} {integration!.name}
</span> </span>
</Link> </Link>
@@ -268,7 +268,7 @@ export default function IntegrationsPage() {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.4 }} transition={{ duration: 0.4 }}
className="text-lg font-semibold text-neutral-500 dark:text-neutral-400 mb-6 tracking-wide uppercase" className="text-lg font-semibold text-neutral-400 mb-6 tracking-wide uppercase"
> >
{group.label} {group.label}
</motion.h2> </motion.h2>
@@ -284,19 +284,19 @@ export default function IntegrationsPage() {
> >
<Link <Link
href={`/integrations/${integration.id}`} href={`/integrations/${integration.id}`}
className="group relative p-6 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-2xl hover:border-brand-orange/50 dark:hover:border-brand-orange/50 transition-all duration-300 hover:-translate-y-1 hover:shadow-xl block h-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange focus-visible:ring-offset-2" className="group relative p-6 bg-neutral-900/50 backdrop-blur-sm border border-neutral-800 rounded-2xl hover:border-brand-orange/50 transition-all duration-300 hover:-translate-y-1 hover:shadow-xl block h-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange focus-visible:ring-offset-2"
> >
<div className="flex items-start justify-between mb-6"> <div className="flex items-start justify-between mb-6">
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl group-hover:scale-110 transition-transform duration-300"> <div className="p-3 bg-neutral-800 rounded-xl group-hover:scale-110 transition-transform duration-300">
{integration.icon} {integration.icon}
</div> </div>
<ArrowRightIcon className="w-5 h-5 text-neutral-400 group-hover:text-brand-orange transition-colors" /> <ArrowRightIcon className="w-5 h-5 text-neutral-400 group-hover:text-brand-orange transition-colors" />
</div> </div>
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-3"> <h3 className="text-xl font-bold text-white mb-3">
{integration.name} {integration.name}
</h3> </h3>
<p className="text-neutral-600 dark:text-neutral-400 leading-relaxed mb-4"> <p className="text-neutral-400 leading-relaxed mb-4">
{integration.description} {integration.description}
</p> </p>
<span className="text-sm font-medium text-brand-orange opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-1"> <span className="text-sm font-medium text-brand-orange opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-1">
@@ -317,20 +317,20 @@ export default function IntegrationsPage() {
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }} exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className="max-w-md mx-auto mt-8 p-10 border border-dashed border-neutral-300 dark:border-neutral-700 rounded-2xl flex flex-col items-center justify-center text-center" className="max-w-md mx-auto mt-8 p-10 border border-dashed border-neutral-700 rounded-2xl flex flex-col items-center justify-center text-center"
> >
<div className="p-4 bg-neutral-100 dark:bg-neutral-800 rounded-full mb-4"> <div className="p-4 bg-neutral-800 rounded-full mb-4">
<svg className="w-8 h-8 text-neutral-400" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"> <svg className="w-8 h-8 text-neutral-400" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /> <path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg> </svg>
</div> </div>
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-2"> <h3 className="text-xl font-bold text-white mb-2">
Missing something? Missing something?
</h3> </h3>
<p className="text-neutral-600 dark:text-neutral-400 text-sm mb-1"> <p className="text-neutral-400 text-sm mb-1">
No integrations found for &ldquo;{query}&rdquo;. No integrations found for &ldquo;{query}&rdquo;.
</p> </p>
<p className="text-neutral-600 dark:text-neutral-400 text-sm mb-5"> <p className="text-neutral-400 text-sm mb-5">
Let us know which integration you&apos;d like to see next. Let us know which integration you&apos;d like to see next.
</p> </p>
<a <a
@@ -350,12 +350,12 @@ export default function IntegrationsPage() {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="max-w-md mx-auto mt-12 p-6 border border-dashed border-neutral-300 dark:border-neutral-700 rounded-2xl flex flex-col items-center justify-center text-center" className="max-w-md mx-auto mt-12 p-6 border border-dashed border-neutral-700 rounded-2xl flex flex-col items-center justify-center text-center"
> >
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-2"> <h3 className="text-xl font-bold text-white mb-2">
Missing something? Missing something?
</h3> </h3>
<p className="text-neutral-600 dark:text-neutral-400 text-sm mb-4"> <p className="text-neutral-400 text-sm mb-4">
Let us know which integration you&apos;d like to see next. Let us know which integration you&apos;d like to see next.
</p> </p>
<a <a

View File

@@ -8,9 +8,9 @@ export default function ReactIntegrationPage() {
<div className="relative min-h-screen flex flex-col overflow-hidden"> <div className="relative min-h-screen flex flex-col overflow-hidden">
{/* * --- ATMOSPHERE (Background) --- */} {/* * --- ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -25,23 +25,23 @@ export default function ReactIntegrationPage() {
</Link> </Link>
<div className="flex items-center gap-4 mb-8"> <div className="flex items-center gap-4 mb-8">
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl"> <div className="p-3 bg-neutral-800 rounded-xl">
<svg viewBox="0 0 128 128" className="w-10 h-10 text-[#61DAFB] fill-current"> <svg viewBox="0 0 128 128" className="w-10 h-10 text-[#61DAFB] fill-current">
<path d="M64 10.6c18.4 0 34.6 5.8 44.6 14.8 6.4 5.8 10.2 12.8 10.2 20.6 0 21.6-28.6 41.2-64 41.2-1.6 0-3.2-.1-4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11 11.2-5.6 20.2-13 26.2-21.4-6.4-5.8-15.4-10-25.6-12.2-10.2-2.2-21.4-3.4-33-3.4-1.6 0-3.2.1-4.8.2 1.2-10.8 6.2-20.2 13.8-27.6 8.8-8.6 20.6-13.4 33.2-13.4 2.2 0 4.4.2 6.4.4-10.2 12.8-15.6 29.2-15.6 46.2 0 2.6.2 5.2.4 7.8-13.6 1.6-26.2 5.4-37.4 11-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11zm-33.4 62c-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11zm-15.2-16.6c-6.4-5.8-10.2-12.8-10.2-20.6 0-21.6 28.6-41.2 64-41.2 1.6 0 3.2.1 4.8.2 1.2-10.8 6.2-20.2 13.8-27.6 8.8-8.6 20.6-13.4 33.2-13.4 2.2 0 4.4.2 6.4.4-10.2 12.8-15.6 29.2-15.6 46.2 0 2.6.2 5.2.4 7.8-13.6 1.6-26.2 5.4-37.4 11-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8z" /> <path d="M64 10.6c18.4 0 34.6 5.8 44.6 14.8 6.4 5.8 10.2 12.8 10.2 20.6 0 21.6-28.6 41.2-64 41.2-1.6 0-3.2-.1-4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11 11.2-5.6 20.2-13 26.2-21.4-6.4-5.8-15.4-10-25.6-12.2-10.2-2.2-21.4-3.4-33-3.4-1.6 0-3.2.1-4.8.2 1.2-10.8 6.2-20.2 13.8-27.6 8.8-8.6 20.6-13.4 33.2-13.4 2.2 0 4.4.2 6.4.4-10.2 12.8-15.6 29.2-15.6 46.2 0 2.6.2 5.2.4 7.8-13.6 1.6-26.2 5.4-37.4 11-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11zm-33.4 62c-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8 13.6-1.6 26.2-5.4 37.4-11zm-15.2-16.6c-6.4-5.8-10.2-12.8-10.2-20.6 0-21.6 28.6-41.2 64-41.2 1.6 0 3.2.1 4.8.2 1.2-10.8 6.2-20.2 13.8-27.6 8.8-8.6 20.6-13.4 33.2-13.4 2.2 0 4.4.2 6.4.4-10.2 12.8-15.6 29.2-15.6 46.2 0 2.6.2 5.2.4 7.8-13.6 1.6-26.2 5.4-37.4 11-11.2 5.6-20.2 13-26.2 21.4 6.4 5.8 15.4 10 25.6 12.2 10.2 2.2 21.4 3.4 33 3.4 1.6 0 3.2-.1 4.8-.2-1.2 10.8-6.2 20.2-13.8 27.6-8.8 8.6-20.6 13.4-33.2 13.4-2.2 0-4.4-.2-6.4-.4 10.2-12.8 15.6-29.2 15.6-46.2 0-2.6-.2-5.2-.4-7.8z" />
<circle cx="64" cy="64" r="10.6" /> <circle cx="64" cy="64" r="10.6" />
</svg> </svg>
</div> </div>
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white"> <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white">
React Integration React Integration
</h1> </h1>
</div> </div>
<div className="prose prose-neutral dark:prose-invert max-w-none"> <div className="prose prose-invert max-w-none">
<p className="lead text-xl text-neutral-600 dark:text-neutral-400"> <p className="lead text-xl text-neutral-400">
For standard React SPAs (Create React App, Vite, etc.), you can simply add the script tag to your <code>index.html</code>. For standard React SPAs (Create React App, Vite, etc.), you can simply add the script tag to your <code>index.html</code>.
</p> </p>
<hr className="my-8 border-neutral-200 dark:border-neutral-800" /> <hr className="my-8 border-neutral-800" />
<h3>Method 1: index.html (Recommended)</h3> <h3>Method 1: index.html (Recommended)</h3>
<p> <p>

View File

@@ -8,9 +8,9 @@ export default function VueIntegrationPage() {
<div className="relative min-h-screen flex flex-col overflow-hidden"> <div className="relative min-h-screen flex flex-col overflow-hidden">
{/* * --- ATMOSPHERE (Background) --- */} {/* * --- ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -25,23 +25,23 @@ export default function VueIntegrationPage() {
</Link> </Link>
<div className="flex items-center gap-4 mb-8"> <div className="flex items-center gap-4 mb-8">
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl"> <div className="p-3 bg-neutral-800 rounded-xl">
<svg viewBox="0 0 128 128" className="w-10 h-10 text-[#4FC08D] fill-current"> <svg viewBox="0 0 128 128" className="w-10 h-10 text-[#4FC08D] fill-current">
<path d="M82.8 24.6h27.8L64 103.4 17.4 24.6h27.8L64 59.4l18.8-34.8z" /> <path d="M82.8 24.6h27.8L64 103.4 17.4 24.6h27.8L64 59.4l18.8-34.8z" />
<path d="M64 24.6H39L64 67.4l25-42.8H64z" fill="#35495E" /> <path d="M64 24.6H39L64 67.4l25-42.8H64z" fill="#35495E" />
</svg> </svg>
</div> </div>
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white"> <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white">
Vue.js Integration Vue.js Integration
</h1> </h1>
</div> </div>
<div className="prose prose-neutral dark:prose-invert max-w-none"> <div className="prose prose-invert max-w-none">
<p className="lead text-xl text-neutral-600 dark:text-neutral-400"> <p className="lead text-xl text-neutral-400">
Integrating Pulse with Vue.js is straightforward. You can add the script to your <code>index.html</code> file. Integrating Pulse with Vue.js is straightforward. You can add the script to your <code>index.html</code> file.
</p> </p>
<hr className="my-8 border-neutral-200 dark:border-neutral-800" /> <hr className="my-8 border-neutral-800" />
<h3>Method 1: index.html (Recommended)</h3> <h3>Method 1: index.html (Recommended)</h3>
<p> <p>

View File

@@ -8,9 +8,9 @@ export default function WordPressIntegrationPage() {
<div className="relative min-h-screen flex flex-col overflow-hidden"> <div className="relative min-h-screen flex flex-col overflow-hidden">
{/* * --- ATMOSPHERE (Background) --- */} {/* * --- ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -25,22 +25,22 @@ export default function WordPressIntegrationPage() {
</Link> </Link>
<div className="flex items-center gap-4 mb-8"> <div className="flex items-center gap-4 mb-8">
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl"> <div className="p-3 bg-neutral-800 rounded-xl">
<svg viewBox="0 0 128 128" className="w-10 h-10 text-[#21759B] fill-current"> <svg viewBox="0 0 128 128" className="w-10 h-10 text-[#21759B] fill-current">
<path d="M116.6 64c0-19.2-10.4-36-26-45.2l28.6 78.4c-1 3.2-2.2 6.2-3.6 9.2-11.4 12.4-27.8 20.2-46 20.2-6.2 0-12.2-.8-17.8-2.4l26.2-76.4c1.2.2 2.4.4 3.6.4 5.4 0 13.8-.8 13.8-.8 2.8-.2 3.2 4 .4 4.2 0 0-2.8.2-6 .4l19 56.6 5.4-18c2.4-7.4 4.2-12.8 4.2-17.4 0-6-2.2-10.2-7.6-12.6-2.8-1.2-2.2-5.4 1.4-5.4h4.4zM64 121.2c-15.8 0-30.2-6.4-40.8-16.8L46.6 36.8c-2.8-.2-5.8-.4-5.8-.4-2.8-.2-2.4-4.4.4-4.2 0 0 8.4.8 13.6.8 5.4 0 13.6-.8 13.6-.8 2.8-.2 3.2 4 .4 4.2 0 0-2.8.2-5.8.4l18.2 54.4 10.6-31.8L64 121.2zM11.4 64c0 17 8.2 32.2 20.8 41.8L18.8 66.8c-.8-3.4-1.2-6.6-1.2-9.2 0-6.8 2.6-13 6.2-17.8C15.6 47.4 11.4 55.2 11.4 64zM64 6.8c16.2 0 30.8 6.8 41.4 17.6-1.4-.2-2.8-.2-4.2-.2-7.8 0-14.2 1.4-14.2 1.4-2.8.6-2.2 4.8.6 4.2 0 0 5-1 10.6-1 2.2 0 4.6.2 6.6.4L88.2 53 71.4 6.8h-7.4z" /> <path d="M116.6 64c0-19.2-10.4-36-26-45.2l28.6 78.4c-1 3.2-2.2 6.2-3.6 9.2-11.4 12.4-27.8 20.2-46 20.2-6.2 0-12.2-.8-17.8-2.4l26.2-76.4c1.2.2 2.4.4 3.6.4 5.4 0 13.8-.8 13.8-.8 2.8-.2 3.2 4 .4 4.2 0 0-2.8.2-6 .4l19 56.6 5.4-18c2.4-7.4 4.2-12.8 4.2-17.4 0-6-2.2-10.2-7.6-12.6-2.8-1.2-2.2-5.4 1.4-5.4h4.4zM64 121.2c-15.8 0-30.2-6.4-40.8-16.8L46.6 36.8c-2.8-.2-5.8-.4-5.8-.4-2.8-.2-2.4-4.4.4-4.2 0 0 8.4.8 13.6.8 5.4 0 13.6-.8 13.6-.8 2.8-.2 3.2 4 .4 4.2 0 0-2.8.2-5.8.4l18.2 54.4 10.6-31.8L64 121.2zM11.4 64c0 17 8.2 32.2 20.8 41.8L18.8 66.8c-.8-3.4-1.2-6.6-1.2-9.2 0-6.8 2.6-13 6.2-17.8C15.6 47.4 11.4 55.2 11.4 64zM64 6.8c16.2 0 30.8 6.8 41.4 17.6-1.4-.2-2.8-.2-4.2-.2-7.8 0-14.2 1.4-14.2 1.4-2.8.6-2.2 4.8.6 4.2 0 0 5-1 10.6-1 2.2 0 4.6.2 6.6.4L88.2 53 71.4 6.8h-7.4z" />
</svg> </svg>
</div> </div>
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white"> <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white">
WordPress Integration WordPress Integration
</h1> </h1>
</div> </div>
<div className="prose prose-neutral dark:prose-invert max-w-none"> <div className="prose prose-invert max-w-none">
<p className="lead text-xl text-neutral-600 dark:text-neutral-400"> <p className="lead text-xl text-neutral-400">
You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin. You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin.
</p> </p>
<hr className="my-8 border-neutral-200 dark:border-neutral-800" /> <hr className="my-8 border-neutral-800" />
<h3>Method 1: Using a Plugin (Easiest)</h3> <h3>Method 1: Using a Plugin (Easiest)</h3>
<ol> <ol>

View File

@@ -8,19 +8,19 @@ export default function NotFound() {
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
{/* * Grid Pattern with Radial Mask */} {/* * Grid Pattern with Radial Mask */}
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
<div className="text-center px-4 z-10"> <div className="text-center px-4 z-10">
<h1 className="text-9xl font-bold text-transparent bg-clip-text bg-gradient-to-b from-neutral-900 to-neutral-500 dark:from-white dark:to-neutral-500 mb-4"> <h1 className="text-9xl font-bold text-transparent bg-clip-text bg-gradient-to-b from-white to-neutral-500 mb-4">
404 404
</h1> </h1>
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white mb-6"> <h2 className="text-2xl font-bold text-white mb-6">
Page not found Page not found
</h2> </h2>
<p className="text-lg text-neutral-600 dark:text-neutral-400 max-w-md mx-auto mb-10 leading-relaxed"> <p className="text-lg text-neutral-400 max-w-md mx-auto mb-10 leading-relaxed">
Sorry, we couldn't find the page you're looking for. It might have been moved or deleted. Sorry, we couldn't find the page you're looking for. It might have been moved or deleted.
</p> </p>

View File

@@ -27,14 +27,14 @@ function DashboardPreview() {
initial={{ opacity: 0, y: 40 }} initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.4 }} transition={{ duration: 0.7, delay: 0.4 }}
className="relative rounded-xl border border-neutral-200/50 dark:border-neutral-800/50 shadow-2xl overflow-hidden" className="relative rounded-xl border border-neutral-800/50 shadow-2xl overflow-hidden"
> >
{/* * Browser chrome */} {/* * Browser chrome */}
<div className="h-8 bg-neutral-100 dark:bg-neutral-800/80 border-b border-neutral-200 dark:border-white/5 flex items-center px-4 gap-2"> <div className="h-8 bg-neutral-800/80 border-b border-white/5 flex items-center px-4 gap-2">
<div className="w-3 h-3 rounded-full bg-red-400/60" /> <div className="w-3 h-3 rounded-full bg-red-400/60" />
<div className="w-3 h-3 rounded-full bg-yellow-400/60" /> <div className="w-3 h-3 rounded-full bg-yellow-400/60" />
<div className="w-3 h-3 rounded-full bg-green-400/60" /> <div className="w-3 h-3 rounded-full bg-green-400/60" />
<div className="ml-4 flex-1 max-w-xs h-5 rounded bg-neutral-200 dark:bg-neutral-700/50" /> <div className="ml-4 flex-1 max-w-xs h-5 rounded bg-neutral-700/50" />
</div> </div>
{/* * Screenshot with bottom fade */} {/* * Screenshot with bottom fade */}
@@ -47,7 +47,7 @@ function DashboardPreview() {
className="w-full h-auto object-cover object-top" className="w-full h-auto object-cover object-top"
priority priority
/> />
<div className="absolute inset-0 pointer-events-none bg-gradient-to-b from-transparent from-60% to-white dark:to-neutral-950" /> <div className="absolute inset-0 pointer-events-none bg-gradient-to-b from-transparent from-60% to-neutral-950" />
</div> </div>
</motion.div> </motion.div>
</div> </div>
@@ -59,28 +59,28 @@ function ComparisonSection() {
return ( return (
<div className="w-full max-w-4xl mx-auto mb-32"> <div className="w-full max-w-4xl mx-auto mb-32">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white mb-4">Why choose Pulse?</h2> <h2 className="text-2xl font-bold text-white mb-4">Why choose Pulse?</h2>
<p className="text-neutral-500">The lightweight, privacy-friendly alternative.</p> <p className="text-neutral-500">The lightweight, privacy-friendly alternative.</p>
</div> </div>
<div className="overflow-hidden rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm"> <div className="overflow-hidden rounded-2xl border border-neutral-800 bg-neutral-900/50 backdrop-blur-sm">
<table className="w-full text-left border-collapse"> <table className="w-full text-left border-collapse">
<thead> <thead>
<tr className="border-b border-neutral-200 dark:border-neutral-800"> <tr className="border-b border-neutral-800">
<th className="p-6 text-sm font-medium text-neutral-500">Feature</th> <th className="p-6 text-sm font-medium text-neutral-500">Feature</th>
<th className="p-6 text-sm font-bold text-brand-orange">Pulse</th> <th className="p-6 text-sm font-bold text-brand-orange">Pulse</th>
<th className="p-6 text-sm font-medium text-neutral-500">Google Analytics</th> <th className="p-6 text-sm font-medium text-neutral-500">Google Analytics</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-neutral-200 dark:divide-neutral-800"> <tbody className="divide-y divide-neutral-800">
{[ {[
{ feature: "Cookie Banner Required", pulse: false, ga: true }, { feature: "Cookie Banner Required", pulse: false, ga: true },
{ feature: "GDPR Compliant", pulse: true, ga: "Complex" }, { feature: "GDPR Compliant", pulse: true, ga: "Complex" },
{ feature: "Script Size", pulse: "< 1 KB", ga: "45 KB+" }, { feature: "Script Size", pulse: "< 1 KB", ga: "45 KB+" },
{ feature: "Data Ownership", pulse: "Yours", ga: "Google's" }, { feature: "Data Ownership", pulse: "Yours", ga: "Google's" },
].map((row) => ( ].map((row) => (
<tr key={row.feature} className="hover:bg-neutral-50/50 dark:hover:bg-neutral-800/50 transition-colors"> <tr key={row.feature} className="hover:bg-neutral-800/50 transition-colors">
<td className="p-6 text-neutral-900 dark:text-white font-medium">{row.feature}</td> <td className="p-6 text-white font-medium">{row.feature}</td>
<td className="p-6"> <td className="p-6">
{row.pulse === true ? ( {row.pulse === true ? (
<CheckCircleIcon className="w-5 h-5 text-green-500" /> <CheckCircleIcon className="w-5 h-5 text-green-500" />
@@ -237,10 +237,10 @@ export default function HomePage() {
{/* * --- 1. ATMOSPHERE (Background) --- */} {/* * --- 1. ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
{/* * Bottom-right Neutral Glow */} {/* * Bottom-right Neutral Glow */}
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
{/* * Grid Pattern with Radial Mask */} {/* * Grid Pattern with Radial Mask */}
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -266,7 +266,7 @@ export default function HomePage() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }} transition={{ duration: 0.5, delay: 0.1 }}
className="text-5xl md:text-7xl font-bold tracking-tight text-neutral-900 dark:text-white mb-6" className="text-5xl md:text-7xl font-bold tracking-tight text-white mb-6"
> >
Simple analytics for <br /> Simple analytics for <br />
<span className="relative inline-block"> <span className="relative inline-block">
@@ -283,7 +283,7 @@ export default function HomePage() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }} transition={{ duration: 0.5, delay: 0.2 }}
className="text-xl text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto mb-10 leading-relaxed" className="text-xl text-neutral-400 max-w-2xl mx-auto mb-10 leading-relaxed"
> >
Respect your users' privacy while getting the insights you need. Respect your users' privacy while getting the insights you need.
No cookies, no IP tracking, fully GDPR compliant. No cookies, no IP tracking, fully GDPR compliant.
@@ -326,8 +326,8 @@ export default function HomePage() {
<div className="w-12 h-12 rounded-xl bg-brand-orange/10 flex items-center justify-center mb-6 text-brand-orange group-hover:scale-110 transition-transform duration-300"> <div className="w-12 h-12 rounded-xl bg-brand-orange/10 flex items-center justify-center mb-6 text-brand-orange group-hover:scale-110 transition-transform duration-300">
<feature.icon className="w-6 h-6" /> <feature.icon className="w-6 h-6" />
</div> </div>
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-3">{feature.title}</h3> <h3 className="text-xl font-bold text-white mb-3">{feature.title}</h3>
<p className="text-neutral-600 dark:text-neutral-400 leading-relaxed"> <p className="text-neutral-400 leading-relaxed">
{feature.desc} {feature.desc}
</p> </p>
</motion.div> </motion.div>
@@ -345,7 +345,7 @@ export default function HomePage() {
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="text-center mb-20" className="text-center mb-20"
> >
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white mb-6">Ready to switch?</h2> <h2 className="text-2xl font-bold text-white mb-6">Ready to switch?</h2>
<Button onClick={() => initiateOAuthFlow()} variant="primary" className="px-8 py-4 text-lg shadow-lg shadow-brand-orange/20"> <Button onClick={() => initiateOAuthFlow()} variant="primary" className="px-8 py-4 text-lg shadow-lg shadow-brand-orange/20">
Start your free trial Start your free trial
</Button> </Button>
@@ -365,8 +365,8 @@ export default function HomePage() {
return ( return (
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8"> <div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
{showFinishSetupBanner && ( {showFinishSetupBanner && (
<div className="mb-6 flex items-center justify-between gap-4 rounded-2xl border border-brand-orange/30 bg-brand-orange/5 px-4 py-3 dark:bg-brand-orange/10"> <div className="mb-6 flex items-center justify-between gap-4 rounded-2xl border border-brand-orange/30 bg-brand-orange/10 px-4 py-3">
<p className="text-sm text-neutral-700 dark:text-neutral-300"> <p className="text-sm text-neutral-300">
Finish setting up your workspace and add your first site. Finish setting up your workspace and add your first site.
</p> </p>
<div className="flex items-center gap-2 flex-shrink-0"> <div className="flex items-center gap-2 flex-shrink-0">
@@ -381,7 +381,7 @@ export default function HomePage() {
if (typeof window !== 'undefined') localStorage.setItem('pulse_welcome_completed', 'true') if (typeof window !== 'undefined') localStorage.setItem('pulse_welcome_completed', 'true')
setShowFinishSetupBanner(false) setShowFinishSetupBanner(false)
}} }}
className="text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-400 p-1 rounded" className="text-neutral-500 hover:text-neutral-400 p-1 rounded"
aria-label="Dismiss" aria-label="Dismiss"
> >
<XIcon className="h-4 w-4" /> <XIcon className="h-4 w-4" />
@@ -392,8 +392,8 @@ export default function HomePage() {
<div className="mb-8 flex items-center justify-between"> <div className="mb-8 flex items-center justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white">Your Sites</h1> <h1 className="text-2xl font-bold text-white">Your Sites</h1>
<p className="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Manage your analytics sites and view insights.</p> <p className="mt-1 text-sm text-neutral-400">Manage your analytics sites and view insights.</p>
</div> </div>
{(() => { {(() => {
const siteLimit = getSitesLimitForPlan(subscription?.plan_id) const siteLimit = getSitesLimitForPlan(subscription?.plan_id)
@@ -401,7 +401,7 @@ export default function HomePage() {
return atLimit ? ( return atLimit ? (
<div> <div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-sm font-medium text-neutral-500 dark:text-neutral-400 bg-neutral-100 dark:bg-neutral-800 px-3 py-1.5 rounded-lg border border-neutral-200 dark:border-neutral-700"> <span className="text-sm font-medium text-neutral-400 bg-neutral-800 px-3 py-1.5 rounded-lg border border-neutral-700">
Limit reached ({sites.length}/{siteLimit}) Limit reached ({sites.length}/{siteLimit})
</span> </span>
<Link href="/pricing"> <Link href="/pricing">
@@ -411,7 +411,7 @@ export default function HomePage() {
</Link> </Link>
</div> </div>
{deletedSites.length > 0 && ( {deletedSites.length > 0 && (
<p className="text-sm text-neutral-500 dark:text-neutral-400 mt-2"> <p className="text-sm text-neutral-400 mt-2">
You have a site pending deletion. Restore it or permanently delete it to free the slot. You have a site pending deletion. Restore it or permanently delete it to free the slot.
</p> </p>
)} )}
@@ -428,26 +428,26 @@ export default function HomePage() {
{/* * Global Overview - min-h ensures no layout shift when Plan & usage loads */} {/* * Global Overview - min-h ensures no layout shift when Plan & usage loads */}
<div className="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-3"> <div className="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div className="flex min-h-[100px] sm:min-h-[160px] flex-col rounded-2xl border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900"> <div className="flex min-h-[100px] sm:min-h-[160px] flex-col rounded-2xl border border-neutral-800 bg-neutral-900 p-4">
<p className="text-sm text-neutral-500 dark:text-neutral-400">Total Sites</p> <p className="text-sm text-neutral-400">Total Sites</p>
<p className="text-2xl font-bold text-neutral-900 dark:text-white">{sites.length}</p> <p className="text-2xl font-bold text-white">{sites.length}</p>
</div> </div>
<div className="flex min-h-[100px] sm:min-h-[160px] flex-col rounded-2xl border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900"> <div className="flex min-h-[100px] sm:min-h-[160px] flex-col rounded-2xl border border-neutral-800 bg-neutral-900 p-4">
<p className="text-sm text-neutral-500 dark:text-neutral-400">Total Visitors (24h)</p> <p className="text-sm text-neutral-400">Total Visitors (24h)</p>
<p className="text-2xl font-bold text-neutral-900 dark:text-white"> <p className="text-2xl font-bold text-white">
{sites.length === 0 || Object.keys(siteStats).length < sites.length {sites.length === 0 || Object.keys(siteStats).length < sites.length
? '--' ? '--'
: Object.values(siteStats).reduce((sum, { stats }) => sum + (stats?.visitors ?? 0), 0).toLocaleString()} : Object.values(siteStats).reduce((sum, { stats }) => sum + (stats?.visitors ?? 0), 0).toLocaleString()}
</p> </p>
</div> </div>
<div className="flex min-h-[160px] flex-col rounded-2xl border border-neutral-200 bg-brand-orange/10 p-4 dark:border-neutral-800"> <div className="flex min-h-[160px] flex-col rounded-2xl border border-neutral-800 bg-brand-orange/10 p-4">
<p className="text-sm text-brand-orange">Plan & usage</p> <p className="text-sm text-brand-orange">Plan & usage</p>
{subscriptionLoading ? ( {subscriptionLoading ? (
<div className="animate-pulse space-y-2"> <div className="animate-pulse space-y-2">
<div className="h-6 w-24 rounded bg-brand-orange/25 dark:bg-brand-orange/20" /> <div className="h-6 w-24 rounded bg-brand-orange/20" />
<div className="h-4 w-full rounded bg-brand-orange/25 dark:bg-brand-orange/20" /> <div className="h-4 w-full rounded bg-brand-orange/20" />
<div className="h-4 w-3/4 rounded bg-brand-orange/25 dark:bg-brand-orange/20" /> <div className="h-4 w-3/4 rounded bg-brand-orange/20" />
<div className="h-4 w-20 rounded bg-brand-orange/25 dark:bg-brand-orange/20 pt-2" /> <div className="h-4 w-20 rounded bg-brand-orange/20 pt-2" />
</div> </div>
) : subscription ? ( ) : subscription ? (
<> <>
@@ -464,7 +464,7 @@ export default function HomePage() {
})()} })()}
</p> </p>
{(typeof subscription.sites_count === 'number' || (subscription.pageview_limit > 0 && typeof subscription.pageview_usage === 'number') || (subscription.next_invoice_amount_due != null && subscription.next_invoice_currency && !subscription.cancel_at_period_end && (subscription.subscription_status === 'active' || subscription.subscription_status === 'trialing'))) && ( {(typeof subscription.sites_count === 'number' || (subscription.pageview_limit > 0 && typeof subscription.pageview_usage === 'number') || (subscription.next_invoice_amount_due != null && subscription.next_invoice_currency && !subscription.cancel_at_period_end && (subscription.subscription_status === 'active' || subscription.subscription_status === 'trialing'))) && (
<p className="text-sm text-neutral-600 dark:text-neutral-400 mt-1"> <p className="text-sm text-neutral-400 mt-1">
{typeof subscription.sites_count === 'number' && ( {typeof subscription.sites_count === 'number' && (
<span>Sites: {(() => { <span>Sites: {(() => {
const limit = getSitesLimitForPlan(subscription.plan_id) const limit = getSitesLimitForPlan(subscription.plan_id)
@@ -512,12 +512,12 @@ export default function HomePage() {
</div> </div>
{!sitesLoading && sites.length === 0 && ( {!sitesLoading && sites.length === 0 && (
<div className="mb-8 rounded-2xl border-2 border-dashed border-brand-orange/30 bg-brand-orange/5 p-6 text-center dark:bg-brand-orange/10"> <div className="mb-8 rounded-2xl border-2 border-dashed border-brand-orange/30 bg-brand-orange/10 p-6 text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-brand-orange/20 text-brand-orange mb-4"> <div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-brand-orange/20 text-brand-orange mb-4">
<GlobeIcon className="h-7 w-7" /> <GlobeIcon className="h-7 w-7" />
</div> </div>
<h2 className="text-xl font-bold text-neutral-900 dark:text-white mb-2">Add your first site</h2> <h2 className="text-xl font-bold text-white mb-2">Add your first site</h2>
<p className="text-neutral-600 dark:text-neutral-400 mb-6 max-w-md mx-auto"> <p className="text-neutral-400 mb-6 max-w-md mx-auto">
Connect a domain to start collecting privacy-friendly analytics. You can add more sites later from the dashboard. Connect a domain to start collecting privacy-friendly analytics. You can add more sites later from the dashboard.
</p> </p>
<Link href="/sites/new"> <Link href="/sites/new">
@@ -553,31 +553,31 @@ export default function HomePage() {
{deletedSites.length > 0 && ( {deletedSites.length > 0 && (
<div className="mt-8"> <div className="mt-8">
<h3 className="text-sm font-medium text-neutral-500 dark:text-neutral-400 mb-4">Scheduled for Deletion</h3> <h3 className="text-sm font-medium text-neutral-400 mb-4">Scheduled for Deletion</h3>
<div className="space-y-3"> <div className="space-y-3">
{deletedSites.map((site) => { {deletedSites.map((site) => {
const purgeAt = site.deleted_at ? new Date(new Date(site.deleted_at).getTime() + 7 * 24 * 60 * 60 * 1000) : null const purgeAt = site.deleted_at ? new Date(new Date(site.deleted_at).getTime() + 7 * 24 * 60 * 60 * 1000) : null
const daysLeft = purgeAt ? Math.max(0, Math.ceil((purgeAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24))) : 0 const daysLeft = purgeAt ? Math.max(0, Math.ceil((purgeAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24))) : 0
return ( return (
<div key={site.id} className="flex items-center justify-between p-4 rounded-xl border border-neutral-200 dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900/50 opacity-60"> <div key={site.id} className="flex items-center justify-between p-4 rounded-xl border border-neutral-800 bg-neutral-900/50 opacity-60">
<div> <div>
<span className="font-medium text-neutral-700 dark:text-neutral-300">{site.name}</span> <span className="font-medium text-neutral-300">{site.name}</span>
<span className="ml-2 text-sm text-neutral-400">{site.domain}</span> <span className="ml-2 text-sm text-neutral-400">{site.domain}</span>
<span className="ml-3 inline-flex items-center rounded-full bg-red-50 px-2 py-0.5 text-xs font-medium text-red-600 dark:bg-red-900/20 dark:text-red-400"> <span className="ml-3 inline-flex items-center rounded-full bg-red-900/20 px-2 py-0.5 text-xs font-medium text-red-400">
Deleting in {daysLeft} day{daysLeft !== 1 ? 's' : ''} Deleting in {daysLeft} day{daysLeft !== 1 ? 's' : ''}
</span> </span>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => handleRestore(site.id)} onClick={() => handleRestore(site.id)}
className="px-3 py-1.5 text-xs font-medium text-neutral-700 dark:text-neutral-300 border border-neutral-300 dark:border-neutral-700 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors" className="px-3 py-1.5 text-xs font-medium text-neutral-300 border border-neutral-700 rounded-lg hover:bg-neutral-800 transition-colors"
> >
Restore Restore
</button> </button>
<button <button
onClick={() => handlePermanentDelete(site.id)} onClick={() => handlePermanentDelete(site.id)}
className="px-3 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 border border-red-200 dark:border-red-900 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors" className="px-3 py-1.5 text-xs font-medium text-red-400 border border-red-900 rounded-lg hover:bg-red-900/20 transition-colors"
> >
Delete Now Delete Now
</button> </button>

View File

@@ -19,8 +19,8 @@ export default function PricingPage() {
<Suspense fallback={ <Suspense fallback={
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-16"> <div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-16">
<div className="text-center mb-12"> <div className="text-center mb-12">
<div className="h-10 w-64 animate-pulse rounded bg-neutral-100 dark:bg-neutral-800 mx-auto mb-4" /> <div className="h-10 w-64 animate-pulse rounded bg-neutral-800 mx-auto mb-4" />
<div className="h-5 w-96 animate-pulse rounded bg-neutral-100 dark:bg-neutral-800 mx-auto" /> <div className="h-5 w-96 animate-pulse rounded bg-neutral-800 mx-auto" />
</div> </div>
<PricingCardsSkeleton /> <PricingCardsSkeleton />
</div> </div>

View File

@@ -39,9 +39,9 @@ export function IntegrationGuide({ integration, children }: IntegrationGuideProp
<div className="relative min-h-screen flex flex-col overflow-hidden"> <div className="relative min-h-screen flex flex-col overflow-hidden">
{/* * --- ATMOSPHERE (Background) --- */} {/* * --- ATMOSPHERE (Background) --- */}
<div className="absolute inset-0 -z-10 pointer-events-none"> <div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 dark:bg-neutral-400/10 rounded-full blur-[128px] opacity-40" /> <div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-400/10 rounded-full blur-[128px] opacity-40" />
<div <div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]" className="absolute inset-0 bg-grid-pattern opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }} style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/> />
</div> </div>
@@ -56,18 +56,18 @@ export function IntegrationGuide({ integration, children }: IntegrationGuideProp
</Link> </Link>
<div className="flex items-center gap-4 mb-8"> <div className="flex items-center gap-4 mb-8">
<div className="p-3 bg-neutral-100 dark:bg-neutral-800 rounded-xl"> <div className="p-3 bg-neutral-800 rounded-xl">
{headerIcon} {headerIcon}
</div> </div>
<h1 className="text-4xl md:text-5xl font-bold text-neutral-900 dark:text-white"> <h1 className="text-4xl md:text-5xl font-bold text-white">
{integration.name} Integration {integration.name} Integration
</h1> </h1>
</div> </div>
<div className="prose prose-neutral dark:prose-invert max-w-none"> <div className="prose prose-invert max-w-none">
{children} {children}
<hr className="my-8 border-neutral-200 dark:border-neutral-800" /> <hr className="my-8 border-neutral-800" />
<h3>Optional: Frustration Tracking</h3> <h3>Optional: Frustration Tracking</h3>
<p> <p>
Detect rage clicks and dead clicks by adding the frustration tracking Detect rage clicks and dead clicks by adding the frustration tracking
@@ -82,8 +82,8 @@ export function IntegrationGuide({ integration, children }: IntegrationGuideProp
{/* * --- Related Integrations --- */} {/* * --- Related Integrations --- */}
{relatedIntegrations.length > 0 && ( {relatedIntegrations.length > 0 && (
<div className="mt-16 pt-10 border-t border-neutral-200 dark:border-neutral-800"> <div className="mt-16 pt-10 border-t border-neutral-800">
<h2 className="text-xl font-bold text-neutral-900 dark:text-white mb-6"> <h2 className="text-xl font-bold text-white mb-6">
Related Integrations Related Integrations
</h2> </h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
@@ -91,16 +91,16 @@ export function IntegrationGuide({ integration, children }: IntegrationGuideProp
<Link <Link
key={related.id} key={related.id}
href={`/integrations/${related.id}`} href={`/integrations/${related.id}`}
className="group flex items-center gap-4 p-4 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-xl hover:border-brand-orange/50 dark:hover:border-brand-orange/50 transition-all duration-300" className="group flex items-center gap-4 p-4 bg-neutral-900/50 backdrop-blur-sm border border-neutral-800 rounded-xl hover:border-brand-orange/50 transition-all duration-300"
> >
<div className="p-2 bg-neutral-100 dark:bg-neutral-800 rounded-lg shrink-0 [&_svg]:w-6 [&_svg]:h-6"> <div className="p-2 bg-neutral-800 rounded-lg shrink-0 [&_svg]:w-6 [&_svg]:h-6">
{related.icon} {related.icon}
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<span className="font-semibold text-neutral-900 dark:text-white block"> <span className="font-semibold text-white block">
{related.name} {related.name}
</span> </span>
<span className="text-sm text-neutral-500 dark:text-neutral-400 truncate block"> <span className="text-sm text-neutral-400 truncate block">
{related.description} {related.description}
</span> </span>
</div> </div>

View File

@@ -219,10 +219,10 @@ export default function PricingSection() {
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="text-center mb-12" className="text-center mb-12"
> >
<h2 className="text-3xl font-bold text-neutral-900 dark:text-white mb-4"> <h2 className="text-3xl font-bold text-white mb-4">
Transparent Pricing Transparent Pricing
</h2> </h2>
<p className="text-lg text-neutral-600 dark:text-neutral-400"> <p className="text-lg text-neutral-400">
Scale with your traffic. No hidden fees. Scale with your traffic. No hidden fees.
</p> </p>
</motion.div> </motion.div>
@@ -232,13 +232,13 @@ export default function PricingSection() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }} transition={{ duration: 0.5, delay: 0.1 }}
className="max-w-6xl mx-auto border border-neutral-200 dark:border-neutral-800 rounded-2xl bg-white/50 dark:bg-neutral-900/50 backdrop-blur-xl shadow-sm overflow-hidden mb-20" className="max-w-6xl mx-auto border border-neutral-800 rounded-2xl bg-neutral-900/50 backdrop-blur-xl shadow-sm overflow-hidden mb-20"
> >
{/* Top Toolbar */} {/* Top Toolbar */}
<div className="p-6 border-b border-neutral-200 dark:border-neutral-800 flex flex-col md:flex-row items-center justify-between gap-8 bg-neutral-50/50 dark:bg-neutral-900/50"> <div className="p-6 border-b border-neutral-800 flex flex-col md:flex-row items-center justify-between gap-8 bg-neutral-900/50">
<div className="w-full md:w-2/3"> <div className="w-full md:w-2/3">
<div className="flex justify-between text-sm font-medium text-neutral-500 dark:text-neutral-400 mb-4"> <div className="flex justify-between text-sm font-medium text-neutral-400 mb-4">
<span>10k</span> <span>10k</span>
<span className="text-brand-orange font-bold text-lg"> <span className="text-brand-orange font-bold text-lg">
Up to {currentTraffic.label} monthly pageviews Up to {currentTraffic.label} monthly pageviews
@@ -254,23 +254,23 @@ export default function PricingSection() {
onChange={(e) => setSliderIndex(parseInt(e.target.value))} onChange={(e) => setSliderIndex(parseInt(e.target.value))}
aria-label="Monthly pageview limit" aria-label="Monthly pageview limit"
aria-valuetext={`${currentTraffic.label} pageviews per month`} aria-valuetext={`${currentTraffic.label} pageviews per month`}
className="w-full h-2 bg-neutral-200 rounded-lg appearance-none cursor-pointer dark:bg-neutral-700 accent-brand-orange focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2" className="w-full h-2 bg-neutral-700 rounded-lg appearance-none cursor-pointer accent-brand-orange focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2"
/> />
</div> </div>
<div className="flex flex-col items-end gap-2 shrink-0"> <div className="flex flex-col items-end gap-2 shrink-0">
<span className="text-xs text-neutral-500 dark:text-neutral-400 font-medium uppercase tracking-wide"> <span className="text-xs text-neutral-400 font-medium uppercase tracking-wide">
Get 1 month free with yearly Get 1 month free with yearly
</span> </span>
<div className="bg-neutral-200 dark:bg-neutral-800 p-1 rounded-lg flex" role="radiogroup" aria-label="Billing interval"> <div className="bg-neutral-800 p-1 rounded-lg flex" role="radiogroup" aria-label="Billing interval">
<button <button
onClick={() => setIsYearly(false)} onClick={() => setIsYearly(false)}
role="radio" role="radio"
aria-checked={!isYearly} aria-checked={!isYearly}
className={`min-w-[88px] px-4 py-2 rounded-lg text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange ${ className={`min-w-[88px] px-4 py-2 rounded-lg text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange ${
!isYearly !isYearly
? 'bg-white dark:bg-neutral-700 text-neutral-900 dark:text-white shadow-sm' ? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-500 hover:text-neutral-900 dark:hover:text-white' : 'text-neutral-500 hover:text-white'
}`} }`}
> >
Monthly Monthly
@@ -281,8 +281,8 @@ export default function PricingSection() {
aria-checked={isYearly} aria-checked={isYearly}
className={`min-w-[88px] px-4 py-2 rounded-lg text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange ${ className={`min-w-[88px] px-4 py-2 rounded-lg text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange ${
isYearly isYearly
? 'bg-white dark:bg-neutral-700 text-neutral-900 dark:text-white shadow-sm' ? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-500 hover:text-neutral-900 dark:hover:text-white' : 'text-neutral-500 hover:text-white'
}`} }`}
> >
Yearly Yearly
@@ -292,15 +292,15 @@ export default function PricingSection() {
</div> </div>
{/* Pricing Grid */} {/* Pricing Grid */}
<div className="grid md:grid-cols-5 divide-y md:divide-y-0 md:divide-x divide-neutral-200 dark:divide-neutral-800"> <div className="grid md:grid-cols-5 divide-y md:divide-y-0 md:divide-x divide-neutral-800">
{/* Free Plan */} {/* Free Plan */}
<div className="p-6 flex flex-col relative transition-colors hover:bg-neutral-50/50 dark:hover:bg-neutral-800/50"> <div className="p-6 flex flex-col relative transition-colors hover:bg-neutral-800/50">
<div className="mb-8"> <div className="mb-8">
<h3 className="text-lg font-bold text-neutral-900 dark:text-white mb-2">Free</h3> <h3 className="text-lg font-bold text-white mb-2">Free</h3>
<p className="text-sm text-neutral-500 dark:text-neutral-400 min-h-[40px] mb-4">For trying Pulse on a personal project</p> <p className="text-sm text-neutral-400 min-h-[40px] mb-4">For trying Pulse on a personal project</p>
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
<span className="text-4xl font-bold text-neutral-900 dark:text-white">0</span> <span className="text-4xl font-bold text-white">0</span>
<span className="text-neutral-500 dark:text-neutral-400 font-medium">/forever</span> <span className="text-neutral-400 font-medium">/forever</span>
</div> </div>
</div> </div>
@@ -320,7 +320,7 @@ export default function PricingSection() {
<ul className="space-y-4 flex-grow"> <ul className="space-y-4 flex-grow">
{['1 site', '5k monthly pageviews', '6 months data retention', '100% Data ownership'].map((feature) => ( {['1 site', '5k monthly pageviews', '6 months data retention', '100% Data ownership'].map((feature) => (
<li key={feature} className="flex items-start gap-3 text-sm text-neutral-600 dark:text-neutral-400"> <li key={feature} className="flex items-start gap-3 text-sm text-neutral-400">
<CheckCircleIcon className="w-5 h-5 shrink-0 text-neutral-400" /> <CheckCircleIcon className="w-5 h-5 shrink-0 text-neutral-400" />
<span>{feature}</span> <span>{feature}</span>
</li> </li>
@@ -333,7 +333,7 @@ export default function PricingSection() {
const isTeam = plan.id === 'team' const isTeam = plan.id === 'team'
return ( return (
<div key={plan.id} className={`p-6 flex flex-col relative transition-colors ${isTeam ? 'bg-brand-orange/[0.02]' : 'hover:bg-neutral-50/50 dark:hover:bg-neutral-800/50'}`}> <div key={plan.id} className={`p-6 flex flex-col relative transition-colors ${isTeam ? 'bg-brand-orange/[0.02]' : 'hover:bg-neutral-800/50'}`}>
{isTeam && ( {isTeam && (
<> <>
<div className="absolute top-0 left-0 w-full h-1 bg-brand-orange" /> <div className="absolute top-0 left-0 w-full h-1 bg-brand-orange" />
@@ -344,17 +344,17 @@ export default function PricingSection() {
)} )}
<div className="mb-8"> <div className="mb-8">
<h3 className="text-lg font-bold text-neutral-900 dark:text-white mb-2">{plan.name}</h3> <h3 className="text-lg font-bold text-white mb-2">{plan.name}</h3>
<p className="text-sm text-neutral-500 dark:text-neutral-400 min-h-[40px] mb-4">{plan.description}</p> <p className="text-sm text-neutral-400 min-h-[40px] mb-4">{plan.description}</p>
{priceDetails ? ( {priceDetails ? (
isYearly ? ( isYearly ? (
<div> <div>
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
<span className="text-4xl font-bold text-neutral-900 dark:text-white"> <span className="text-4xl font-bold text-white">
{priceDetails.yearlyTotal} {priceDetails.yearlyTotal}
</span> </span>
<span className="text-neutral-500 dark:text-neutral-400 font-medium">/year</span> <span className="text-neutral-400 font-medium">/year</span>
</div> </div>
<div className="flex items-center gap-2 mt-2 text-sm font-medium"> <div className="flex items-center gap-2 mt-2 text-sm font-medium">
<span className="text-neutral-400 line-through decoration-neutral-400"> <span className="text-neutral-400 line-through decoration-neutral-400">
@@ -367,14 +367,14 @@ export default function PricingSection() {
</div> </div>
) : ( ) : (
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
<span className="text-4xl font-bold text-neutral-900 dark:text-white"> <span className="text-4xl font-bold text-white">
{priceDetails.baseMonthly} {priceDetails.baseMonthly}
</span> </span>
<span className="text-neutral-500 dark:text-neutral-400 font-medium">/mo</span> <span className="text-neutral-400 font-medium">/mo</span>
</div> </div>
) )
) : ( ) : (
<div className="text-4xl font-bold text-neutral-900 dark:text-white"> <div className="text-4xl font-bold text-white">
Custom Custom
</div> </div>
)} )}
@@ -391,7 +391,7 @@ export default function PricingSection() {
<ul className="space-y-4 flex-grow"> <ul className="space-y-4 flex-grow">
{plan.features.map((feature) => ( {plan.features.map((feature) => (
<li key={feature} className="flex items-start gap-3 text-sm text-neutral-600 dark:text-neutral-400"> <li key={feature} className="flex items-start gap-3 text-sm text-neutral-400">
<CheckCircleIcon className={`w-5 h-5 shrink-0 ${isTeam ? 'text-brand-orange' : 'text-neutral-400'}`} /> <CheckCircleIcon className={`w-5 h-5 shrink-0 ${isTeam ? 'text-brand-orange' : 'text-neutral-400'}`} />
<span>{feature}</span> <span>{feature}</span>
</li> </li>
@@ -402,11 +402,11 @@ export default function PricingSection() {
})} })}
{/* Enterprise Section */} {/* Enterprise Section */}
<div className="p-6 bg-neutral-50/50 dark:bg-neutral-900/50 flex flex-col"> <div className="p-6 bg-neutral-900/50 flex flex-col">
<div className="mb-8"> <div className="mb-8">
<h3 className="text-lg font-bold text-neutral-900 dark:text-white mb-2">Enterprise</h3> <h3 className="text-lg font-bold text-white mb-2">Enterprise</h3>
<p className="text-sm text-neutral-500 dark:text-neutral-400 min-h-[40px] mb-4">For high volume sites and custom needs</p> <p className="text-sm text-neutral-400 min-h-[40px] mb-4">For high volume sites and custom needs</p>
<div className="text-4xl font-bold text-neutral-900 dark:text-white"> <div className="text-4xl font-bold text-white">
Custom Custom
</div> </div>
</div> </div>
@@ -428,7 +428,7 @@ export default function PricingSection() {
'Managed Proxy', 'Managed Proxy',
'Raw data export' 'Raw data export'
].map((feature) => ( ].map((feature) => (
<li key={feature} className="flex items-start gap-3 text-sm text-neutral-600 dark:text-neutral-400"> <li key={feature} className="flex items-start gap-3 text-sm text-neutral-400">
<CheckCircleIcon className="w-5 h-5 text-neutral-400 shrink-0" /> <CheckCircleIcon className="w-5 h-5 text-neutral-400 shrink-0" />
<span>{feature}</span> <span>{feature}</span>
</li> </li>