fix(public): make all around improvements

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-11-14 10:39:05 +01:00
parent 4e8afb4eea
commit ab4efcfd77
13 changed files with 6185 additions and 135 deletions

View File

@@ -9,6 +9,8 @@ export default function Layout({
children: ReactNode; children: ReactNode;
}): React.ReactElement { }): React.ReactElement {
return ( return (
<>
<Navbar />
<main className="overflow-hidden"> <main className="overflow-hidden">
<HeroContainer className="h-screen pointer-events-none" /> <HeroContainer className="h-screen pointer-events-none" />
<div className="absolute h-screen inset-0 radial-gradient-dot-pages select-none pointer-events-none" /> <div className="absolute h-screen inset-0 radial-gradient-dot-pages select-none pointer-events-none" />
@@ -16,5 +18,7 @@ export default function Layout({
{children} {children}
</div> </div>
</main> </main>
<Footer />
</>
); );
} }

View File

@@ -60,11 +60,7 @@ export default async function Layout({ children }: { children: ReactNode }) {
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body className={cn(GeistSans.variable, GeistMono.variable)}> <body className={cn(GeistSans.variable, GeistMono.variable)}>
<RootProvider> <RootProvider>
<TooltipProvider> <TooltipProvider>{children}</TooltipProvider>
<Navbar />
{children}
<Footer />
</TooltipProvider>
</RootProvider> </RootProvider>
</body> </body>
</html> </html>

View File

@@ -1,4 +1,6 @@
import { Footer } from '@/components/footer';
import { Hero } from '@/components/hero'; import { Hero } from '@/components/hero';
import Navbar from '@/components/navbar';
import { Faq } from '@/components/sections/faq'; import { Faq } from '@/components/sections/faq';
import { Features } from '@/components/sections/features'; import { Features } from '@/components/sections/features';
import { Pricing } from '@/components/sections/pricing'; import { Pricing } from '@/components/sections/pricing';
@@ -16,6 +18,8 @@ export const experimental_ppr = true;
export default function HomePage() { export default function HomePage() {
return ( return (
<>
<Navbar />
<main> <main>
<Hero /> <Hero />
<Features /> <Features />
@@ -35,5 +39,7 @@ export default function HomePage() {
<Pricing /> <Pricing />
<Sdks /> <Sdks />
</main> </main>
<Footer />
</>
); );
} }

View File

@@ -1,9 +1,8 @@
'use client'; 'use client';
import { useIsDarkMode } from '@/lib/dark-mode';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { AnimatePresence, motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useEffect, useState } from 'react'; import { useState } from 'react';
import { Button } from './ui/button'; import { Button } from './ui/button';
type Frame = { type Frame = {
@@ -14,22 +13,11 @@ type Frame = {
}; };
function LivePreview() { function LivePreview() {
const isDark = useIsDarkMode();
const colorScheme = isDark ? 'dark' : 'light';
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(true);
}, []);
if (!loaded) {
return null;
}
return ( return (
<iframe <iframe
// src={`http://localhost:3000/share/overview/zef2XC?header=0&range=30d&colorScheme=${colorScheme}`} src={
src={`https://dashboard.openpanel.dev/share/overview/zef2XC?header=0&range=30d&colorScheme=${colorScheme}`} 'https://dashboard.openpanel.dev/share/overview/zef2XC?header=0&range=30d'
}
className="w-full h-full" className="w-full h-full"
title="Live preview" title="Live preview"
scrolling="no" scrolling="no"
@@ -38,15 +26,17 @@ function LivePreview() {
} }
function Image({ src }: { src: string }) { function Image({ src }: { src: string }) {
const isDark = useIsDarkMode();
const colorScheme = isDark ? 'dark' : 'light';
return ( return (
<div> <div>
<img <img
className="w-full h-full" className="w-full h-full block dark:hidden"
src={`/${src}-${colorScheme}.png`} src={`/${src}-light.png`}
alt={src} alt={`${src} light`}
/>
<img
className="w-full h-full hidden dark:block"
src={`/${src}-dark.png`}
alt={`${src} dark`}
/> />
</div> </div>
); );
@@ -78,12 +68,12 @@ export function HeroCarousel() {
label: 'Retention', label: 'Retention',
Component: () => <Image src="retention" />, Component: () => <Image src="retention" />,
}, },
// { {
// id: 'profile', id: 'profile',
// key: 'profile', key: 'profile',
// label: 'Inspect profile', label: 'Inspect profile',
// Component: () => <Image src="profile" />, Component: () => <Image src="profile" />,
// }, },
]; ];
const [activeFrames, setActiveFrames] = useState<Frame[]>([frames[0]]); const [activeFrames, setActiveFrames] = useState<Frame[]>([frames[0]]);

View File

@@ -1,9 +1,10 @@
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { DollarSignIcon } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
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 { Button } from './ui/button'; import { Button } from './ui/button';
import { WorldMap } from './world-map';
export function Hero() { export function Hero() {
return ( return (
@@ -13,7 +14,11 @@ export function Hero() {
{/* Content */} {/* Content */}
<div className="container relative z-10"> <div className="container relative z-10">
<div className="max-w-2xl col gap-4 pt-28 text-center mx-auto "> <div className="max-w-2xl col gap-4 pt-44 text-center mx-auto ">
<Tag className="self-center">
<DollarSignIcon className="size-4" />
Free during beta
</Tag>
<h1 className="text-4xl md:text-6xl font-bold leading-[1.1]"> <h1 className="text-4xl md:text-6xl font-bold leading-[1.1]">
An open-source alternative to <span>Mixpanel</span> An open-source alternative to <span>Mixpanel</span>
</h1> </h1>
@@ -31,7 +36,7 @@ export function Hero() {
</Link> </Link>
</Button> </Button>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Free for 30 days, no credit card required Free trail for 30 days, no credit card required
</p> </p>
</div> </div>

View File

@@ -12,7 +12,6 @@ import { Logo } from './logo';
import { Button } from './ui/button'; import { Button } from './ui/button';
const Navbar = () => { const Navbar = () => {
const pathname = usePathname();
const [isScrolled, setIsScrolled] = useState(false); const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const navbarRef = useRef<HTMLDivElement>(null); const navbarRef = useRef<HTMLDivElement>(null);
@@ -36,10 +35,6 @@ const Navbar = () => {
return () => window.removeEventListener('click', handleClick); return () => window.removeEventListener('click', handleClick);
}, [isMobileMenuOpen]); }, [isMobileMenuOpen]);
if (pathname.startsWith('/docs')) {
return null;
}
return ( return (
<nav className="fixed top-4 z-50 w-full" ref={navbarRef}> <nav className="fixed top-4 z-50 w-full" ref={navbarRef}>
<div className="container"> <div className="container">

View File

@@ -194,7 +194,7 @@ export function EventsFeature() {
return ( return (
<div className="overflow-hidden p-8 max-h-[700px]"> <div className="overflow-hidden p-8 max-h-[700px]">
<div <div
className="min-w-[1000px] gap-4 flex flex-col overflow-hidden relative isolate" className="min-w-[500px] gap-4 flex flex-col overflow-hidden relative isolate"
// style={{ height: 60 * TOTAL_EVENTS + 16 * (TOTAL_EVENTS - 1) }} // style={{ height: 60 * TOTAL_EVENTS + 16 * (TOTAL_EVENTS - 1) }}
> >
<AnimatePresence mode="popLayout" initial={false}> <AnimatePresence mode="popLayout" initial={false}>
@@ -228,7 +228,9 @@ export function EventsFeature() {
{event.location} {event.location}
</div> </div>
<div className="w-[150px] py-2 px-4 truncate"> <div className="w-[150px] py-2 px-4 truncate">
<img src={getPlatformIcon(event.platform)} alt="" /> <span className="mr-2 text-xl relative top-px">
{getPlatformIcon(event.platform)}
</span>
{event.platform} {event.platform}
</div> </div>
</motion.div> </motion.div>

View File

@@ -38,25 +38,18 @@ const COHORT_DATA = [
{ {
week: 'Week 7', week: 'Week 7',
users: '2,108', users: '2,108',
retention: [100, 82, 71, 65, 61, 57], retention: [100, 82, 71, 65, 61],
}, },
{ {
week: 'Week 8', week: 'Week 8',
users: '1,896', users: '1,896',
retention: [100, 83, 72, 66, 62, 58], retention: [100, 83, 72, 66],
}, },
{ {
week: 'Week 9', week: 'Week 9',
users: '2,156', users: '2,156',
retention: [100, 81, 70, 64, 60, 56], retention: [100, 81, 70],
}, },
{ week: 'Week 10', users: '2,089', retention: [100, 84, 73, 67, 63] },
{ week: 'Week 11', users: '1,967', retention: [100, 82, 71, 65] },
{ week: 'Week 12', users: '2,198', retention: [100, 83, 72] },
{ week: 'Week 13', users: '2,045', retention: [100, 81] },
// { week: 'Week 14', users: '1,978', retention: [100, 84, 73] },
// { week: 'Week 15', users: '2,134', retention: [100, 82] },
// { week: 'Week 16', users: '1,923', retention: [100] },
]; ];
const COHORT_DATA_ALT = [ const COHORT_DATA_ALT = [
{ {
@@ -92,25 +85,18 @@ const COHORT_DATA_ALT = [
{ {
week: 'Week 7', week: 'Week 7',
users: '2,432', users: '2,432',
retention: [100, 93, 88, 72, 64, 60], retention: [100, 93, 88, 72, 64],
}, },
{ {
week: 'Week 8', week: 'Week 8',
users: '2,123', users: '2,123',
retention: [100, 78, 76, 69, 65, 61], retention: [100, 78, 76, 69],
}, },
{ {
week: 'Week 9', week: 'Week 9',
users: '2,567', users: '2,567',
retention: [100, 70, 64, 61, 59, 58], retention: [100, 70, 64],
}, },
{ week: 'Week 10', users: '2,345', retention: [100, 88, 77, 71, 67] },
{ week: 'Week 11', users: '2,234', retention: [100, 86, 75, 69] },
{ week: 'Week 12', users: '2,543', retention: [100, 79, 76] },
{ week: 'Week 13', users: '2,321', retention: [100, 77] },
// { week: 'Week 14', users: '1,978', retention: [100, 84, 73] },
// { week: 'Week 15', users: '2,134', retention: [100, 82] },
// { week: 'Week 16', users: '1,923', retention: [100] },
]; ];
function RetentionCell({ percentage }: { percentage: number }) { function RetentionCell({ percentage }: { percentage: number }) {

View File

@@ -6,6 +6,15 @@ import * as React from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip';
function useMediaQuery(query: string) {
const [matches, setMatches] = React.useState(false);
React.useEffect(() => {
const media = window.matchMedia(query);
setMatches(media.matches);
}, [query]);
return matches;
}
const Slider = ({ const Slider = ({
ref, ref,
className, className,
@@ -19,7 +28,13 @@ const Slider = ({
max: number; max: number;
step: number; step: number;
onValueChange: (value: number[]) => void; onValueChange: (value: number[]) => void;
}) => ( }) => {
const isDesktop = useMediaQuery('(min-width: 768px)');
return (
<>
{!isDesktop && (
<div className="text-sm text-muted-foreground mb-4">{tooltip}</div>
)}
<SliderPrimitive.Root <SliderPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
@@ -31,7 +46,7 @@ const Slider = ({
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-white/10"> <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-white/10">
<SliderPrimitive.Range className="absolute h-full bg-white/90" /> <SliderPrimitive.Range className="absolute h-full bg-white/90" />
</SliderPrimitive.Track> </SliderPrimitive.Track>
{tooltip ? ( {tooltip && isDesktop ? (
<Tooltip open disableHoverableContent> <Tooltip open disableHoverableContent>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-white bg-black ring-offset-black transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" /> <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-white bg-black ring-offset-black transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
@@ -48,7 +63,9 @@ const Slider = ({
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-white bg-black ring-offset-black transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" /> <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-white bg-black ring-offset-black transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
)} )}
</SliderPrimitive.Root> </SliderPrimitive.Root>
); </>
);
};
Slider.displayName = SliderPrimitive.Root.displayName; Slider.displayName = SliderPrimitive.Root.displayName;
export { Slider }; export { Slider };

File diff suppressed because it is too large Load Diff

View File

@@ -58,18 +58,38 @@ const COORDINATES = [
{ lat: -43.532, lng: 172.6306 }, // Christchurch, New Zealand { lat: -43.532, lng: 172.6306 }, // Christchurch, New Zealand
]; ];
const getRandomCoordinates = (count: number) => {
const shuffled = [...COORDINATES].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
};
export function WorldMap() { export function WorldMap() {
const [visiblePins, setVisiblePins] = useState<typeof COORDINATES>([]); const [visiblePins, setVisiblePins] = useState<typeof COORDINATES>([
{ lat: 61.2181, lng: -149.9003 },
{ lat: 31.2304, lng: 121.4737 },
{ lat: 59.5613, lng: 150.8086 },
{ lat: 64.8378, lng: -147.7164 },
{ lat: -33.8688, lng: 151.2093 },
{ lat: 43.0621, lng: 141.3544 },
{ lat: 58.3019, lng: -134.4197 },
{ lat: 37.5665, lng: 126.978 },
{ lat: -41.2865, lng: 174.7762 },
{ lat: -36.8485, lng: 174.7633 },
{ lat: -31.9505, lng: 115.8605 },
{ lat: 35.6762, lng: 139.6503 },
{ lat: 49.2827, lng: -123.1207 },
{ lat: -12.4634, lng: 130.8456 },
{ lat: 56.1304, lng: 101.614 },
{ lat: 22.3193, lng: 114.1694 },
{ lat: 55.3422, lng: -131.6461 },
{ lat: 32.7157, lng: -117.1611 },
{ lat: 61.5815, lng: -149.444 },
{ lat: 60.5544, lng: -151.2583 },
]);
const activePinColor = '#2265EC'; const activePinColor = '#2265EC';
const inactivePinColor = '#818181'; const inactivePinColor = '#818181';
const visiblePinsCount = 20; const visiblePinsCount = 20;
// Helper function to get random coordinates
const getRandomCoordinates = (count: number) => {
const shuffled = [...COORDINATES].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
};
// Helper function to update pins // Helper function to update pins
const updatePins = () => { const updatePins = () => {
setVisiblePins((current) => { setVisiblePins((current) => {
@@ -97,16 +117,13 @@ export function WorldMap() {
}; };
useEffect(() => { useEffect(() => {
// Initial pins
setVisiblePins(getRandomCoordinates(10));
// Update pins every 4 seconds // Update pins every 4 seconds
const interval = setInterval(updatePins, 2000); const interval = setInterval(updatePins, 4000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
const map = useMemo(() => { const map = useMemo(() => {
const map = new DottedMap({ map: JSON.parse(mapJsonString) }); const map = new DottedMap({ map: mapJsonString as any });
visiblePins.forEach((coord) => { visiblePins.forEach((coord) => {
map.addPin({ map.addPin({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 262 KiB