chore: bump version to 0.1.3 and update @ciphera-net/ui dependency to 0.0.46; refine feature descriptions for clarity and consistency

This commit is contained in:
Usman Baig
2026-02-07 01:49:46 +01:00
parent f734252fb1
commit 6f6fc67eea
3 changed files with 147 additions and 207 deletions

View File

@@ -4,8 +4,9 @@
* @file Features / Product Tour page. * @file Features / Product Tour page.
* *
* Comprehensive showcase of everything Pulse offers — designed to convert * Comprehensive showcase of everything Pulse offers — designed to convert
* visitors who land here from search or internal navigation. Each feature * visitors who land here from search or internal navigation. Uses mixed
* section uses consistent glass-morphism cards with staggered animations. * layout styles (cards, inline lists, numbered steps, split sections) to
* avoid visual monotony.
*/ */
import Link from 'next/link' import Link from 'next/link'
@@ -18,115 +19,89 @@ import {
ZapIcon, ZapIcon,
GlobeIcon, GlobeIcon,
Share2Icon, Share2Icon,
EyeIcon,
ArrowRightIcon, ArrowRightIcon,
} from '@ciphera-net/ui' } from '@ciphera-net/ui'
// * Feature definitions grouped by section // * Pillar features (top 3 cards — matches landing page)
const heroFeatures = [ const pillars = [
{ {
icon: LockIcon, icon: LockIcon,
title: 'Privacy First', title: 'Privacy First',
description: description:
'No cookies, no IP tracking, no fingerprinting. Fully GDPR, CCPA, and PECR compliant out of the box — no cookie banner required.', 'No cookies, no IP tracking, no fingerprinting. Fully GDPR, CCPA, and PECR compliant — no cookie banner required.',
}, },
{ {
icon: BarChartIcon, icon: BarChartIcon,
title: 'Simple Dashboard', title: 'Simple Dashboard',
description: description:
'One clear dashboard with everything you need. Page views, unique visitors, referral sources, and top pages — no learning curve.', 'One clear dashboard with everything you need. Page views, visitors, referral sources, and top pages — no learning curve.',
}, },
{ {
icon: ZapIcon, icon: ZapIcon,
title: 'Lightweight Script', title: 'Lightweight Script',
description: description:
'Less than 1 KB. Our tracking script loads in milliseconds and has zero impact on your Lighthouse score or Core Web Vitals.', 'Less than 1 KB. Loads in milliseconds with zero impact on your Lighthouse score or Core Web Vitals.',
}, },
] ]
const coreCapabilities = [ // * Core capabilities — rendered as an icon + text list, NOT cards
const capabilities = [
{ {
icon: ( icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"> <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3v11.25A2.25 2.25 0 0 0 6 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0 1 18 16.5h-2.25m-7.5 0h7.5m-7.5 0-1 3m8.5-3 1 3m0 0 .5 1.5m-.5-1.5h-9.5m0 0-.5 1.5m.75-9 3-3 2.148 2.148A12.061 12.061 0 0 1 16.5 7.605" /> <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3v11.25A2.25 2.25 0 0 0 6 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0 1 18 16.5h-2.25m-7.5 0h7.5m-7.5 0-1 3m8.5-3 1 3m0 0 .5 1.5m-.5-1.5h-9.5m0 0-.5 1.5m.75-9 3-3 2.148 2.148A12.061 12.061 0 0 1 16.5 7.605" />
</svg> </svg>
), ),
title: 'Real-Time Analytics', title: 'Real-Time Analytics',
description: 'Watch visitors arrive on your site in real time. See active pages, live referrers, and current visitor counts — no delay.', description: 'Watch visitors arrive live. See active pages, referrers, and current visitor counts with zero delay.',
}, },
{ {
icon: ( icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"> <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" />
</svg> </svg>
), ),
title: 'Conversion Funnels', title: 'Conversion Funnels',
description: 'Define multi-step funnels to see where visitors drop off. Track sign-ups, purchases, or any custom flow with precise conversion rates.', description: 'Define multi-step funnels and see exactly where visitors drop off in your sign-up or checkout flow.',
}, },
{ {
icon: ( icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"> <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 3v1.5M3 21v-6m0 0 2.77-.693a9 9 0 0 1 6.208.682l.108.054a9 9 0 0 0 6.086.71l3.114-.732a48.524 48.524 0 0 1-.005-10.499l-3.11.732a9 9 0 0 1-6.085-.711l-.108-.054a9 9 0 0 0-6.208-.682L3 4.5M3 15V4.5" /> <path strokeLinecap="round" strokeLinejoin="round" d="M3 3v1.5M3 21v-6m0 0 2.77-.693a9 9 0 0 1 6.208.682l.108.054a9 9 0 0 0 6.086.71l3.114-.732a48.524 48.524 0 0 1-.005-10.499l-3.11.732a9 9 0 0 1-6.085-.711l-.108-.054a9 9 0 0 0-6.208-.682L3 4.5M3 15V4.5" />
</svg> </svg>
), ),
title: 'Goals & Events', title: 'Goals & Events',
description: 'Set goals for key pages or custom events. Track completions, conversion rates, and revenue attribution — all without cookies.', description: 'Track custom goals, completions, and conversion rates — all without cookies or complex setup.',
}, },
{ {
icon: ( icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"> <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" /> <path strokeLinecap="round" strokeLinejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
</svg> </svg>
), ),
title: 'UTM Campaign Tracking', title: 'UTM Campaign Tracking',
description: 'Automatically parse UTM parameters to attribute traffic to campaigns, sources, and mediums. Built-in UTM link builder included.', description: 'Automatically parse UTM parameters. Built-in link builder for campaigns, sources, and mediums.',
}, },
{ {
icon: Share2Icon, icon: Share2Icon,
title: 'Shared Dashboards', title: 'Shared Dashboards',
description: 'Generate a public link to share your analytics with clients, teammates, or the world. No login required for viewers.', description: 'Generate a public link to share analytics with clients or teammates — no login required.',
}, },
{ {
icon: GlobeIcon, icon: GlobeIcon,
title: 'Geographic Insights', title: 'Geographic Insights',
description: 'See where your visitors are from — country, region, and city level. All derived at request time; IP addresses are never stored.', description: 'Country, region, and city-level breakdowns. IPs are never stored — derived at request time only.',
}, },
] ]
const technicalFeatures = [ // * Trust signals — rendered as compact inline items, NOT cards
{ const trustSignals = [
title: 'One-Line Installation', { label: 'Open Source', detail: 'Frontend fully on GitHub' },
description: 'Add a single <script> tag to your site. Works with any framework — React, Vue, Angular, WordPress, Shopify, and 70+ more.', { label: 'Swiss Infrastructure', detail: 'Data processed in Switzerland' },
link: { href: '/integrations', label: 'View all integrations' }, { label: 'No Cookie Banners', detail: 'Skip consent popups entirely' },
}, { label: '100% Data Ownership', detail: 'We never sell or share your data' },
{ { label: 'Bot & Spam Filtering', detail: 'Automatic exclusion of non-human traffic' },
title: 'Open Source', { label: '75+ Integrations', detail: 'React, Vue, WordPress, Shopify & more' },
description: 'Our frontend is fully open source on GitHub. Inspect the code, contribute, or self-host. Transparency is a feature.',
link: { href: 'https://github.com/ciphera-net/pulse', label: 'View on GitHub', external: true },
},
{
title: 'Swiss Infrastructure',
description: 'All data is processed and stored in Switzerland, one of the strongest privacy jurisdictions in the world.',
},
{
title: 'No Cookie Banners',
description: 'Since we don\'t use cookies, you can skip the annoying consent popups entirely. Better UX for your visitors.',
},
{
title: '100% Data Ownership',
description: 'Your analytics data belongs to you, not us. We never sell, share, or mine your data for advertising.',
},
{
title: 'Bot & Spam Filtering',
description: 'Automatic exclusion of bots, crawlers, and data-center traffic. Your metrics reflect real human visitors.',
},
]
const deviceInsights = [
{ label: 'Browsers', description: 'Chrome, Firefox, Safari, Edge, and more' },
{ label: 'Operating Systems', description: 'Windows, macOS, Linux, iOS, Android' },
{ label: 'Device Types', description: 'Desktop, mobile, and tablet breakdowns' },
{ label: 'Screen Resolutions', description: 'Responsive design intelligence' },
] ]
export default function FeaturesPage() { export default function FeaturesPage() {
@@ -163,9 +138,9 @@ export default function FeaturesPage() {
</p> </p>
</motion.div> </motion.div>
{/* * ─── PILLAR CARDS (Privacy, Dashboard, Lightweight) ─── */} {/* * ─── PILLAR CARDS (3 glass cards — matches landing page) ─── */}
<div className="grid md:grid-cols-3 gap-6 text-left mb-24"> <div className="grid md:grid-cols-3 gap-6 text-left mb-28">
{heroFeatures.map((feature, i) => ( {pillars.map((feature, i) => (
<motion.div <motion.div
key={feature.title} key={feature.title}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
@@ -187,89 +162,56 @@ export default function FeaturesPage() {
))} ))}
</div> </div>
{/* * ─── CORE CAPABILITIES ─── */} {/* * ─── CORE CAPABILITIES (icon list — NO cards) ─── */}
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
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="text-center mb-12" className="mb-28"
> >
<h2 className="text-3xl md:text-4xl font-bold text-neutral-900 dark:text-white mb-4"> <div className="text-center mb-14">
Powerful analytics, <span className="gradient-text">simplified</span>
</h2>
<p className="text-lg text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto">
Everything from real-time dashboards to conversion funnels without the bloat.
</p>
</motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-24">
{coreCapabilities.map((cap, i) => (
<motion.div
key={cap.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.08 }}
className="group relative p-8 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"
>
<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">
{typeof cap.icon === 'object' ? cap.icon : <cap.icon className="w-6 h-6" />}
</div>
<h3 className="text-lg font-bold text-neutral-900 dark:text-white mb-2">
{cap.title}
</h3>
<p className="text-neutral-600 dark:text-neutral-400 text-sm leading-relaxed">
{cap.description}
</p>
</motion.div>
))}
</div>
{/* * ─── DEVICE & TECH INSIGHTS ─── */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-24"
>
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-bold text-neutral-900 dark:text-white mb-4"> <h2 className="text-3xl md:text-4xl font-bold text-neutral-900 dark:text-white mb-4">
Know your audience 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-600 dark:text-neutral-400 max-w-2xl mx-auto">
Understand which devices, browsers, and screen sizes your visitors use so you can build for what matters. Everything from real-time dashboards to conversion funnels without the bloat.
</p> </p>
</div> </div>
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4"> <div className="grid md:grid-cols-2 gap-x-16 gap-y-10 max-w-4xl mx-auto">
{deviceInsights.map((item, i) => ( {capabilities.map((cap, i) => (
<motion.div <motion.div
key={item.label} key={cap.title}
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, x: i % 2 === 0 ? -15 : 15 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.4, delay: i * 0.08 }} transition={{ duration: 0.4, delay: i * 0.08 }}
className="p-6 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-xl text-center" className="flex gap-4"
> >
<div className="w-10 h-10 rounded-lg bg-brand-orange/10 flex items-center justify-center mx-auto mb-4 text-brand-orange"> <div className="w-10 h-10 rounded-lg bg-brand-orange/10 flex items-center justify-center shrink-0 text-brand-orange mt-0.5">
<EyeIcon className="w-5 h-5" /> {typeof cap.icon === 'object' ? cap.icon : <cap.icon className="w-5 h-5" />}
</div>
<div>
<h3 className="font-bold text-neutral-900 dark:text-white mb-1">
{cap.title}
</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed">
{cap.description}
</p>
</div> </div>
<h3 className="font-bold text-neutral-900 dark:text-white mb-1">{item.label}</h3>
<p className="text-sm text-neutral-500 dark:text-neutral-400">{item.description}</p>
</motion.div> </motion.div>
))} ))}
</div> </div>
</motion.div> </motion.div>
{/* * ─── CONTENT ANALYTICS HIGHLIGHT ─── */} {/* * ─── CONTENT ANALYTICS (split layout — visual variety) ─── */}
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
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-24 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-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark: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>
@@ -280,7 +222,13 @@ export default function FeaturesPage() {
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">
{['Top pages by views & unique visitors', 'Entry pages — first impressions that work', 'Exit pages — where you lose attention', 'Referral sources — where traffic comes from'].map((item) => ( {[
'Top pages by views & unique visitors',
'Entry pages — first impressions that work',
'Exit pages — where you lose attention',
'Referral sources — where traffic comes from',
'Browser, OS & device breakdowns',
].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-600 dark: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" />
@@ -303,7 +251,7 @@ 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 border border-neutral-200 dark:border-neutral-700" className="p-4 bg-neutral-50 dark: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-neutral-900 dark:text-white truncate mr-4">
@@ -328,72 +276,71 @@ export default function FeaturesPage() {
</div> </div>
</motion.div> </motion.div>
{/* * ─── TECHNICAL & TRUST FEATURES ─── */} {/* * ─── TRUST SIGNALS (compact grid — NO card borders) ─── */}
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
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="text-center mb-12" className="mb-28"
>
<h2 className="text-3xl md:text-4xl font-bold text-neutral-900 dark:text-white mb-4">
Built for trust
</h2>
<p className="text-lg text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto">
Open source, Swiss hosted, and designed to keep your visitors&apos; data where it belongs with them.
</p>
</motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-24">
{technicalFeatures.map((feat, i) => (
<motion.div
key={feat.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.08 }}
className="p-8 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-2xl"
>
<h3 className="text-lg font-bold text-neutral-900 dark:text-white mb-2">
{feat.title}
</h3>
<p className="text-neutral-600 dark:text-neutral-400 text-sm leading-relaxed mb-4">
{feat.description}
</p>
{feat.link && (
feat.link.external ? (
<a
href={feat.link.href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-sm font-medium text-brand-orange hover:underline"
>
{feat.link.label}
<ArrowRightIcon className="w-4 h-4" />
</a>
) : (
<Link
href={feat.link.href}
className="inline-flex items-center gap-1 text-sm font-medium text-brand-orange hover:underline"
>
{feat.link.label}
<ArrowRightIcon className="w-4 h-4" />
</Link>
)
)}
</motion.div>
))}
</div>
{/* * ─── HOW IT WORKS ─── */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-24"
> >
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-bold text-neutral-900 dark:text-white mb-4">
Built for trust
</h2>
<p className="text-lg text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto">
Open source, Swiss hosted, and designed to keep your visitors&apos; data where it belongs.
</p>
</div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-x-12 gap-y-6 max-w-4xl mx-auto">
{trustSignals.map((signal, i) => (
<motion.div
key={signal.label}
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: i * 0.06 }}
className="flex items-start gap-3 py-2"
>
<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" />
</svg>
<div>
<span className="font-semibold text-neutral-900 dark:text-white text-sm">{signal.label}</span>
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-0.5">{signal.detail}</p>
</div>
</motion.div>
))}
</div>
<div className="flex justify-center gap-6 mt-8">
<Link
href="/integrations"
className="inline-flex items-center gap-1 text-sm font-medium text-brand-orange hover:underline"
>
View all integrations <ArrowRightIcon className="w-4 h-4" />
</Link>
<a
href="https://github.com/ciphera-net/pulse"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-sm font-medium text-brand-orange hover:underline"
>
View on GitHub <ArrowRightIcon className="w-4 h-4" />
</a>
</div>
</motion.div>
{/* * ─── HOW IT WORKS (numbered steps — inline, NO card borders) ─── */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-28"
>
<div className="text-center mb-14">
<h2 className="text-3xl md:text-4xl font-bold text-neutral-900 dark:text-white mb-4"> <h2 className="text-3xl md:text-4xl font-bold text-neutral-900 dark: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>
@@ -402,41 +349,34 @@ export default function FeaturesPage() {
</p> </p>
</div> </div>
<div className="grid md:grid-cols-3 gap-6"> <div className="flex flex-col md:flex-row items-start md:items-center justify-center gap-8 md:gap-6 max-w-3xl mx-auto">
{[ {[
{ { step: '1', title: 'Create your site', desc: 'Sign up and add your domain.' },
step: '1', { step: '2', title: 'Add the script', desc: 'Paste one <script> tag.' },
title: 'Create your site', { step: '3', title: 'Watch the data flow', desc: 'Real-time analytics, instantly.' },
description: 'Sign up and add your domain. We verify ownership automatically.',
},
{
step: '2',
title: 'Add the script',
description: 'Paste one <script> tag before your closing </head>. That\'s it.',
},
{
step: '3',
title: 'Watch the data flow',
description: 'Visit your dashboard to see real-time analytics. No waiting period.',
},
].map((s, i) => ( ].map((s, i) => (
<motion.div <motion.div
key={s.step} key={s.step}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.15 }} transition={{ duration: 0.4, delay: i * 0.15 }}
className="relative p-8 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-2xl text-center" className="flex items-center gap-4 flex-1"
> >
<div className="w-10 h-10 rounded-full bg-brand-orange text-white font-bold text-lg flex items-center justify-center mx-auto mb-6"> <div className="w-10 h-10 rounded-full bg-brand-orange text-white font-bold text-lg flex items-center justify-center shrink-0">
{s.step} {s.step}
</div> </div>
<h3 className="text-lg font-bold text-neutral-900 dark:text-white mb-2"> <div>
{s.title} <h3 className="font-bold text-neutral-900 dark:text-white text-sm">
</h3> {s.title}
<p className="text-neutral-600 dark:text-neutral-400 text-sm leading-relaxed"> </h3>
{s.description} <p className="text-xs text-neutral-500 dark:text-neutral-400">
</p> {s.desc}
</p>
</div>
{i < 2 && (
<ArrowRightIcon className="w-5 h-5 text-neutral-300 dark:text-neutral-600 shrink-0 hidden md:block" />
)}
</motion.div> </motion.div>
))} ))}
</div> </div>

12
package-lock.json generated
View File

@@ -1,14 +1,14 @@
{ {
"name": "pulse-frontend", "name": "pulse-frontend",
"version": "0.1.2", "version": "0.1.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pulse-frontend", "name": "pulse-frontend",
"version": "0.1.2", "version": "0.1.3",
"dependencies": { "dependencies": {
"@ciphera-net/ui": "^0.0.45", "@ciphera-net/ui": "^0.0.46",
"@ducanh2912/next-pwa": "^10.2.9", "@ducanh2912/next-pwa": "^10.2.9",
"axios": "^1.13.2", "axios": "^1.13.2",
"country-flag-icons": "^1.6.4", "country-flag-icons": "^1.6.4",
@@ -1467,9 +1467,9 @@
} }
}, },
"node_modules/@ciphera-net/ui": { "node_modules/@ciphera-net/ui": {
"version": "0.0.45", "version": "0.0.46",
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.45/de60e0da8e1c78ea906d49fdc85cd7d7dd163348", "resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.46/852ce8d0289505c3d3f625a0f076bfb3fb03ef9f",
"integrity": "sha512-KvrNKb9NzLMztB75h94opaaUp9RG43QW7GCRcVX+xGT8EFmrXi/N2h2kpjHZV652H/Cz1EXfcDA0hzoq/+wJXA==", "integrity": "sha512-ZYu1u07B1ROYFYFznWQk2sShSqhFpPKNPCBUjqqElTP5hMYX0jVAnVIzqXUugdLl7E8luwvL6Lx+dOaN4oPORg==",
"dependencies": { "dependencies": {
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "pulse-frontend", "name": "pulse-frontend",
"version": "0.1.2", "version": "0.1.3",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -10,7 +10,7 @@
"type-check": "tsc --noEmit" "type-check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@ciphera-net/ui": "^0.0.45", "@ciphera-net/ui": "^0.0.46",
"@ducanh2912/next-pwa": "^10.2.9", "@ducanh2912/next-pwa": "^10.2.9",
"axios": "^1.13.2", "axios": "^1.13.2",
"country-flag-icons": "^1.6.4", "country-flag-icons": "^1.6.4",