improve(public): re-design landing page a bit

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-06-18 14:38:14 +02:00
parent 9b16bbaccd
commit 5c6d71f176
23 changed files with 859 additions and 154 deletions

View File

@@ -79,7 +79,7 @@ export default async function Page({
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/> />
<article className="container max-w-4xl col"> <article className="container max-w-4xl col">
<div className="py-16 col gap-3"> <div className="pt-16 pb-4 col gap-3">
<h1 className="text-5xl font-bold">{page.data.title}</h1> <h1 className="text-5xl font-bold">{page.data.title}</h1>
{page.data.description && ( {page.data.description && (
<p className="text-muted-foreground text-xl"> <p className="text-muted-foreground text-xl">

View File

@@ -0,0 +1,86 @@
import { url } from '@/app/layout.config';
import { pageSource } from '@/lib/source';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import Script from 'next/script';
export async function generateMetadata(): Promise<Metadata> {
const page = await pageSource.getPage(['pricing']);
if (!page) {
return {
title: 'Page Not Found',
};
}
return {
title: page.data.title,
description: page.data.description,
alternates: {
canonical: url(page.url),
},
openGraph: {
title: page.data.title,
description: page.data.description,
type: 'website',
url: url(page.url),
},
twitter: {
card: 'summary_large_image',
title: page.data.title,
description: page.data.description,
},
};
}
export default async function Page() {
const page = await pageSource.getPage(['pricing']);
const Body = page?.data.body;
if (!page || !Body) {
return notFound();
}
// Create the JSON-LD data
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: page.data.title,
publisher: {
'@type': 'Organization',
name: 'OpenPanel',
logo: {
'@type': 'ImageObject',
url: url('/logo.png'),
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': url(page.url),
},
};
return (
<div>
<Script
id="page-schema"
strategy="beforeInteractive"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article className="container max-w-4xl col">
<div className="pt-16 pb-8 col gap-3">
<h1 className="text-5xl font-bold">{page.data.title}</h1>
{page.data.description && (
<p className="text-muted-foreground text-xl">
{page.data.description}
</p>
)}
</div>
<div className="prose">
<Body />
</div>
</article>
</div>
);
}

View File

@@ -7,6 +7,7 @@ import { Pricing } from '@/components/sections/pricing';
import { Sdks } from '@/components/sections/sdks'; import { Sdks } from '@/components/sections/sdks';
import { Stats, StatsPure } from '@/components/sections/stats'; import { Stats, StatsPure } from '@/components/sections/stats';
import { Testimonials } from '@/components/sections/testimonials'; import { Testimonials } from '@/components/sections/testimonials';
import { WhyOpenPanel } from '@/components/why-openpanel';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { Suspense } from 'react'; import { Suspense } from 'react';
@@ -22,15 +23,9 @@ export default function HomePage() {
<Navbar /> <Navbar />
<main> <main>
<Hero /> <Hero />
<WhyOpenPanel />
<Features /> <Features />
<Testimonials /> <Testimonials />
<Suspense
fallback={
<StatsPure projectCount={0} eventCount={0} last24hCount={0} />
}
>
<Stats />
</Suspense>
<Faq /> <Faq />
<Pricing /> <Pricing />
<Sdks /> <Sdks />

View File

@@ -0,0 +1,28 @@
'use client';
import {
BatteryFullIcon,
BatteryLowIcon,
BatteryMediumIcon,
} from 'lucide-react';
import { useEffect, useState } from 'react';
export function BatteryIcon({ className }: { className?: string }) {
const [index, setIndex] = useState(0);
const icons = [BatteryLowIcon, BatteryMediumIcon, BatteryFullIcon];
const Icon = icons[index];
useEffect(() => {
const interval = setInterval(() => {
setIndex((index + 1) % icons.length);
}, 750);
return () => clearInterval(interval);
}, [index]);
if (!Icon) {
return <div className={className} />;
}
return <Icon className={className} />;
}

View File

@@ -0,0 +1,81 @@
'use client';
import { AnimatePresence, motion } from 'framer-motion';
import { useEffect, useState } from 'react';
const brandConfig = {
Mixpanel: '#7A59FF',
'Google Analytics': '#E37400',
Amplitude: '#00CF98',
} as const;
const words = Object.keys(brandConfig);
function useWordCycle(words: string[], interval: number, mounted: boolean) {
const [index, setIndex] = useState(0);
const [isInitial, setIsInitial] = useState(true);
useEffect(() => {
if (!mounted) {
return;
}
if (isInitial) {
setIndex(Math.floor(Math.random() * words.length));
setIsInitial(false);
return;
}
const timer = setInterval(() => {
setIndex((current) => (current + 1) % words.length);
}, interval);
return () => clearInterval(timer);
}, [words, interval, isInitial, mounted]);
return words[index];
}
export function Competition() {
const [mounted, setMounted] = useState(false);
const word = useWordCycle(words, 2100, mounted);
const color = brandConfig[word as keyof typeof brandConfig];
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return (
<span className="block truncate leading-tight -mt-1" style={{ color }}>
{word}
</span>
);
}
return (
<AnimatePresence mode="wait" initial={false}>
<motion.div
key={word}
className="block truncate leading-tight -mt-1"
style={{ color }}
>
{word?.split('').map((char, index) => (
<motion.span
key={`${word}-${char}-${index.toString()}`}
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -10, opacity: 0 }}
transition={{
duration: 0.15,
delay: index * 0.015,
ease: 'easeOut',
}}
style={{ display: 'inline-block', whiteSpace: 'pre' }}
>
{char}
</motion.span>
))}
</motion.div>
</AnimatePresence>
);
}

View File

@@ -1,7 +1,28 @@
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ChevronRightIcon } from 'lucide-react'; import { ChevronRightIcon, ConeIcon } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
export function SmallFeature({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div
className={cn(
'bg-background-light rounded-lg p-1 border border-border group',
className,
)}
>
<div className="bg-background-dark rounded-lg p-8 h-full group-hover:bg-background-light transition-colors">
{children}
</div>
</div>
);
}
export function Feature({ export function Feature({
children, children,
media, media,
@@ -16,7 +37,7 @@ export function Feature({
return ( return (
<div <div
className={cn( className={cn(
'border rounded-lg bg-background-light overflow-hidden', 'border rounded-lg bg-background-light overflow-hidden p-1',
className, className,
)} )}
> >
@@ -30,7 +51,7 @@ export function Feature({
{media && ( {media && (
<div <div
className={cn( className={cn(
'bg-background-dark h-full', 'bg-background-dark h-full rounded-md overflow-hidden',
reverse && 'md:order-first', reverse && 'md:order-first',
)} )}
> >
@@ -50,13 +71,16 @@ export function FeatureContent({
}: { }: {
icon?: React.ReactNode; icon?: React.ReactNode;
title: string; title: string;
content: string[]; content: React.ReactNode[];
className?: string; className?: string;
}) { }) {
return ( return (
<div className={className}> <div className={className}>
{icon && ( {icon && (
<div className="bg-foreground text-background rounded-md p-4 inline-block mb-1"> <div
data-icon
className="bg-foreground text-background rounded-md p-4 inline-block mb-6 transition-colors"
>
{icon} {icon}
</div> </div>
)} )}
@@ -72,6 +96,17 @@ export function FeatureContent({
); );
} }
export function FeatureListItem({
icon: Icon,
title,
}: { icon: React.ComponentType<any>; title: string }) {
return (
<div className="row items-center gap-2" key="funnel">
<Icon className="size-4 text-foreground/70" strokeWidth={1.5} /> {title}
</div>
);
}
export function FeatureList({ export function FeatureList({
title, title,
items, items,

View File

@@ -59,23 +59,16 @@ export function Footer() {
</ul> </ul>
</div> </div>
{/* <div className="col gap-3">
<h3 className="font-medium">Company</h3>
<ul className="gap-2 col text-muted-foreground">
<li>
<Link href="/about">About</Link>
</li>
<li>
<Link href="/contact">Contact</Link>
</li>
</ul>
</div> */}
<div className="col gap-3 "> <div className="col gap-3 ">
<h3 className="font-medium">Comparisons</h3> <h3 className="font-medium">Articles</h3>
<ul className="gap-2 col text-muted-foreground"> <ul className="gap-2 col text-muted-foreground">
<li> <li>
<Link href="/articles/vs-mixpanel">vs Mixpanel</Link> <Link href="/articles/vs-mixpanel">OpenPanel vs Mixpanel</Link>
</li>
<li>
<Link href="/articles/mixpanel-alternatives">
Mixpanel alternatives
</Link>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -1,46 +1,125 @@
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { DollarSignIcon } from 'lucide-react'; import {
ArrowRightIcon,
CalendarIcon,
CheckIcon,
ChevronRightIcon,
CookieIcon,
CreditCardIcon,
DatabaseIcon,
DollarSignIcon,
FlaskRoundIcon,
GithubIcon,
ServerIcon,
StarIcon,
} from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useState } from 'react';
import { Competition } from './competition';
import { HeroCarousel } from './hero-carousel'; import { HeroCarousel } from './hero-carousel';
import { HeroMap } from './hero-map'; import { HeroMap } from './hero-map';
import { Tag } from './tag'; import { Tag } from './tag';
import { Button } from './ui/button'; import { Button } from './ui/button';
const perks = [
{ text: 'Free trial 30 days', icon: CalendarIcon },
{ text: 'No credit card required', icon: CreditCardIcon },
{ text: 'Cookie-less tracking', icon: CookieIcon },
{ text: 'Open-source', icon: GithubIcon },
{ text: 'Your data, your rules', icon: DatabaseIcon },
{ text: 'Self-hostable', icon: ServerIcon },
];
export function Hero() { export function Hero() {
return ( return (
<HeroContainer> <HeroContainer>
{/* Shadow bottom */} <div className="container relative z-10 col sm:row sm:py-44 max-sm:pt-32">
<div className="w-full absolute bottom-0 h-32 bg-gradient-to-t from-background to-transparent z-20" /> <div className="col gap-8 w-full sm:w-1/2 sm:pr-12">
<div className="col gap-4">
{/* Content */} <Tag className="self-start">
<div className="container relative z-10"> <StarIcon className="size-4 fill-yellow-500 text-yellow-500" />
<div className="max-w-2xl col gap-4 pt-44 text-center mx-auto "> Trusted by +2000 projects
<Tag className="self-center">
<DollarSignIcon className="size-4" />
Release 1.0 is live!
</Tag> </Tag>
<h1 className="text-4xl md:text-6xl font-bold leading-[1.1]"> <h1
An open-source alternative to <span>Mixpanel</span> className="text-4xl md:text-5xl font-extrabold leading-[1.1]"
title="An open-source alternative to Mixpanel"
>
An open-source alternative to <Competition />
</h1> </h1>
<p className="text-xl text-muted-foreground"> <p className="text-xl text-muted-foreground">
The power of Mixpanel, the ease of Plausible and nothing from Google A web and product analytics platform that combines the power of
Analytics 😉 Mixpanel with the ease of Plausible and one of the best Google
Analytics replacements.
</p> </p>
</div> </div>
<Button size="lg" asChild className="group w-72">
{/* CTA */}
<div className="col md:row gap-4 center-center my-12">
<Button size="lg" asChild>
<Link href="https://dashboard.openpanel.dev/onboarding"> <Link href="https://dashboard.openpanel.dev/onboarding">
Try it for free Get started now
<ChevronRightIcon className="size-4 group-hover:translate-x-1 transition-transform group-hover:scale-125" />
</Link> </Link>
</Button> </Button>
<p className="text-sm text-muted-foreground">
Free trial for 30 days, no credit card required <ul className="grid grid-cols-2 gap-2">
</p> {perks.map((perk) => (
<li key={perk.text} className="text-sm text-muted-foreground">
<perk.icon className="size-4 inline-block mr-1" />
{perk.text}
</li>
))}
</ul>
</div> </div>
<HeroCarousel /> <div className="col sm:w-1/2 relative group">
<div
className={cn([
'overflow-hidden rounded-lg border border-border bg-background shadow-lg',
'sm:absolute sm:left-0 sm:-top-12 sm:w-[800px] sm:-bottom-32',
'max-sm:h-[800px] max-sm:-mx-4 max-sm:mt-12 relative',
])}
>
{/* Window controls */}
<div className="flex items-center gap-2 px-4 py-2 border-b border-border bg-muted/50 h-12">
<div className="flex gap-1.5">
<div className="w-3 h-3 rounded-full bg-red-500" />
<div className="w-3 h-3 rounded-full bg-yellow-500" />
<div className="w-3 h-3 rounded-full bg-green-500" />
</div>
{/* URL bar */}
<a
target="_blank"
rel="noreferrer noopener nofollow"
href="https://demo.openpanel.dev/demo/shoey"
className="group flex-1 mx-4 px-3 py-1 text-sm bg-background rounded-md border border-border flex items-center gap-2"
>
<span className="text-muted-foreground flex-1">
https://demo.openpanel.dev
</span>
<ArrowRightIcon className="size-4 opacity-0 group-hover:opacity-100 transition-opacity" />
</a>
</div>
<iframe
src={'https://demo.openpanel.dev/demo/shoey?range=lastHour'}
className="w-full h-full"
title="Live preview"
scrolling="no"
/>
<div className="pointer-events-none absolute inset-0 top-12 center-center group-hover:bg-foreground/20 transition-colors">
<Button
asChild
className="group-hover:opacity-100 opacity-0 transition-opacity pointer-events-auto"
>
<Link
href="https://demo.openpanel.dev/demo/shoey"
rel="noreferrer noopener nofollow"
target="_blank"
>
Test live demo
<FlaskRoundIcon className="size-4" />
</Link>
</Button>
</div>
</div>
</div>
</div> </div>
</HeroContainer> </HeroContainer>
); );
@@ -55,13 +134,6 @@ export function HeroContainer({
}): React.ReactElement { }): React.ReactElement {
return ( return (
<section className={cn('radial-gradient overflow-hidden relative')}> <section className={cn('radial-gradient overflow-hidden relative')}>
{/* Map */}
<HeroMap />
{/* Gradient over map */}
<div className="absolute inset-0 radial-gradient-dot-1 select-none" />
<div className="absolute inset-0 radial-gradient-dot-1 select-none" />
<div className={cn('relative z-10', className)}>{children}</div> <div className={cn('relative z-10', className)}>{children}</div>
{/* Shadow bottom */} {/* Shadow bottom */}

View File

@@ -2,25 +2,37 @@ import {
Feature, Feature,
FeatureContent, FeatureContent,
FeatureList, FeatureList,
FeatureListItem,
FeatureMore, FeatureMore,
SmallFeature,
} from '@/components/feature'; } from '@/components/feature';
import { Section, SectionHeader } from '@/components/section'; import { Section, SectionHeader } from '@/components/section';
import { Tag } from '@/components/tag'; import { Tag } from '@/components/tag';
import { import {
ActivityIcon,
AreaChartIcon, AreaChartIcon,
BarChart2Icon, BarChart2Icon,
BarChartIcon, BarChartIcon,
BatteryIcon, CheckIcon,
ClockIcon, ClockIcon,
CloudIcon, CloudIcon,
ConeIcon, ConeIcon,
CookieIcon, CookieIcon,
DatabaseIcon, DatabaseIcon,
GithubIcon,
LayersIcon,
LineChartIcon, LineChartIcon,
LockIcon,
MapIcon, MapIcon,
PieChartIcon, PieChartIcon,
ServerIcon,
Share2Icon,
ShieldIcon,
UserIcon, UserIcon,
WalletIcon,
ZapIcon,
} from 'lucide-react'; } from 'lucide-react';
import { BatteryIcon } from '../battery-icon';
import { EventsFeature } from './features/events-feature'; import { EventsFeature } from './features/events-feature';
import { ProductAnalyticsFeature } from './features/product-analytics-feature'; import { ProductAnalyticsFeature } from './features/product-analytics-feature';
import { ProfilesFeature } from './features/profiles-feature'; import { ProfilesFeature } from './features/profiles-feature';
@@ -52,21 +64,30 @@ export function Features() {
className="mt-4" className="mt-4"
title="Get a quick overview" title="Get a quick overview"
items={[ items={[
'• Visitors', <FeatureListItem key="line" icon={CheckIcon} title="Visitors" />,
'• Referrals', <FeatureListItem key="line" icon={CheckIcon} title="Referrals" />,
'• Top pages', <FeatureListItem key="line" icon={CheckIcon} title="Top pages" />,
'• Top entries', <FeatureListItem
'• Top exists', key="line"
'• Devices', icon={CheckIcon}
'• Sessions', title="Top entries"
'• Bounce rate', />,
'• Duration', <FeatureListItem
'• Geography', key="line"
icon={CheckIcon}
title="Top exists"
/>,
<FeatureListItem key="line" icon={CheckIcon} title="Devices" />,
<FeatureListItem key="line" icon={CheckIcon} title="Sessions" />,
<FeatureListItem
key="line"
icon={CheckIcon}
title="Bounce rate"
/>,
<FeatureListItem key="line" icon={CheckIcon} title="Duration" />,
<FeatureListItem key="line" icon={CheckIcon} title="Geography" />,
]} ]}
/> />
{/* <FeatureMore href="#" className="mt-4">
And mouch more
</FeatureMore> */}
</Feature> </Feature>
<Feature reverse media={<ProductAnalyticsFeature />}> <Feature reverse media={<ProductAnalyticsFeature />}>
@@ -76,72 +97,49 @@ export function Features() {
'Turn data into decisions with powerful visualizations and real-time insights.', 'Turn data into decisions with powerful visualizations and real-time insights.',
]} ]}
/> />
<FeatureList
className="mt-4"
title="Understand your product"
items={[
<FeatureListItem key="funnel" icon={ConeIcon} title="Funnel" />,
<FeatureListItem
key="retention"
icon={UserIcon}
title="Retention"
/>,
<FeatureListItem
key="bar"
icon={BarChartIcon}
title="A/B tests"
/>,
<FeatureListItem
key="pie"
icon={PieChartIcon}
title="Conversion"
/>,
]}
/>
<FeatureList <FeatureList
className="mt-4" className="mt-4"
title="Supported charts" title="Supported charts"
items={[ items={[
<div className="row items-center gap-2" key="line"> <FeatureListItem key="line" icon={LineChartIcon} title="Line" />,
<LineChartIcon <FeatureListItem key="bar" icon={BarChartIcon} title="Bar" />,
className="size-4 text-foreground/70" <FeatureListItem key="pie" icon={PieChartIcon} title="Pie" />,
strokeWidth={1.5} <FeatureListItem key="area" icon={AreaChartIcon} title="Area" />,
/>{' '} <FeatureListItem
Line key="histogram"
</div>, icon={BarChart2Icon}
<div className="row items-center gap-2" key="bar"> title="Histogram"
<BarChartIcon />,
className="size-4 text-foreground/70" <FeatureListItem key="map" icon={MapIcon} title="Map" />,
strokeWidth={1.5}
/>{' '}
Bar
</div>,
<div className="row items-center gap-2" key="pie">
<PieChartIcon
className="size-4 text-foreground/70"
strokeWidth={1.5}
/>{' '}
Pie
</div>,
<div className="row items-center gap-2" key="area">
<AreaChartIcon
className="size-4 text-foreground/70"
strokeWidth={1.5}
/>{' '}
Area
</div>,
<div className="row items-center gap-2" key="histogram">
<BarChart2Icon
className="size-4 text-foreground/70"
strokeWidth={1.5}
/>{' '}
Histogram
</div>,
<div className="row items-center gap-2" key="map">
<MapIcon
className="size-4 text-foreground/70"
strokeWidth={1.5}
/>{' '}
Map
</div>,
<div className="row items-center gap-2" key="funnel">
<ConeIcon
className="size-4 text-foreground/70"
strokeWidth={1.5}
/>{' '}
Funnel
</div>,
<div className="row items-center gap-2" key="retention">
<UserIcon
className="size-4 text-foreground/70"
strokeWidth={1.5}
/>{' '}
Retention
</div>,
]} ]}
/> />
</Feature> </Feature>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Feature> <SmallFeature className="[&_[data-icon]]:hover:bg-blue-500">
<FeatureContent <FeatureContent
icon={<ClockIcon className="size-8" strokeWidth={1} />} icon={<ClockIcon className="size-8" strokeWidth={1} />}
title="Real time analytics" title="Real time analytics"
@@ -149,8 +147,8 @@ export function Features() {
'Get instant insights into your data. No need to wait for data to be processed, like other tools out there, looking at you GA4...', 'Get instant insights into your data. No need to wait for data to be processed, like other tools out there, looking at you GA4...',
]} ]}
/> />
</Feature> </SmallFeature>
<Feature> <SmallFeature className="[&_[data-icon]]:hover:bg-purple-500">
<FeatureContent <FeatureContent
icon={<DatabaseIcon className="size-8" strokeWidth={1} />} icon={<DatabaseIcon className="size-8" strokeWidth={1} />}
title="Own your own data" title="Own your own data"
@@ -159,10 +157,8 @@ export function Features() {
'Self-host it on your own infrastructure to have complete control.', 'Self-host it on your own infrastructure to have complete control.',
]} ]}
/> />
</Feature> </SmallFeature>
<div /> <SmallFeature className="[&_[data-icon]]:hover:bg-indigo-500">
<div />
<Feature>
<FeatureContent <FeatureContent
icon={<CloudIcon className="size-8" strokeWidth={1} />} icon={<CloudIcon className="size-8" strokeWidth={1} />}
title="Cloud or self-hosted" title="Cloud or self-hosted"
@@ -176,8 +172,8 @@ export function Features() {
> >
More about self-hosting More about self-hosting
</FeatureMore> </FeatureMore>
</Feature> </SmallFeature>
<Feature> <SmallFeature className="[&_[data-icon]]:hover:bg-green-500">
<FeatureContent <FeatureContent
icon={<CookieIcon className="size-8" strokeWidth={1} />} icon={<CookieIcon className="size-8" strokeWidth={1} />}
title="No cookies" title="No cookies"
@@ -186,7 +182,93 @@ export function Features() {
'We follow GDPR guidelines closely, ensuring your personal information is protected without using invasive technologies.', 'We follow GDPR guidelines closely, ensuring your personal information is protected without using invasive technologies.',
]} ]}
/> />
</Feature> <FeatureMore
href="/articles/cookieless-analytics"
className="mt-4 -mb-4"
>
Cookieless analytics
</FeatureMore>
</SmallFeature>
<SmallFeature className="[&_[data-icon]]:hover:bg-gray-500">
<FeatureContent
icon={<GithubIcon className="size-8" strokeWidth={1} />}
title="Open-source"
content={[
'Our code is open and transparent. Contribute, fork, or learn from our implementation.',
]}
/>
<FeatureMore
href="https://git.new/openpanel"
className="mt-4 -mb-4"
>
View the code
</FeatureMore>
</SmallFeature>
<SmallFeature className="[&_[data-icon]]:hover:bg-purple-500">
<FeatureContent
icon={<LockIcon className="size-8" strokeWidth={1} />}
title="Your data, your rules"
content={[
'Complete control over your data. Export, delete, or manage it however you need.',
]}
/>
</SmallFeature>
<SmallFeature className="[&_[data-icon]]:hover:bg-yellow-500">
<FeatureContent
icon={<WalletIcon className="size-8" strokeWidth={1} />}
title="Affordably priced"
content={[
'Transparent pricing that scales with your needs. No hidden fees or surprise charges.',
]}
/>
</SmallFeature>
<SmallFeature className="[&_[data-icon]]:hover:bg-orange-500">
<FeatureContent
icon={<ZapIcon className="size-8" strokeWidth={1} />}
title="Moving fast"
content={[
'Regular updates and improvements. We move quickly to add features you need.',
]}
/>
</SmallFeature>
<SmallFeature className="[&_[data-icon]]:hover:bg-blue-500">
<FeatureContent
icon={<ActivityIcon className="size-8" strokeWidth={1} />}
title="Real-time data"
content={[
'See your analytics as they happen. No waiting for data processing or updates.',
]}
/>
</SmallFeature>
<SmallFeature className="[&_[data-icon]]:hover:bg-indigo-500">
<FeatureContent
icon={<Share2Icon className="size-8" strokeWidth={1} />}
title="Sharable reports"
content={[
'Easily share insights with your team. Export and distribute reports with a single click.',
<i key="soon">Coming soon</i>,
]}
/>
</SmallFeature>
<SmallFeature className="[&_[data-icon]]:hover:bg-pink-500">
<FeatureContent
icon={<BarChart2Icon className="size-8" strokeWidth={1} />}
title="Visualize your data"
content={[
'Beautiful, interactive visualizations that make your data easy to understand.',
]}
/>
</SmallFeature>
<SmallFeature className="[&_[data-icon]]:hover:bg-indigo-500">
<FeatureContent
icon={<LayersIcon className="size-8" strokeWidth={1} />}
title="Best of both worlds"
content={[
'Combine the power of self-hosting with the convenience of cloud deployment.',
]}
/>
</SmallFeature>
</div> </div>
<Feature media={<EventsFeature />}> <Feature media={<EventsFeature />}>

View File

@@ -1,4 +1,5 @@
import { CheckIcon, DollarSignIcon } from 'lucide-react'; import { cn } from '@/lib/utils';
import { CheckIcon, ChevronRightIcon, DollarSignIcon } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { DoubleSwirl } from '../Swirls'; import { DoubleSwirl } from '../Swirls';
import { PricingSlider } from '../pricing-slider'; import { PricingSlider } from '../pricing-slider';
@@ -7,9 +8,14 @@ import { Tag } from '../tag';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
export default Pricing; export default Pricing;
export function Pricing() { export function Pricing({ className }: { className?: string }) {
return ( return (
<Section className="overflow-hidden relative bg-foreground dark:bg-background-dark text-background dark:text-foreground rounded-xl p-0 pb-32 pt-16 max-w-7xl mx-auto"> <Section
className={cn(
'overflow-hidden relative bg-foreground dark:bg-background-dark text-background dark:text-foreground xl:rounded-xl p-0 pb-32 pt-16 max-w-7xl mx-auto',
className,
)}
>
<DoubleSwirl className="absolute top-0 left-0" /> <DoubleSwirl className="absolute top-0 left-0" />
<div className="container relative z-10"> <div className="container relative z-10">
<SectionHeader <SectionHeader
@@ -20,7 +26,7 @@ export function Pricing() {
</Tag> </Tag>
} }
title="Simple pricing" title="Simple pricing"
description="Our simple, usage-based pricing means you only pay for what you use. Scale effortlessly for the best value." description="Just pick how many events you want to track each month. No hidden costs."
/> />
<div className="grid md:grid-cols-[400px_1fr] gap-8"> <div className="grid md:grid-cols-[400px_1fr] gap-8">
@@ -52,9 +58,15 @@ export function Pricing() {
</li> </li>
</ul> </ul>
<Button variant="secondary" className="self-start mt-4" asChild> <Button
variant="secondary"
size="lg"
asChild
className="self-start mt-4 px-8 group"
>
<Link href="https://dashboard.openpanel.dev/onboarding"> <Link href="https://dashboard.openpanel.dev/onboarding">
Start for free Get started now
<ChevronRightIcon className="size-4 group-hover:translate-x-1 transition-transform group-hover:scale-125" />
</Link> </Link>
</Button> </Button>
</div> </div>

View File

@@ -42,7 +42,7 @@ export function TwitterCard({
}; };
return ( return (
<div className="border rounded-lg p-4 col gap-4 bg-background-light"> <div className="border rounded-lg p-8 col gap-4 bg-background-light">
<div className="row gap-4"> <div className="row gap-4">
<div className="size-12 rounded-full bg-muted overflow-hidden shrink-0"> <div className="size-12 rounded-full bg-muted overflow-hidden shrink-0">
{avatarUrl && ( {avatarUrl && (

View File

@@ -0,0 +1,87 @@
import { cn } from '@/lib/utils';
import { ArrowDownIcon } from 'lucide-react';
import Image from 'next/image';
import { Section, SectionHeader } from './section';
import { Tag } from './tag';
import { Tooltip } from './ui/tooltip';
const images = [
{
name: 'Helpy UI',
url: 'https://helpy-ui.com',
logo: 'helpy-ui.png',
border: true,
},
{
name: 'KiddoKitchen',
url: 'https://kiddokitchen.se',
logo: 'kiddokitchen.png',
border: false,
},
{
name: 'Maneken',
url: 'https://maneken.app',
logo: 'maneken.jpg',
border: false,
},
{
name: 'Midday',
url: 'https://midday.ai',
logo: 'midday.png',
border: true,
},
{
name: 'Screenzen',
url: 'https://www.screenzen.co',
logo: 'screenzen.avif',
border: true,
},
{
name: 'Tiptip',
url: 'https://tiptip.id',
logo: 'tiptip.jpg',
border: true,
},
];
export function WhyOpenPanel() {
return (
<div className="bg-background-light my-12 col">
<Section className="container my-0 py-20">
<SectionHeader
title="Why OpenPanel?"
description="We built OpenPanel to get the best of both web and product analytics. With that in mind we have created a simple but very powerful platform that can handle most companies needs."
/>
<div className="center-center col gap-4 -mt-4">
<Tag>
<ArrowDownIcon className="size-4" strokeWidth={1.5} />
With 2000+ registered projects
</Tag>
<div className="row gap-4 justify-center flex-wrap">
{images.map((image) => (
<a
href={image.url}
target="_blank"
rel="noopener noreferrer nofollow"
key={image.logo}
className={cn(
'group rounded-lg bg-white center-center size-20 hover:scale-110 transition-all duration-300',
image.border && 'p-2 border border-border shadow-sm',
)}
title={image.name}
>
<Image
src={`/logos/${image.logo}`}
alt={image.name}
width={80}
height={80}
className="rounded-lg grayscale group-hover:grayscale-0 transition-all duration-300"
/>
</a>
))}
</div>
</div>
</Section>
</div>
);
}

View File

@@ -0,0 +1,118 @@
---
title: Cookieless Analytics
description: Discover how to gather meaningful insights without cookies and why OpenPanel makes it effortless.
tag: Guide
team: OpenPanel Team
date: 2025-06-17
cover: /content/cookieless-analytics.jpg
---
The age of tracking everyone, everywhere, with endless cookies is fading fast. Todays users expect both useful experiences and respect for their privacy. Enter **cookieless analytics**—a smarter way to understand your audience without leaving a trail of crumbs behind. In this guide, well unpack why this approach matters, and how you can get up and running in minutes with OpenPanel.
## What Is Cookieless Analytics, Really?
Put simply, its tracking without relying on third-party cookies. Instead of stuffing bits of data into a users browser, you pivot to methods like:
* **Server-side events.** Capture interactions directly on your backend.
* **Session-based identifiers.** Tie actions together during a visit—then discard the identifier when they leave.
* **First-party data.** Use your own signup forms, preferences, and logs.
* **Device fingerprints** (used sparingly). Hash together non-identifying signals like screen size and language.
Each of these respects privacy laws and keeps you off users “block” lists—without sacrificing insights.
## Why Ditch Cookies? Four Big Wins
### 1. Stay Ahead of Privacy Laws
Regulations like GDPR and CCPA arent going away. By design, cookieless systems:
* Avoid endless consent banners
* Keep you clear of hefty fines
* Show customers you take privacy seriously
### 2. Delight Your Visitors
Nothing disrupts a first impression like a pop-up you cant close. With cookieless analytics:
* Pages load faster
* There are no nagging “Accept cookies?” prompts
* Your site works even when someones browser is locked down
### 3. Future-Proof Your Data
Browsers are phasing out third-party cookies (Safari, Firefox already have). A cookieless stack means:
* No last-minute scrambling when Chrome follows suit
* Compatibility with privacy-focused browsers
* A sustainable analytics foundation
### 4. Cleaner, More Trustworthy Insights
When you rely on your own data sources:
* You reduce duplicate or incomplete sessions
* You focus on active, consenting users
* Your reports match your real user base
## Why OpenPanel Shines for Cookieless Tracking
We built OpenPanel from the ground up with privacy at its heart—and with features you actually need:
### Privacy by Default
* **Zero cookies.** Ever.
* **GDPR & CCPA compliant.** Out of the box.
* **Transparent data policies.** Your users know whats collected and why.
### Powerful, Yet Simple Analytics
* **Real-time dashboards.** Watch events as they happen.
* **Custom events & properties.** Track anything from “add to wishlist” to “video watched.”
* **Rich reports.** Dive deep on funnels, retention, and user journeys.
### Plug-and-Play Setup
1. **Drop in our script:** Copypaste, and youre live.
2. **Pick your SDK:** JavaScript, Python, Go… whatever fits.
3. **Start tracking in minutes.** No extra configuration.
### Open Source & Self-Hosted Option
* **Inspect the code.** Full transparency.
* **Self-host if you choose.** Keep data on your servers.
* **No vendor lock-in.** Export anytime.
## Quick Start: Two Steps to Cookieless Insights
1. **Add the tracking snippet**
```html
<script>
window.op = window.op || function(...args) {
(window.op.q = window.op.q || []).push(args);
};
window.op('init', {
clientId: 'YOUR_CLIENT_ID',
trackScreenViews: true,
trackOutgoingLinks: true,
trackAttributes: true,
});
</script>
<script src="https://openpanel.dev/op1.js" defer async></script>
```
2. **Fire off your first event**
```javascript
// Simple click
window.op('track', 'signup_button_clicked');
// Purchase with details
window.op('track', 'order_placed', {
orderId: 'ORD-20250617-001',
revenue: 49.95,
currency: 'EUR',
});
```
Thats it—youre capturing all the user interactions you need, cookie-free.

View File

@@ -0,0 +1,115 @@
---
title: Mixpanel Alternatives
description: Our top open-source picks if you want to move away from Mixpanel
date: 2025-07-18
tag: Comparison
team: OpenPanel Team
cover: /content/cover-mixpanel.jpg
---
> We have tried to keep this list as fair as possible, even though we compete with these tools.
Analytics tools fall into two main groups:
- Web analytics gives you page views, sessions, referrers
- Product analytics shows what users do inside your product
Mixpanel is best known for product analytics. Web tools like Google Analytics tell you about traffic, but a product analytics tool tells you how and when users use your features.
We start with the key features you need. Then we give you three open-source options.
## Key product analytics features
1. **Event tracking**
Record any action—signups, clicks, purchases—to see how users behave.
2. **Funnels**
Show each step in a process (for example signup → verify email → first purchase). Find where users drop off so you can improve that step.
3. **Retention analysis**
Find out how many users come back over days or weeks:
- **N-day retention**: percent who return after N days
- **Rolling retention**: percent active in a set time period
- **Cohorts**: compare groups of users who signed up at the same time
4. **User details**
Look at one users journey. This helps you fix issues and give users a better experience.
5. **Flexible dashboards**
Build charts and reports to answer any question about your product.
## How to choose an open-source alternative
Look at these points when you compare tools:
| Point | What to check |
|-------------------|--------------------------------------------------|
| License | True open source (MIT, Apache) or source-only |
| Resource needs | CPU and memory for running the tool |
| Features | Funnels, retention, user view, session replay |
| Setup | Easy install or complex pipeline |
| Maintenance | Updates, docs and community support |
| Integrations | SDKs for web, mobile, server; data exports |
## 1. OpenPanel
You guessed it right, of course we'll promote OpenPanel, it was made solely because we wanted an alternative to Mixpanel. We have used Mixpanel a lot but for a startup we didn't have the cash flow needed to pay the bills. We also didn't need all Mixpanel's features so we decided to build our own platform that combined web & product analytics into one simple, affordable and self-hostable platform.
**License:** AGPL-3.0 license
**What you get**:
- You get a very good web analytics overview (similar to Plausible, Simple Analytics)
- You can track custom events
- You can track users
- You can create any type of chart you want
- You can create funnels and conversions for A/B testing and improve your product
- You can create retention charts to understand how long your users stays
**Good points**:
- Low cost and self-hostable
- Web and product analytics together
- Fast setup and clean interface
**Drawbacks**:
- Fewer third-party integrations
- No session-replay or heatmaps yet
## 2. PostHog
A full-feature platform with both cloud and self-hosted options.
**License:** MIT License (core) + Enterprise License (ee/)
**What you get**:
- Funnels, trends, cohorts
- Session recordings, feature flags, A/B tests
- Plugins (Kafka, Snowflake exports)
**Good points**:
- Lots of features, active community
- Free up to 1 million events per month
- Good docs and tutorials
**Drawbacks**:
- Self-host needs high CPU and RAM
- Cloud costs rise fast after 1M events
- Some learning needed for plugins
## 3. Snowplow Analytics
**Overview**
A tool that sends event data into your own data warehouse.
**License:** Apache-2.0 license
**What you get**:
- Open collectors and pipelines
- Works with Redshift, BigQuery, Snowflake
- Custom schemas via Iglu registry
**Good points**:
- Full control of raw events
- Scales to billions of events
- Good for teams with data-warehouse skills
**Drawbacks**:
- Setup is complex (Kafka, Spark, Hadoop)
- No built-in dashboards (need Looker or similar)

View File

@@ -7,12 +7,15 @@ import Stats from '@/components/sections/stats';
import Testimonials from '@/components/sections/testimonials'; import Testimonials from '@/components/sections/testimonials';
import Faq from '@/components/sections/faq'; import Faq from '@/components/sections/faq';
Experience transparent, usage-based pricing that grows with your needs. Simply choose your monthly event volume and pay accordingly - no surprises. Our pricing model is simple, pick how many events you want and pay that amount. It doesn't matter what tier you're in, you should always be able to:
All features are included in every plan, with no hidden fees or artificial feature restrictions. What you see is what you get. - Create as many reports you want
- Add as many websites you needs
- Invite all your co-workers
- You probably get the point
<div className="lg:-mx-20 xl:-mx-40 not-prose -mt-16"> <div className="lg:-mx-20 xl:-mx-40 not-prose -mt-16">
<Pricing /> <Pricing className="!rounded-xl" />
<Testimonials /> <Testimonials />
<Faq /> <Faq />
</div> </div>

View File

@@ -7,8 +7,6 @@ import Stats from '@/components/sections/stats';
import Testimonials from '@/components/sections/testimonials'; import Testimonials from '@/components/sections/testimonials';
import Faq from '@/components/sections/faq'; import Faq from '@/components/sections/faq';
Hey there! 👋
TL;DR [**Become a supporter 🫶**](https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV) TL;DR [**Become a supporter 🫶**](https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV)
First off, we want to say a massive thank you for even considering supporting OpenPanel. As an open-source project, every single supporter means the world to us. We're not a big corporation just a small team passionate about building something useful for the developer community. First off, we want to say a massive thank you for even considering supporting OpenPanel. As an open-source project, every single supporter means the world to us. We're not a big corporation just a small team passionate about building something useful for the developer community.

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB