improve(public): re-design landing page a bit
@@ -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">
|
||||||
|
|||||||
86
apps/public/app/(content)/bajs/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 />
|
||||||
|
|||||||
28
apps/public/components/battery-icon.tsx
Normal 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} />;
|
||||||
|
}
|
||||||
81
apps/public/components/competition.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
@@ -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 />}>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
87
apps/public/components/why-openpanel.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
118
apps/public/content/articles/cookieless-analytics.mdx
Normal 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. Today’s 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, we’ll unpack why this approach matters, and how you can get up and running in minutes with OpenPanel.
|
||||||
|
|
||||||
|
## What Is Cookieless Analytics, Really?
|
||||||
|
|
||||||
|
Put simply, it’s tracking without relying on third-party cookies. Instead of stuffing bits of data into a user’s 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 aren’t 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 can’t close. With cookieless analytics:
|
||||||
|
|
||||||
|
* Pages load faster
|
||||||
|
* There are no nagging “Accept cookies?” prompts
|
||||||
|
* Your site works even when someone’s 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 what’s 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:** Copy–paste, and you’re 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',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
That’s it—you’re capturing all the user interactions you need, cookie-free.
|
||||||
115
apps/public/content/articles/mixpanel-alternatives.mdx
Normal 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 user’s 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)
|
||||||
@@ -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>
|
||||||
@@ -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.
|
||||||
|
|||||||
BIN
apps/public/public/content/cookieless-analytics.jpg
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
apps/public/public/logos/helpy-ui.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
apps/public/public/logos/kiddokitchen.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
apps/public/public/logos/maneken.jpg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
apps/public/public/logos/midday.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
apps/public/public/logos/screenzen.avif
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
apps/public/public/logos/tiptip.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |