public: update landing page

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-03-19 22:37:37 +01:00
parent b7513f24d5
commit 1b9edc6bd2
47 changed files with 509 additions and 4061 deletions

View File

@@ -5,7 +5,7 @@ export const revalidate = 3600;
export default function Page() {
return (
<div className="container mt-[150px]">
<div className="container mt-[150px] max-w-2xl">
<article className="prose">
<Heading1>Privacy Policy</Heading1>
<p>Last updated: February 22, 2024</p>

View File

@@ -5,7 +5,7 @@ export const revalidate = 3600;
export default function Page() {
return (
<div className="container mt-[150px]">
<div className="container mt-[150px] max-w-2xl">
<article className="prose">
<Heading1>Terms and Conditions</Heading1>
<p>Last updated: February 22, 2024</p>

View File

@@ -1,74 +0,0 @@
import * as React from 'react';
import type { SVGProps } from 'react';
export const Blob1 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M162.9 81.7c6.8 18.6-7.8 46.3-29.5 61.3-21.6 15-50.3 17.4-70.1 3.8-19.8-13.5-30.8-42.9-23.2-62.7 7.6-19.7 33.7-29.9 60.9-30.2 27.1-.3 55.2 9.2 61.9 27.8Z" />
</svg>
);
export const Blob2 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M142.7 86.2c9.4 28.8 11.5 60-2.1 70-13.6 9.9-42.8-1.5-63.9-18.2-21.2-16.7-34.2-38.8-28.9-62C53 52.9 76.5 28.7 96.6 29.8c20.1 1.1 36.8 27.5 46.1 56.4Z" />
</svg>
);
export const Blob3 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M135.5 88.9c4.3 12.9-2.5 29.9-18.7 44-16.2 14-41.6 25.1-54.7 16.3-13.1-8.8-14-37.6-5.6-56.1C64.9 74.7 82.4 66.4 99 66.8c16.6.3 32.2 9.2 36.5 22.1Z" />
</svg>
);
export const Blob4 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M161.5 88.5c9.2 20.1 1.7 54.1-18.1 67.6-19.8 13.5-51.9 6.6-76.8-11.2-25-17.9-42.8-46.7-36-63.3 6.7-16.6 38.1-21 66.8-20.2 28.7.9 54.8 7 64.1 27.1Z" />
</svg>
);
export const Blob5 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M144.6 81.1c3.8 15.9-9.2 32.9-27.5 47.4-18.4 14.6-42.2 26.7-58 17.6-15.7-9-23.5-39.3-15.3-61.3 8.1-21.9 32.2-35.6 54.4-35 22.2.5 42.7 15.4 46.4 31.3Z" />
</svg>
);
export const Blob6 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M158 83.8c9.5 26.6 4.2 60.6-14.8 74.1-19.1 13.5-52 6.5-68.5-8.9-16.5-15.3-16.6-39-9.7-62 7-23.1 21-45.5 40.1-47.2 19.1-1.7 43.4 17.5 52.9 44Z" />
</svg>
);
export const Blob7 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M163.8 81.2c6.3 17.6-9.8 44.3-28.5 55.1-18.8 10.7-40.3 5.4-58.3-7.1C58.9 116.6 44.2 96.9 48.6 82c4.4-14.9 27.9-24.9 54-25.7 26.1-.9 54.9 7.4 61.2 24.9Z" />
</svg>
);
export const Blob8 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M140.2 93.2c8.6 20.4 10.1 49.3-2.1 57.8-12.2 8.6-38.2-3.3-54.2-17.6s-22.1-31-17.8-45.5c4.4-14.5 19.1-26.6 34.4-26.8 15.3-.2 31 11.7 39.7 32.1Z" />
</svg>
);
export const Blob9 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M150.6 84.9c6.7 19.2-2 44.7-22.1 60.8-20.1 16.2-51.8 23-73.6 8.9C33 140.5 21 105.4 30.1 82.8 39.2 60.2 69.6 50 95.8 51.4c26.2 1.3 48.1 14.3 54.8 33.5Z" />
</svg>
);
export const Blob10 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M162 75.8c6.7 24.7-7.5 52.1-30.7 69.5s-55.4 24.8-77.8 10.4C31 141.4 18.3 105.4 27.7 77 37 48.6 68.5 27.9 98.1 28.5c29.5.6 57.2 22.6 63.9 47.3Z" />
</svg>
);
export const Blob11 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M164.3 77.3c7.7 25.5-5.2 55.9-26.2 69.9-21 14.1-50.2 11.8-66.2-1.5-16.1-13.3-19-37.7-12.2-62 6.9-24.3 23.6-48.7 46.1-50.6 22.5-1.9 50.8 18.7 58.5 44.2Z" />
</svg>
);
export const Blob12 = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" {...props}>
<path d="M140 81.8c5.8 23.1.4 44.7-14.1 55.7-14.6 11-38.2 11.4-59-1.5C46 123.1 27.8 96.9 33.8 73.6c6-23.4 36.1-43.8 59.7-41.7 23.6 2.1 40.7 26.8 46.5 49.9Z" />
</svg>
);

View File

@@ -1,75 +0,0 @@
'use client';
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';
import Autoplay from 'embla-carousel-autoplay';
import Image from 'next/image';
const images = [
{
title: 'Beautiful overview, everything is clickable to get more details',
url: '/demo-2/1.png',
},
{
title: 'Histogram, perfect for showing active users',
url: '/demo-2/2.png',
},
{ title: 'Make your overview public', url: '/demo-2/3.png' },
{
title: 'See real time events from your users',
url: '/demo-2/4.png',
},
{ title: 'The classic line chart', url: '/demo-2/5.png' },
{
title: 'Bar charts to see your most popular content',
url: '/demo-2/6.png',
},
{ title: 'Get nice metric cards with graphs', url: '/demo-2/7.png' },
];
export function PreviewCarousel() {
return (
<Carousel
className="w-full"
opts={{ loop: true }}
plugins={[
Autoplay({
delay: 2000,
}),
]}
>
<CarouselContent>
{images.map((item) => (
<CarouselItem
key={item.url}
className="flex-[0_0_80%] max-w-3xl pl-8"
>
<div
style={{
aspectRatio: 2982 / 1484,
}}
>
<div className="p-1 rounded-xl overflow-hidden bg-gradient-to-b from-blue-100/50 to-white/50">
<Image
priority
className="w-full h-full object-cover rounded-lg"
src={item.url}
width={2982 * 0.5}
height={1484 * 0.5}
alt={item.title}
/>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="hidden md:visible" />
<CarouselNext className="hidden md:visible" />
</Carousel>
);
}

View File

@@ -27,7 +27,7 @@ export function Heading1({ children, className }: Props) {
return (
<h1
className={cn(
'text-4xl md:text-5xl font-bold text-slate-800 !leading-tight',
'text-4xl md:text-5xl font-bold text-slate-900 !leading-tight font-serif',
className
)}
>
@@ -39,7 +39,10 @@ export function Heading1({ children, className }: Props) {
export function Heading2({ children, className }: Props) {
return (
<h2
className={cn('text-4xl md:text-5xl font-bold text-slate-800', className)}
className={cn(
'text-4xl md:text-5xl font-bold text-slate-900 font-serif',
className
)}
>
{children}
</h2>
@@ -49,7 +52,23 @@ export function Heading2({ children, className }: Props) {
export function Heading3({ children, className }: Props) {
return (
<h3
className={cn('text-2xl md:text-3xl font-bold text-slate-800', className)}
className={cn(
'text-2xl md:text-3xl font-bold text-slate-900 font-serif',
className
)}
>
{children}
</h3>
);
}
export function Heading4({ children, className }: Props) {
return (
<h3
className={cn(
'text-xl md:text-2xl font-bold text-slate-900 font-serif',
className
)}
>
{children}
</h3>

View File

@@ -0,0 +1,145 @@
'use client';
import { cn } from '@/utils/cn';
import Image from 'next/image';
import { Heading3 } from './copy';
interface FeatureItem {
title: string;
description: string | React.ReactNode;
className: string;
image: string;
}
const features: FeatureItem[] = [
{
title: 'Visualize Your Data',
description: (
<p>
Gain a deep understanding of your data with our visualization tools.
</p>
),
className: '',
image: '/demo-3/img-1.png',
},
{
title: 'Get a good overview',
description: (
<p>
Even though we want to provide advanced charts and graphs, we also want
you to understand your data at a glance.
</p>
),
className: 'bg-slate-100',
image: '/demo-3/img-2.png',
},
{
title: 'Real-Time Data Access',
description: (
<>
<p>
Access all your events in real-time. No delays or waiting for data to
be accessible.
</p>
<p>
Mark events as conversions to highlight and soon notifications with
out iOS/Android app.
</p>
</>
),
className: '',
image: '/demo-3/img-3.png',
},
{
title: 'Unlimited dashboards with charts',
description: (
<>
<p>
Create beautiful charts and graphs to visualize your data and share
them with your team.
</p>
<div className="flex gap-2 flex-wrap">
<div className="border border-border px-3 py-1 rounded">
Linear
</div>
<div className="border border-border px-3 py-1 rounded"> Area</div>
<div className="border border-border px-3 py-1 rounded"> Bar</div>
<div className="border border-border px-3 py-1 rounded"> Map</div>
<div className="border border-border px-3 py-1 rounded"> Pie</div>
<div className="border border-border px-3 py-1 rounded">
Funnels
</div>
<div className="border border-border px-3 py-1 rounded">
Histogram
</div>
<div className="border border-border px-3 py-1 rounded">
Metrics
</div>
</div>
</>
),
className: 'bg-slate-100',
image: '/demo-3/img-4.png',
},
{
title: 'Understand your users',
description: (
<>
<p>
Deep dive into your user's behavior and understand how they interact
with your app/website.
</p>
</>
),
className: '',
image: '/demo-3/img-5.png',
},
];
export function Features() {
return (
<div className="flex flex-col">
{features.map((feature, i) => {
return (
<Feature key={feature.title} {...feature} even={i % 2 === 0}>
{feature.description}
</Feature>
);
})}
</div>
);
}
export function Feature({
title,
children,
className,
image,
even,
}: FeatureItem & { even: boolean; children: React.ReactNode }) {
return (
<section className={cn('py-16 group', className)}>
<div
className={cn(
'container flex min-h-[300px] items-center gap-16 justify-between max-md:flex-col-reverse',
!even && 'md:flex-row-reverse'
)}
>
<div className="flex flex-col w-full">
<Heading3 className="mb-2">{title}</Heading3>
<div className="prose-xl">{children}</div>
</div>
<div className="w-full">
<Image
src={image}
alt={title}
width={600}
height={400}
className="border-8 border-black/5 rounded-xl w-full max-w-xl group-hover:rotate-1 group-hover:scale-[101%] transition-transform duration-500"
/>
</div>
</div>
</section>
);
}

View File

@@ -1,56 +1,36 @@
import { Tooltip, TooltipContent } from '@/components/ui/tooltip';
import { TooltipTrigger } from '@radix-ui/react-tooltip';
import Image from 'next/image';
import { PreviewCarousel } from './carousel';
import { Heading1, Lead2 } from './copy';
import { JoinWaitlistHero } from './join-waitlist-hero';
import { SocialProofServer } from './social-proof';
import { SocialProof } from './social-proof/social-proof';
const avatars = [
'https://api.dicebear.com/7.x/adventurer/svg?seed=Chester&backgroundColor=b6e3f4',
'https://api.dicebear.com/7.x/adventurer/svg?seed=Casper&backgroundColor=c0aede',
'https://api.dicebear.com/7.x/adventurer/svg?seed=Boo&backgroundColor=ffdfbf',
];
export function Hero() {
return (
<div className="flex py-32 flex-col items-center w-full text-center bg-[#1F54FF] relative overflow-hidden">
{/* <div className="inset-0 absolute h-full w-full bg-[radial-gradient(circle,rgba(255,255,255,0.1)_0%,rgba(255,255,255,0)_100%)]"></div> */}
<div className="inset-0 absolute h-full w-full flex items-center justify-center">
<div className="w-[600px] h-[600px] ring-1 ring-white/05 rounded-full shrink-0"></div>
</div>
<div className="inset-0 absolute h-full w-full flex items-center justify-center">
<div className="w-[900px] h-[900px] ring-1 ring-white/10 rounded-full shrink-0"></div>
</div>
<div className="inset-0 absolute h-full w-full flex items-center justify-center">
<div className="w-[1200px] h-[1200px] ring-1 ring-white/20 rounded-full shrink-0"></div>
</div>
<div className="relative flex flex-col items-center max-w-3xl">
<Image
width={64}
height={64}
src="/logo-white.png"
alt="Openpanel Logo"
className="w-16 h-16 mb-8"
/>
<Heading1 className="mb-4 text-white">
An open-source
<br />
alternative to Mixpanel
</Heading1>
<Lead2 className="text-white/70 font-light">
The power of Mixpanel, the ease of Plausible <br />
and nothing from Google Analytics 😉
</Lead2>
<div className="my-12 w-full flex flex-col items-center">
<div className="relative overflow-hidden">
{/* <div className="bg-blue-50 w-2/5 h-full absolute top-0 right-0"></div> */}
<div className="container md:h-screen min-h-[700px] flex flex-col md:flex-row items-center relative max-md:pt-32 gap-4 md:gap-8">
<div className="flex-1 lg:min-w-[400px] sm:min-w-[350px] max-md:text-center">
<Heading1 className="mb-4 text-slate-950">
An open-source
<br />
alternative to Mixpanel
</Heading1>
<Lead2 className="mb-12">
The power of Mixpanel, the ease of Plausible and nothing from Google
Analytics 😉
</Lead2>
<JoinWaitlistHero />
<SocialProofServer className="mt-6" />
</div>
<div className="mt-16 md:pt-8 w-full">
<div className="bg-black/5 md:p-2 rounded-2xl h-[max(90vh,650px)] flex">
<iframe
src="https://dashboard.openpanel.dev/share/overview/ZQsEhG"
className="w-full h-full rounded-xl h-[max(90vh,650px)]"
title="Openpanel Dashboard"
scrolling="no"
/>
</div>
</div>
</div>
<PreviewCarousel />
</div>
);
}

View File

@@ -11,7 +11,6 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { cn } from '@/utils/cn';
import Link from 'next/link';
interface JoinWaitlistProps {
className?: string;
@@ -20,51 +19,33 @@ interface JoinWaitlistProps {
export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
const [value, setValue] = useState('');
const [open, setOpen] = useState(false);
const [success, setSuccess] = useState(false);
useEffect(() => {
if (open) {
// @ts-ignore
window.op?.('event', 'waitlist_open');
window.op('event', 'waitlist_success');
}
}, [open]);
useEffect(() => {
if (success) {
// @ts-ignore
window.op?.('event', 'waitlist_success', {
email: value,
});
}
}, [success]);
const renderSuccess = () => (
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Thanks so much!</DialogTitle>
<DialogDescription>
You're now on the waiting list. We'll let you know when we're ready.
Should be within a month or two 🚀
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setOpen(false)}>Got it!</Button>
</DialogFooter>
</DialogContent>
);
const renderForm = () => (
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Almost there!</DialogTitle>
<DialogDescription>
Enter your email to join the waiting list. We'll let you know when
we're ready.
</DialogDescription>
</DialogHeader>
return (
<>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Thanks so much!</DialogTitle>
<DialogDescription>
You're now on the waiting list. We'll let you know when we're
ready. Should be within a month or two 🚀
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setOpen(false)}>Got it!</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<form
className="w-full max-w-md"
onSubmit={(e) => {
e.preventDefault();
fetch('/api/waitlist', {
@@ -75,7 +56,7 @@ export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
},
}).then((res) => {
if (res.ok) {
setSuccess(true);
setOpen(true);
}
});
}}
@@ -95,42 +76,6 @@ export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
</Button>
</div>
</form>
</DialogContent>
);
return (
<>
<Dialog open={open} onOpenChange={setOpen}>
{success ? renderSuccess() : renderForm()}
</Dialog>
<div className="flex gap-4">
<Button
size="lg"
className="text-lg h-12"
onClick={() => {
setOpen(true);
}}
>
Join waitlist now
</Button>
<div className="relative">
<Link
href="https://dashboard.openpanel.dev/share/overview/ZQsEhG"
target="_blank"
rel="nofollow"
>
<Button size="lg" variant="outline" className="text-lg h-12">
Demo
</Button>
</Link>
<img
src="/clickable-demo.png"
className="w-44 shrink-0 absolute left-full -top-8 translate-x-10 max-w-none"
alt="Clickable demo button"
/>
</div>
</div>
</>
);
}

View File

@@ -1,6 +1,6 @@
import { cn } from '@/utils/cn';
import type { Metadata } from 'next';
import { Bricolage_Grotesque } from 'next/font/google';
import { Bricolage_Grotesque, Inter } from 'next/font/google';
import { OpenpanelProvider } from '@openpanel/nextjs';
@@ -9,6 +9,8 @@ import { defaultMeta } from './meta';
import '@/styles/globals.css';
import { Navbar } from './navbar';
export const metadata: Metadata = {
...defaultMeta,
alternates: {
@@ -16,10 +18,17 @@ export const metadata: Metadata = {
},
};
const font = Bricolage_Grotesque({
const head = Bricolage_Grotesque({
display: 'swap',
subsets: ['latin'],
weight: ['400', '700'],
variable: '--font-serif',
});
const body = Inter({
display: 'swap',
subsets: ['latin'],
weight: ['400', '700'],
variable: '--font-sans',
});
export default function RootLayout({
@@ -31,10 +40,12 @@ export default function RootLayout({
<html lang="en" className="light">
<body
className={cn(
'min-h-screen antialiased grainy text-slate-600',
font.className
'min-h-screen antialiased grainy text-slate-900 font-sans',
head.variable,
body.variable
)}
>
<Navbar darkText />
{children}
<Footer />
</body>

View File

@@ -9,6 +9,8 @@ export const defaultMeta: Metadata = {
description,
openGraph: {
title,
url: 'https://openpanel.dev',
type: 'website',
images: [
{
url: 'https://openpanel.dev/ogimage.png',

View File

@@ -18,7 +18,7 @@ export function Navbar({ darkText = false, className }: Props) {
className={cn('absolute top-0 left-0 right-0 z-10', textColor, className)}
>
<div className="container flex justify-between items-center py-4">
<Logo />
<Logo className="max-sm:[&_span]:hidden" />
<nav className="flex gap-4">
{pathname !== '/' && <Link href="/">Home</Link>}
<a href="https://docs.openpanel.dev" target="_blank">

View File

@@ -1,9 +1,11 @@
import { db } from '@openpanel/db';
import { ALink } from '@/components/ui/button';
import { ExternalLinkIcon } from 'lucide-react';
import { Heading2, Lead2, Paragraph } from './copy';
import { Features } from './features';
import { Hero } from './hero';
import { Navbar } from './navbar';
import { Sections } from './section';
import { Pricing } from './pricing';
import { PunchLines } from './punch-lines';
export const dynamic = 'force-dynamic';
export const revalidate = 3600;
@@ -11,22 +13,36 @@ export const revalidate = 3600;
export default function Page() {
return (
<div>
<Navbar darkText={false} className="[&_img]:hidden" />
<Hero />
<div className="container">
<div className="my-24">
<Heading2 className="md:text-5xl mb-2 leading-none">
<div className="py-24 bg-gradient-to-bl from-blue-600 to-blue-800">
<div className="container">
<Heading2 className="md:text-5xl mb-2 leading-none text-white">
Analytics should be easy
<br />
and powerful
</Heading2>
<Lead2>
<Lead2 className="text-white/80">
The power of Mixpanel, the ease of Plausible and nothing from Google
Analytics 😉
Analytics 😉 Curious how it looks?
</Lead2>
<ALink
href="https://dashboard.openpanel.dev/share/overview/ZQsEhG"
target="_blank"
className="mt-8"
variant={'outline'}
>
Check out the demo
<ExternalLinkIcon className="w-4 h-4 ml-2" />
</ALink>
</div>
<Sections />
</div>
<Features />
<PunchLines />
<Pricing />
<div className="container mt-40">
<div className="flex flex-col md:flex-row gap-8">
<div className="mb-4 md:w-1/2 flex-shrink-0 relative">

View File

@@ -0,0 +1,72 @@
import { CheckIcon } from 'lucide-react';
import { Heading2, Lead2 } from './copy';
export function Pricing() {
return (
<div className="bg-slate-200 py-32" id="#pricing">
<div className="container">
<section className="container flex flex-col gap-6 md:max-w-[64rem]">
<div className="mx-auto flex w-full flex-col gap-4 md:max-w-[58rem]">
<Heading2>Simple, transparent pricing</Heading2>
<Lead2 className="max-w-[85%] leading-normal text-muted-foreground sm:text-lg sm:leading-7">
Everything is included, just decide how many events you want to
track each month.
</Lead2>
</div>
<div className="grid w-full items-start gap-10 rounded-lg border md:p-10 md:grid-cols-[1fr_200px]">
<div className="grid gap-6">
<h3 className="text-xl font-bold sm:text-2xl">
What&apos;s included for{' '}
<span className="bg-slate-300 rounded px-0.5">all plans</span>
</h3>
<ul className="grid gap-3 text-sm text-muted-foreground sm:grid-cols-2">
<li className="flex items-center">
<CheckIcon className="mr-2 h-4 w-4" /> Unlimited websites/apps
</li>
<li className="flex items-center">
<CheckIcon className="mr-2 h-4 w-4" /> Unlimited Users
</li>
<li className="flex items-center">
<CheckIcon className="mr-2 h-4 w-4" /> Unlimted dashboards
</li>
<li className="flex items-center">
<CheckIcon className="mr-2 h-4 w-4" /> Unlimted charts
</li>
<li className="flex items-center">
<CheckIcon className="mr-2 h-4 w-4" /> Unlimted tracked
profiles
</li>
<li className="flex items-center font-bold text-slate-900">
<CheckIcon className="mr-2 h-4 w-4" /> Yes, its that simple
</li>
</ul>
</div>
<div className="flex flex-col gap-4 text-left md:text-right">
<div>
<p className="text-sm font-medium text-muted-foreground">
From
</p>
<h4 className="text-7xl font-bold">$10</h4>
<p className="text-sm font-medium text-muted-foreground">
billed monthly
</p>
</div>
{/* <Link href="/login" className={cn(buttonVariants({ size: "lg" }))}>
Get Started
</Link> */}
</div>
</div>
<div className="mx-auto flex w-full max-w-[58rem] flex-col gap-4">
<p className="max-w-[85%] leading-normal text-muted-foreground sm:leading-7">
Exact pricing will come soon, but we asure you, it will be cheaper
than the competition.
</p>
<p className="font-bold">During beta everything is free!</p>
</div>
</section>
</div>
</div>
);
}

View File

@@ -0,0 +1,141 @@
import { cn } from '@/utils/cn';
import type { LucideIcon, LucideProps } from 'lucide-react';
import {
ClockIcon,
CloudIcon,
CookieIcon,
DollarSignIcon,
HandshakeIcon,
KeyIcon,
} from 'lucide-react';
import Image from 'next/image';
import { Heading2, Heading3, Heading4 } from './copy';
const items = [
{
title: 'Own Your Own Data',
description: (
<p>
All our serveres are hosted in EU (Stockholm) and we are fully GDPR
compliant.
</p>
),
icon: KeyIcon,
color: '#2563EB',
className: 'bg-blue-light',
},
{
title: 'Cloud or Self-Hosting',
description: (
<p>
Choose between the flexibility of cloud-based hosting or the autonomy of
self-hosting to tailor your analytics infrastructure to your needs.
</p>
),
icon: CloudIcon,
color: '#ff7557',
className: '', // 'bg-[#ff7557]',
},
{
title: 'Real-Time Events',
description: (
<p>
Stay up-to-date with real-time event tracking, enabling instant insights
into user actions as they happen.
</p>
),
icon: ClockIcon,
color: '#7fe1d8',
className: '', // bg-[#7fe1d8]
},
{
title: 'No cookies!',
description: (
<p>
Our trackers are cookie-free, skip that annyoing cookie consent banner!
</p>
),
icon: CookieIcon,
color: '#f8bc3c',
className: 'bg-blue-dark', //'bg-[#f8bc3c]',
},
{
title: 'Cost-Effective',
description: (
<p>
We have combined the best from Mixpanel and Plausible. Cut the costs and
keep the features.
</p>
),
icon: DollarSignIcon,
color: '#0f7ea0',
className: 'bg-[#3ba974]',
},
{
title: 'Predictable pricing',
description: (
<p>You only pay for events, everything else is included. No surprises.</p>
),
icon: HandshakeIcon,
color: '#0f7ea0',
className: 'bg-[#3ba974]',
},
{
title: 'First Class React Native Support',
description: (
<p>
Our SDK is built with React Native in mind, making it easy to integrate
with your mobile apps.
</p>
),
icon: (({ className }: LucideProps) => {
return (
<Image
src="/react-native.svg"
alt="React Native"
className={cn(className, 'p-3')}
width={50}
height={50}
/>
);
}) as unknown as LucideIcon,
color: '#3ba974',
className: 'bg-[#e19900]',
},
];
export function PunchLines() {
return (
<div className="bg-slate-700 py-32">
<Heading2 className="text-white text-center mb-16">
Not convinced?
</Heading2>
<div className="container">
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{items.map((item) => {
const Icon = item.icon;
return (
<div
className="border border-border p-6 rounded-xl bg-white"
key={item.title}
>
<div
className={cn(
'h-14 w-14 rounded-full flex items-center justify-center mb-4',
item.color
)}
style={{ background: item.color }}
>
<Icon color="#fff" />
</div>
<Heading4>{item.title}</Heading4>
<div className="prose">{item.description}</div>
</div>
);
})}
</div>
</div>
</div>
);
}

View File

@@ -1,235 +0,0 @@
'use client';
import { cn } from '@/utils/cn';
import type { LucideIcon, LucideProps } from 'lucide-react';
import {
ArrowUpFromDotIcon,
BarChart2Icon,
BellIcon,
BookmarkIcon,
CheckCircle,
ClockIcon,
CloudIcon,
CloudLightningIcon,
CompassIcon,
ConeIcon,
DatabaseIcon,
DollarSignIcon,
DownloadIcon,
FilterIcon,
FolderIcon,
HandCoinsIcon,
HandshakeIcon,
KeyIcon,
PieChartIcon,
RouteIcon,
ServerIcon,
ShieldPlusIcon,
ShoppingCartIcon,
StarIcon,
ThumbsUpIcon,
TrendingUpIcon,
UserRoundSearchIcon,
UsersIcon,
WebhookIcon,
} from 'lucide-react';
import { Widget } from './widget';
interface SectionItem {
title: string;
description: string | React.ReactNode;
icon: LucideIcon;
color: string;
soon?: string;
icons: LucideIcon[];
className: string;
}
const sections: SectionItem[] = [
{
title: 'Own Your Own Data',
description: (
<>
<p>
Take control of your data privacy and ownership with our platform,
ensuring full transparency and security.
</p>
<p>
All our serveres are hosted in EU (Stockholm) and we are fully GDPR
compliant.
</p>
</>
),
icon: KeyIcon,
color: '#2563EB',
icons: [FolderIcon, DatabaseIcon, ShieldPlusIcon, KeyIcon],
className: 'bg-blue-light',
},
{
title: 'Cloud or Self-Hosting',
description: (
<p>
Choose between the flexibility of cloud-based hosting or the autonomy of
self-hosting to tailor your analytics infrastructure to your needs.
</p>
),
icon: CloudIcon,
color: '#ff7557',
icons: [CloudIcon, CheckCircle, ServerIcon, DownloadIcon],
className: '', // 'bg-[#ff7557]',
},
{
title: 'Real-Time Events',
description: (
<p>
Stay up-to-date with real-time event tracking, enabling instant insights
into user actions as they happen.
</p>
),
icon: ClockIcon,
color: '#7fe1d8',
icons: [CloudLightningIcon, ShoppingCartIcon, ArrowUpFromDotIcon],
className: '', // bg-[#7fe1d8]
},
{
title: 'Deep Dive into User Behaviors',
description: (
<p>
Gain profound insights into user behavior with comprehensive analytics
tools, allowing you to understand your audience's actions and
preferences.
</p>
),
icon: UserRoundSearchIcon,
color: '#f8bc3c',
icons: [UsersIcon, RouteIcon, BookmarkIcon],
className: 'bg-blue-dark', //'bg-[#f8bc3c]',
},
{
title: 'Powerful Report Explorer',
description: (
<p>
Explore and analyze your data effortlessly with our powerful report
explorer, simplifying the process of deriving meaningful insights.
</p>
),
icon: CompassIcon,
color: '#b3596e',
icons: [ThumbsUpIcon, TrendingUpIcon, PieChartIcon, BarChart2Icon],
className: 'bg-[#ff7557]',
},
{
soon: 'Coming soon',
title: 'Funnels',
description: (
<p>
Track user conversion funnels seamlessly, providing valuable insights
into user journey optimization.
</p>
),
icon: ConeIcon,
color: '#72bef4',
icons: [ConeIcon, FilterIcon],
className: '', //'bg-[#72bef4]',
},
{
soon: 'Coming with our native app',
title: 'Push Notifications',
description: (
<p>
Stay informed about conversions, events, and peaks with our upcoming
push notification tool, empowering you to monitor and respond to
critical activities in real-time.
</p>
),
icon: BellIcon,
color: '#ffb27a',
icons: [WebhookIcon, BellIcon],
className: '', //'bg-[#ffb27a]',
},
{
title: 'Cost-Effective Alternative to Mixpanel',
description: (
<p>
Enjoy the same powerful analytics capabilities as Mixpanel at a fraction
of the cost, ensuring affordability without compromising on quality.
</p>
),
icon: DollarSignIcon,
color: '#0f7ea0',
icons: [DollarSignIcon, HandCoinsIcon, HandshakeIcon, StarIcon],
className: 'bg-[#3ba974]',
},
{
soon: 'Something Plausible lacks',
title: 'Great Support for React Native',
description: (
<p>
Benefit from robust support for React Native, ensuring seamless
integration and compatibility for your projects, a feature notably
lacking in other platforms like Plausible.
</p>
),
icon: (({ className }: LucideProps) => {
return (
<img src="/react-native.svg" alt="React Native" className={className} />
);
}) as unknown as LucideIcon,
color: '#3ba974',
icons: [FolderIcon, DatabaseIcon, ShieldPlusIcon, KeyIcon],
className: 'bg-[#e19900]',
},
];
// To lazy to think now...
function checkIndex(index: number) {
switch (index) {
case 0:
case 3:
case 4:
case 7:
case 8:
case 10:
return true;
default:
return false;
}
}
export function Sections() {
return (
<>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 gap-y-16">
{sections.map((section, i) => {
const even = checkIndex(i);
const offsets = even
? [
'-top-10 -left-10 rotate-12',
'top-10 -rotate-12',
'-right-5',
'-right-10 -top-20',
]
: ['-top-10 -left-20 rotate-12', 'top-10 -rotate-12', '-right-5'];
const className = even
? cn('[&_*]:text-white/90 col-span-2', section.className)
: cn('border border-border', section.className);
return (
<Widget
key={section.title}
title={section.title}
className={className}
icons={section.icons}
offsets={offsets}
>
{section.description}
</Widget>
);
})}
</div>
</>
);
}

View File

@@ -11,7 +11,7 @@ export async function SocialProofServer(props: Props) {
const waitlistCount = await db.waitlist.count();
return (
<TooltipProvider>
<SocialProof count={waitlistCount} {...props} />;
<SocialProof count={waitlistCount} {...props} />
</TooltipProvider>
);
}

View File

@@ -1,21 +1,12 @@
'use client';
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { cn } from '@/utils/cn';
// import { StarIcon } from 'lucide-react';
import Image from 'next/image';
interface JoinWaitlistProps {
@@ -25,15 +16,15 @@ interface JoinWaitlistProps {
export function SocialProof({ className, count }: JoinWaitlistProps) {
return (
<div className={cn('flex gap-2 justify-center items-center', className)}>
<div className={cn('flex items-center gap-2', className)}>
<div className="flex">
<Tooltip>
<TooltipTrigger>
<Image
className="rounded-full"
src="/clickhouse.png"
width={24}
height={24}
width={40}
height={40}
alt="Clickhouse"
/>
</TooltipTrigger>
@@ -44,8 +35,8 @@ export function SocialProof({ className, count }: JoinWaitlistProps) {
<Image
className="rounded-full"
src="/getdreams.png"
width={24}
height={24}
width={40}
height={40}
alt="GetDreams"
/>
</TooltipTrigger>
@@ -56,17 +47,27 @@ export function SocialProof({ className, count }: JoinWaitlistProps) {
<Image
className="rounded-full"
src="/kiddokitchen.png"
width={24}
height={24}
width={40}
height={40}
alt="KiddoKitchen"
/>
</TooltipTrigger>
<TooltipContent>KiddoKitchen is here</TooltipContent>
</Tooltip>
</div>
<p className="text-white">
{count} early birds have already signed up! 🚀
</p>
<div>
<p className="text-left">{count} early birds have signed up! 🚀</p>
{/* <div className="flex gap-0.5">
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
<StarIcon size={16} color="#F5C962" fill="#F5C962" />
</div> */}
</div>
</div>
);
}
// <div class="flex flex-col gap-y-2 mt-5 lg:mt-3"><p class="text-gray-700 dark:text-gray-100 text-xs w-24 text-start font-semibold whitespace-nowrap">What users think</p><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://pbs.twimg.com/profile_images/1744063824431370240/BbVtyCiy_normal.png" alt="feedback_0"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Been a long time Mixpanel user and without a doubt there's a bunch of room to innovate. I'm confident Openpanel is on the right path! ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flame w-5 h-5 text-red-600 fill-orange-400 animate-pulse"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"></path></svg></div><img class="w-5 h-5 rounded-full border border-2 border-red-500" src="https://pbs.twimg.com/profile_images/1751607056316944384/8E4F88FL_normal.jpg" alt="feedback_1"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ I have used Openpanel for the last 6 months (since Im the creator) for 3 different sites/apps. Its a great analytics product that has everything you need. Still lacking a native app but will work hard to make that a reality! ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://pbs.twimg.com/profile_images/1701887174042324992/g2GBIQay_normal.jpg" alt="feedback_2"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ would be cool if it was easier to edit text after image is generated ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flame w-5 h-5 text-red-600 fill-orange-400 animate-pulse"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"></path></svg></div><img class="w-5 h-5 rounded-full border border-2 border-red-500" src="https://pbs.twimg.com/profile_images/1194368464946974728/1D2biimN_normal.jpg" alt="feedback_3"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Awesome product, very easy to use and understand. I miss a native app and the documentation could be improved. Otherwise I love it. ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://lh3.googleusercontent.com/a/ACg8ocIWiGTd3nWE5etp-CFhxrTKFvSLSJJd7pPmiM9SNJ9sAg=s96-c" alt="feedback_4"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ I have used Open panel since the private beta and i'm super impressed by the product already, the speed after you give feedback to actually get the features is truly amazing! Can't wait to see where Openpanel are in 6 months! ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://lh3.googleusercontent.com/a/ACg8ocKymAw_YoIrfoGp-bWMlDsXgM6St0dzaVJ7m_lGNXDtrA=s96-c" alt="feedback_5"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Impressively fast UI and easy to integrate! Added it alongside my current analytics tool for my native app in less than an hour. ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://pbs.twimg.com/profile_images/1735771119980879872/Mx5MlB9e_normal.jpg" alt="feedback_6"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Im using plausible and find it pleasing but limited.
// Im looking forward to trying out Openpanel, the demo pictures and the page look professional. The listed features seem to be broader then plausible. :) ”</span></div><div class="flex flex-row w-full gap-x-2 items-start"><div class="w-5 shrink-0"></div><img class="w-5 h-5 rounded-full " src="https://pbs.twimg.com/profile_images/1767459527006334976/unbMENPG_normal.jpg" alt="feedback_7"><span class="flex-wrap text-xs text-gray-600 dark:text-gray-200 text-start font-normal">“ Incredibly easy to implement and a joy to use. 5/5 would recommend. ”</span></div></div>

View File

@@ -1,43 +0,0 @@
import type { ReactNode } from 'react';
import { cn } from '@/utils/cn';
import type { LucideIcon } from 'lucide-react';
import { Heading3 } from './copy';
interface WidgetProps {
title: string;
children: ReactNode;
className?: string;
icons: LucideIcon[];
offsets: string[];
}
export function Widget({
title,
children,
className,
icons,
offsets,
}: WidgetProps) {
return (
<div
className={cn(
'p-10 rounded-xl relative overflow-hidden flex flex-col hover:scale-[101%] transition-all duration-300 ease-in-out bg-white hover:shadow min-h-[300px] max-md:col-span-3',
className
)}
>
<Heading3 className="mb-2">{title}</Heading3>
<div className="prose-xl">{children}</div>
<div className="flex justify-between mt-auto">
{icons.map((Icon, i) => (
<Icon
key={i}
size={120}
className={cn('flex-shrink-0 opacity-5 relative', offsets?.[i])}
strokeWidth={1.5}
/>
))}
</div>
</div>
);
}