update public web

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-02-06 22:26:31 +01:00
parent 473810984c
commit 41ca38c841
11 changed files with 523 additions and 367 deletions

View File

@@ -34,39 +34,36 @@ const images = [
{ title: 'The classic pie chart', url: '/demo/pie-min.png' },
];
export function HomeCarousel() {
export function PreviewCarousel() {
return (
<div className="mx-auto max-w-6xl p-4">
<div className="relative">
<div className="rounded-lg w-full max-w-6xl aspect-video dashed absolute -left-5 -top-5"></div>
<Carousel
className="w-full"
opts={{ loop: true }}
plugins={[
Autoplay({
delay: 2000,
}),
]}
>
<CarouselContent>
{images.map((item) => (
<CarouselItem key={item.url}>
<div className="aspect-video rounded-md overflow-hidden">
<Image
className="w-full h-full object-cover"
src={item.url}
width={1080}
height={608}
alt={item.title}
/>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="hidden md:visible" />
<CarouselNext className="hidden md:visible" />
</Carousel>
</div>
</div>
<Carousel
className="w-full"
opts={{ loop: true }}
plugins={[
Autoplay({
delay: 2000,
}),
]}
>
<CarouselContent>
{images.map((item) => (
<CarouselItem key={item.url} className="flex-[0_0_80%] pl-8">
<div className="aspect-video">
<div className="p-3 bg-white/20 rounded-xl overflow-hidden">
<Image
className="w-full h-full object-cover rounded-lg"
src={item.url}
width={1080}
height={608}
alt={item.title}
/>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="hidden md:visible" />
<CarouselNext className="hidden md:visible" />
</Carousel>
);
}

View File

@@ -25,7 +25,9 @@ export function Paragraph({ children, className }: Props) {
export function Heading1({ children, className }: Props) {
return (
<h1 className={cn('text-4xl md:text-6xl font-bold', className)}>
<h1
className={cn('text-5xl md:text-6xl font-bold text-slate-800', className)}
>
{children}
</h1>
);
@@ -33,8 +35,20 @@ export function Heading1({ children, className }: Props) {
export function Heading2({ children, className }: Props) {
return (
<h2 className={cn('text-2xl md:text-4xl font-bold', className)}>
<h2
className={cn('text-4xl md:text-5xl font-bold text-slate-800', className)}
>
{children}
</h2>
);
}
export function Heading3({ children, className }: Props) {
return (
<h3
className={cn('text-2xl md:text-3xl font-bold text-slate-800', className)}
>
{children}
</h3>
);
}

View File

@@ -0,0 +1,79 @@
// background-image: radial-gradient(circle at 1px 1px, black 1px, transparent 0);
// background-size: 40px 40px;
import { Logo } from '@/components/Logo';
import {
BarChart2Icon,
CookieIcon,
Globe2Icon,
LayoutPanelTopIcon,
LockIcon,
} from 'lucide-react';
import { Heading1, Lead, Lead2 } from './copy';
import { JoinWaitlist } from './join-waitlist';
const features = [
{
title: 'Great overview',
icon: LayoutPanelTopIcon,
},
{
title: 'Beautiful charts',
icon: BarChart2Icon,
},
{
title: 'Privacy focused',
icon: LockIcon,
},
{
title: 'Open-source',
icon: Globe2Icon,
},
{
title: 'No cookies',
icon: CookieIcon,
},
];
export function Hero() {
return (
<div>
<div className="absolute top-0 left-0 right-0 py-6">
<div className="container">
<div className="flex justify-between">
<Logo />
</div>
</div>
</div>
<div
className="flex flex-col items-center w-full text-center text-blue-950 bg-[radial-gradient(circle_at_2px_2px,#D9DEF6_2px,transparent_0)]"
style={{
backgroundSize: '70px 70px',
}}
>
<div className="py-20 pt-32 p-4 flex flex-col items-center max-w-3xl bg-[radial-gradient(circle,rgba(255,255,255,0.7)_0%,rgba(255,255,255,0.7)_50%,rgba(255,255,255,0)_100%)]">
<Heading1 className="mb-4">
A open-source
<br />
alternative to Mixpanel
</Heading1>
<p className="mb-8">
Combine Mixpanel and Plausible and you get Openpanel. A simple
analytics tool that respects privacy.
</p>
<JoinWaitlist />
<div className="flex flex-wrap gap-10 mt-8 max-w-xl justify-center">
{features.map(({ icon: Icon, title }) => (
<div className="flex gap-2 items-center justify-center">
<Icon className="text-blue-light " />
{title}
</div>
))}
</div>
</div>
</div>
</div>
);
}

View File

@@ -10,8 +10,13 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { cn } from '@/utils/cn';
export function JoinWaitlist() {
interface JoinWaitlistProps {
className?: string;
}
export function JoinWaitlist({ className }: JoinWaitlistProps) {
const [value, setValue] = useState('');
const [open, setOpen] = useState(false);
@@ -59,7 +64,10 @@ export function JoinWaitlist() {
<div className="relative w-full mb-8">
<input
placeholder="Enter your email"
className="border border-slate-100 rounded-md shadow-sm bg-white h-12 w-full px-4 outline-none focus:ring-1 ring-black"
className={cn(
'border border-slate-100 rounded-md shadow-sm bg-white h-12 w-full px-4 outline-none focus:ring-1 ring-black text-blue-darker',
className
)}
value={value}
onChange={(e) => setValue(e.target.value)}
/>

View File

@@ -3,6 +3,7 @@ import { cn } from '@/utils/cn';
import '@/styles/globals.css';
import type { Metadata } from 'next';
import { Bricolage_Grotesque } from 'next/font/google';
import Script from 'next/script';
import { defaultMeta } from './meta';
@@ -14,6 +15,12 @@ export const metadata: Metadata = {
},
};
const font = Bricolage_Grotesque({
display: 'swap',
subsets: ['latin'],
weights: [400, 700],
});
export default async function RootLayout({
children,
}: {
@@ -21,7 +28,12 @@ export default async function RootLayout({
}) {
return (
<html lang="en" className="light">
<body className={cn('min-h-screen font-sans antialiased grainy')}>
<body
className={cn(
'min-h-screen antialiased grainy text-slate-600',
font.className
)}
>
{children}
</body>
<Script

View File

@@ -1,169 +1,154 @@
import { Logo } from '@/components/Logo';
import { Button } from '@/components/ui/button';
import type { LucideIcon } from 'lucide-react';
import {
BarChart2,
BellIcon,
ClockIcon,
CloudIcon,
CompassIcon,
ConeIcon,
CookieIcon,
DollarSignIcon,
Globe2Icon,
KeyIcon,
LayoutPanelTopIcon,
LockIcon,
UserRoundSearchIcon,
UsersIcon,
} from 'lucide-react';
import Image from 'next/image';
import { HomeCarousel } from './carousel';
import { Heading1, Heading2, Lead, Lead2, Paragraph } from './copy';
import { PreviewCarousel } from './carousel';
import { Heading2, Lead2, Paragraph } from './copy';
import { Hero } from './hero';
import { JoinWaitlist } from './join-waitlist';
import { Section, Sections } from './section';
const features = [
{
title: 'Great overview',
icon: LayoutPanelTopIcon,
},
{
title: 'Beautiful charts',
icon: BarChart2,
},
{
title: 'Privacy focused',
icon: LockIcon,
},
{
title: 'Open-source',
icon: Globe2Icon,
},
{
title: 'No cookies',
icon: CookieIcon,
},
{
title: 'User journey',
icon: UsersIcon,
},
];
import { Sections } from './section';
export default function Page() {
return (
<div>
<div className="max-w-6xl p-4 mx-auto absolute top-0 left-0 right-0 py-6">
<div className="flex justify-between">
<Logo />
</div>
<Hero />
<div className="bg-gradient-to-b from-blue-light to-[#FFFFFF] py-16 md:py-40 text-center">
<PreviewCarousel />
</div>
<div className="flex flex-col items-center bg-gradient-to-br from-white via-white to-blue-200 w-full text-center text-blue-950">
<div className="py-20 pt-32 p-4 flex flex-col items-center max-w-3xl ">
<Heading1 className="mb-4 fancy-text">
A open-source
<div className="container">
<div className="mb-24">
<Heading2 className="md:text-5xl mb-2 leading-none">
Analytics should be easy
<br />
alternative to Mixpanel
</Heading1>
<Lead className="mb-8">
Combine Mixpanel and Plausible and you get Openpanel. A simple
analytics tool that respects privacy.
</Lead>
<JoinWaitlist />
<div className="grid grid-cols-2 md:grid-cols-3 gap-8 mt-8">
{features.map(({ icon: Icon, title }) => (
<div className="flex gap-2 items-center justify-center">
<Icon />
{title}
and powerful
</Heading2>
<Lead2>
The power of Mixpanel, the ease of Plausible and nothing from Google
Analytics 😉
</Lead2>
</div>
<Sections />
</div>
<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">
<Heading2>Another analytic tool? Really?</Heading2>
{/* <SirenIcon
strokeWidth={0.5}
size={300}
className="opacity-10 absolute -rotate-12 -left-20 -top-10"
/> */}
</div>
<div className="flex flex-col gap-4 max-w-3xl">
<h3 className="text-lg font-bold text-blue-dark">TL;DR</h3>
<Paragraph>
Our open-source analytic library fills a crucial gap by combining
the strengths of Mixpanel's powerful features with Plausible's
clear overview page. Motivated by the lack of an open-source
alternative to Mixpanel and inspired by Plausible's simplicity, we
aim to create an intuitive platform with predictable pricing. With
a single-tier pricing model and limits only on monthly event
counts, our goal is to democratize analytics, offering
unrestricted access to all features while ensuring affordability
and transparency for users of all project sizes.
</Paragraph>
<h3 className="text-lg font-bold text-blue-dark mt-12">The why</h3>
<Paragraph>
Our open-source analytic library emerged from a clear need within
the analytics community. While platforms like Mixpanel offer
powerful and user-friendly features, they lack a comprehensive
overview page akin to Plausible's, which succinctly summarizes
essential metrics. Recognizing this gap, we saw an opportunity to
combine the strengths of both platforms while addressing their
respective shortcomings.
</Paragraph>
<Paragraph>
One significant motivation behind our endeavor was the absence of
an open-source alternative to Mixpanel. We believe in the
importance of accessibility and transparency in analytics, which
led us to embark on creating a solution that anyone can freely use
and contribute to.
</Paragraph>
<Paragraph>
Inspired by Plausible's exemplary approach to simplicity and
clarity, we aim to build upon their foundation and further refine
the user experience. By harnessing the best practices demonstrated
by Plausible, we aspire to create an intuitive and streamlined
analytics platform that empowers users to derive actionable
insights effortlessly.
</Paragraph>
<Paragraph>
Our own experiences with traditional analytics platforms like
Mixpanel underscored another critical aspect driving our project:
the need for predictable pricing. As project owners ourselves, we
encountered the frustration of escalating costs as our user base
grew. Therefore, we are committed to offering a single-tier
pricing model that provides unlimited access to all features
without the fear of unexpected expenses.
</Paragraph>
<Paragraph>
In line with our commitment to fairness and accessibility, our
pricing model will only impose limits on the number of events
users can send each month. This approach, akin to Plausible's,
ensures that users have the freedom to explore and utilize our
platform to its fullest potential without arbitrary restrictions
on reports or user counts. Ultimately, our goal is to democratize
analytics by offering a reliable, transparent, and cost-effective
solution for projects of all sizes.
</Paragraph>
</div>
</div>
</div>
<footer className="bg-blue-darker text-white relative mt-40 relative">
<div className="inset-0 absolute h-full w-full bg-[radial-gradient(circle,rgba(255,255,255,0.2)_0%,rgba(255,255,255,0)_100%)]"></div>
<div className="relative container flex flex-col items-center text-center">
<div className="my-24">
<Heading2 className="text-white mb-2">Get early access</Heading2>
<Lead2>
Ready to set your analytics free? Get on our waitlist.
</Lead2>
<div className="mt-8">
<JoinWaitlist className="text-white bg-white/20 border-white/30 focus:ring-white" />
</div>
</div>
<div className="overflow-hidden rounded-xl">
<div className="p-3 bg-white/20">
<Image
src="/demo/overview-min.png"
width={1080}
height={608}
alt="Openpanel overview page"
className="w-full rounded-lg"
/>
</div>
</div>
</div>
<div className="absolute bottom-0 left-0 right-0">
<div className="h-px w-full bg-[radial-gradient(circle,rgba(255,255,255,0.7)_0%,rgba(255,255,255,0.7)_50%,rgba(255,255,255,0)_100%)]"></div>
<div className="p-4 bg-blue-darker">
<div className="container">
<div className="flex justify-between items-center text-sm">
<Logo />
<a
className="hover:underline"
href="https://twitter.com/CarlLindesvard"
target="_blank"
rel="nofollow"
>
Follow on X
</a>
</div>
))}
</div>
</div>
</div>
</div>
<Sections />
<div className="bg-blue-800 p-4 py-8 md:py-16 text-center">
<Heading2 className="text-slate-100 mb-4">
Get a feel how it looks
</Heading2>
<Lead className="text-slate-200 mb-16">
We've crafted a clean and intuitive interface because analytics should
<br />
be straightforward, unlike the complexity often associated with Google
Analytics. 😅
</Lead>
<HomeCarousel />
</div>
<div className="p-4 py-8 md:py-16 text-center flex flex-col items-center">
<Heading2 className="mb-4">Another analytic tool?</Heading2>
<div className="flex flex-col gap-4 max-w-3xl">
<Paragraph>
<strong>TL;DR</strong> Our open-source analytic library fills a
crucial gap by combining the strengths of Mixpanel's powerful
features with Plausible's clear overview page. Motivated by the lack
of an open-source alternative to Mixpanel and inspired by
Plausible's simplicity, we aim to create an intuitive platform with
predictable pricing. With a single-tier pricing model and limits
only on monthly event counts, our goal is to democratize analytics,
offering unrestricted access to all features while ensuring
affordability and transparency for users of all project sizes.
</Paragraph>
<div className="flex gap-2 w-full justify-center my-8">
<div className="rounded-full h-2 w-10 bg-blue-200"></div>
<div className="rounded-full h-2 w-10 bg-blue-400"></div>
<div className="rounded-full h-2 w-10 bg-blue-600"></div>
<div className="rounded-full h-2 w-10 bg-blue-800"></div>
</div>
<Paragraph>
Our open-source analytic library emerged from a clear need within
the analytics community. While platforms like Mixpanel offer
powerful and user-friendly features, they lack a comprehensive
overview page akin to Plausible's, which succinctly summarizes
essential metrics. Recognizing this gap, we saw an opportunity to
combine the strengths of both platforms while addressing their
respective shortcomings.
</Paragraph>
<Paragraph>
One significant motivation behind our endeavor was the absence of an
open-source alternative to Mixpanel. We believe in the importance of
accessibility and transparency in analytics, which led us to embark
on creating a solution that anyone can freely use and contribute to.
</Paragraph>
<Paragraph>
Inspired by Plausible's exemplary approach to simplicity and
clarity, we aim to build upon their foundation and further refine
the user experience. By harnessing the best practices demonstrated
by Plausible, we aspire to create an intuitive and streamlined
analytics platform that empowers users to derive actionable insights
effortlessly.
</Paragraph>
<Paragraph>
Our own experiences with traditional analytics platforms like
Mixpanel underscored another critical aspect driving our project:
the need for predictable pricing. As project owners ourselves, we
encountered the frustration of escalating costs as our user base
grew. Therefore, we are committed to offering a single-tier pricing
model that provides unlimited access to all features without the
fear of unexpected expenses.
</Paragraph>
<Paragraph>
In line with our commitment to fairness and accessibility, our
pricing model will only impose limits on the number of events users
can send each month. This approach, akin to Plausible's, ensures
that users have the freedom to explore and utilize our platform to
its fullest potential without arbitrary restrictions on reports or
user counts. Ultimately, our goal is to democratize analytics by
offering a reliable, transparent, and cost-effective solution for
projects of all sizes.
</Paragraph>
</div>
</div>
</footer>
</div>
);
}

View File

@@ -3,14 +3,41 @@
import { cn } from '@/utils/cn';
import type { LucideIcon, LucideProps } from 'lucide-react';
import {
ArrowUpFromDotIcon,
BarChart2Icon,
BellIcon,
BookmarkIcon,
CheckCircle,
ClockIcon,
CloudIcon,
CloudLightning,
CloudLightningIcon,
CompassIcon,
ConeIcon,
DatabaseIcon,
DollarSignIcon,
DownloadIcon,
FileIcon,
FilterIcon,
FolderIcon,
FolderOpenIcon,
HandCoinsIcon,
HandshakeIcon,
KeyIcon,
PieChartIcon,
PointerIcon,
RouteIcon,
ServerIcon,
ShieldPlusIcon,
ShoppingCartIcon,
SquareUserRound,
StarIcon,
ThumbsUp,
ThumbsUpIcon,
TrendingUpIcon,
UserRoundSearchIcon,
UsersIcon,
WebhookIcon,
} from 'lucide-react';
import {
@@ -25,6 +52,7 @@ import {
Blob9,
} from './blob';
import { Heading2, Lead2 } from './copy';
import { Widget } from './widget';
interface SectionItem {
title: string;
@@ -32,7 +60,8 @@ interface SectionItem {
icon: LucideIcon;
color: string;
soon?: string;
blob: React.ComponentType<LucideProps>;
icons: LucideIcon[];
className: string;
}
const sections: SectionItem[] = [
@@ -42,7 +71,8 @@ const sections: SectionItem[] = [
'Take control of your data privacy and ownership with our platform, ensuring full transparency and security.',
icon: KeyIcon,
color: '#2563EB',
blob: Blob1,
icons: [FolderIcon, DatabaseIcon, ShieldPlusIcon, KeyIcon],
className: 'bg-blue-light',
},
{
title: 'Cloud or Self-Hosting',
@@ -50,7 +80,8 @@ const sections: SectionItem[] = [
'Choose between the flexibility of cloud-based hosting or the autonomy of self-hosting to tailor your analytics infrastructure to your needs.',
icon: CloudIcon,
color: '#ff7557',
blob: Blob2,
icons: [CloudIcon, CheckCircle, ServerIcon, DownloadIcon],
className: '', // 'bg-[#ff7557]',
},
{
title: 'Real-Time Events',
@@ -58,15 +89,17 @@ const sections: SectionItem[] = [
'Stay up-to-date with real-time event tracking, enabling instant insights into user actions as they happen.',
icon: ClockIcon,
color: '#7fe1d8',
blob: Blob3,
icons: [CloudLightningIcon, ShoppingCartIcon, ArrowUpFromDotIcon],
className: '', // bg-[#7fe1d8]
},
{
title: 'Deep Dive into User Behavior',
title: 'Deep Dive into User Behaviors',
description:
"Gain profound insights into user behavior with comprehensive analytics tools, allowing you to understand your audience's actions and preferences.",
icon: UserRoundSearchIcon,
color: '#f8bc3c',
blob: Blob4,
icons: [UsersIcon, RouteIcon, BookmarkIcon],
className: 'bg-blue-dark', //'bg-[#f8bc3c]',
},
{
title: 'Powerful Report Explorer',
@@ -74,7 +107,8 @@ const sections: SectionItem[] = [
'Explore and analyze your data effortlessly with our powerful report explorer, simplifying the process of deriving meaningful insights.',
icon: CompassIcon,
color: '#b3596e',
blob: Blob5,
icons: [ThumbsUpIcon, TrendingUpIcon, PieChartIcon, BarChart2Icon],
className: 'bg-[#ff7557]',
},
{
soon: 'Coming soon',
@@ -83,7 +117,8 @@ const sections: SectionItem[] = [
'Track user conversion funnels seamlessly, providing valuable insights into user journey optimization.',
icon: ConeIcon,
color: '#72bef4',
blob: Blob6,
icons: [ConeIcon, FilterIcon],
className: '', //'bg-[#72bef4]',
},
{
soon: 'Coming with our native app',
@@ -92,7 +127,8 @@ const sections: SectionItem[] = [
'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.',
icon: BellIcon,
color: '#ffb27a',
blob: Blob7,
icons: [WebhookIcon, BellIcon],
className: '', //'bg-[#ffb27a]',
},
{
title: 'Cost-Effective Alternative to Mixpanel',
@@ -100,7 +136,8 @@ const sections: SectionItem[] = [
'Enjoy the same powerful analytics capabilities as Mixpanel at a fraction of the cost, ensuring affordability without compromising on quality.',
icon: DollarSignIcon,
color: '#0f7ea0',
blob: Blob8,
icons: [DollarSignIcon, HandCoinsIcon, HandshakeIcon, StarIcon],
className: 'bg-[#3ba974]',
},
{
soon: 'Something Plausible lacks',
@@ -113,61 +150,59 @@ const sections: SectionItem[] = [
);
}) as unknown as LucideIcon,
color: '#3ba974',
blob: Blob9,
icons: [FolderIcon, DatabaseIcon, ShieldPlusIcon, KeyIcon],
className: 'bg-[#f8bc3c]',
},
];
interface SectionProps extends SectionItem {
reverse?: boolean;
}
export function Section({
title,
description,
icon: Icon,
blob: Blob,
color,
soon,
reverse,
}: SectionProps) {
return (
<div key={title} className={'border-b border-border'}>
<div className="w-full max-w-6xl mx-auto px-4">
<div
className={cn(
'flex py-16 flex-col justify-center',
reverse ? 'md:flex-row' : 'md:flex-row-reverse'
)}
>
<div className="md:w-1/2 flex-shrink-0 justify-center items-center flex max-md:mb-8 overflow-hidden rounded-lg">
<div className="bg-slate-50 rounded-3xl">
<Blob
style={{ fill: color }}
className="w-[600px] opacity-20 transition-transform animate-[spin_60s_ease-in-out_infinite] -m-[100px]"
/>
</div>
<Icon className="w-40 h-40 absolute" strokeWidth={2} />
</div>
<div className="justify-center flex-col flex">
{!!soon && (
<div className="rounded-full border border-border p-2 px-4 leading-none mb-4 self-start">
{soon}
</div>
)}
<Heading2 className="mb-4">{title}</Heading2>
<Lead2>{description}</Lead2>
</div>
</div>
</div>
</div>
);
// 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 (
<>
{sections.map((section, index) => (
<Section key={index} {...section} reverse={index % 2 === 1} />
))}
<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 [&_h3]:text-white 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}
>
<p>{section.description}</p>
</Widget>
);
})}
</div>
</>
);
}

View File

@@ -0,0 +1,43 @@
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-105 transition-all duration-300 ease-in-out bg-white hover:shadow min-h-[300px] max-md:col-span-3',
className
)}
>
<Heading3 className="mb-4">{title}</Heading3>
{children}
<div className="flex justify-between mt-auto">
{icons.map((Icon, i) => (
<Icon
key={i}
size={120}
className={cn('flex-shrink-0 opacity-10 relative', offsets?.[i])}
strokeWidth={1.5}
/>
))}
</div>
</div>
);
}