Landing page improvements

This commit is contained in:
Kamil Kisiela 2023-08-24 11:07:33 +02:00
parent ee04854e2e
commit 734647090c
4 changed files with 299 additions and 233 deletions

View file

@ -2,7 +2,7 @@ import { ReactNode } from 'react';
export function HeroTitle(props: { children: ReactNode }) {
return (
<h1 className="mx-auto max-w-screen-lg bg-gradient-to-r from-yellow-500 via-orange-400 to-yellow-500 bg-clip-text text-center text-5xl font-semibold text-transparent dark:from-yellow-400 dark:to-orange-500 sm:text-5xl lg:text-6xl">
<h1 className="mx-auto max-w-screen-lg bg-gradient-to-r from-yellow-500 via-orange-400 to-yellow-500 bg-clip-text text-center text-5xl font-semibold text-transparent sm:text-5xl lg:text-6xl">
{props.children}
</h1>
);
@ -10,7 +10,7 @@ export function HeroTitle(props: { children: ReactNode }) {
export function HeroSubtitle(props: { children: ReactNode }) {
return (
<p className="mx-auto mt-6 max-w-screen-sm text-center text-lg text-gray-700 dark:text-gray-200 font-light">
<p className="mx-auto mt-6 max-w-screen-sm text-center text-lg text-gray-700 font-light">
{props.children}
</p>
);
@ -26,8 +26,10 @@ export function HeroLinks(props: { children: ReactNode }) {
export function Hero(props: { children: ReactNode }) {
return (
<div className="w-full relative">
<div className="my-6 py-20 px-2 sm:py-24 lg:py-32 relative">{props.children}</div>
<div className="w-full relative overflow-hidden bg-white">
<div className="my-6 py-20 px-2 sm:py-24 lg:py-32">
<div className="z-10 relative">{props.children}</div>
</div>
</div>
);
}

View file

@ -1,20 +1,12 @@
import { ReactNode } from 'react';
import Link from 'next/link';
import clsx from 'clsx';
import { BookIcon } from './book-icon';
const classes = {
root: clsx('flex flex-1 flex-row gap-6 md:flex-col lg:flex-row'),
content: clsx('flex flex-col text-black dark:text-white'),
title: clsx('text-xl font-semibold'),
description: clsx('text-gray-600 dark:text-gray-400'),
};
export function HighlightTextLink(props: { href: string; children: ReactNode }) {
return (
<Link
href={props.href}
className="underline underline-offset-2 transition text-yellow-500 hover:text-yellow-500/75 dark:text-yellow-600 dark:hover:text-yellow-500/100"
className="underline underline-offset-2 transition text-yellow-500 hover:text-yellow-500/75 "
>
{props.children}
</Link>
@ -32,11 +24,11 @@ export function Highlights(props: {
return (
<div className="container mx-auto box-border flex flex-col justify-between gap-12 md:flex-row px-6 py-12">
{props.items.map(({ title, description, icon, documentationLink }, i) => (
<div className={classes.root} key={i}>
<div className="h-16 w-16 shrink-0 text-yellow-500">{icon}</div>
<div className={classes.content}>
<h3 className={classes.title}>{title}</h3>
<p className={classes.description}>{description}</p>
<div className="flex flex-1 flex-row gap-6 md:flex-col lg:flex-row" key={i}>
<div className="lg:h-16 lg:w-16 w-12 h-12 shrink-0 text-yellow-500">{icon}</div>
<div className="flex flex-col text-black gap-y-2">
<h3 className="lg:text-xl text-lg font-semibold">{title}</h3>
<p className="text-gray-600">{description}</p>
<Link
href={documentationLink}
className="mt-4 group inline-flex font-semibold items-center transition hover:underline underline-offset-8 gap-x-2 text-yellow-500"

View file

@ -20,13 +20,12 @@ import registryVersionControlSystemImage from '../../public/features/registry/ve
const classes = {
link: cn(
'inline-block rounded-lg bg-gray-100 px-6 py-3 font-medium text-gray-600 shadow-sm hover:bg-gray-200',
'dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700',
),
feature: cn('w-full', 'even:bg-gray-50 even:dark:bg-gray-900', 'odd:bg-white odd:dark:bg-black'),
feature: cn('w-full', 'even:bg-gray-50', 'odd:bg-white'),
root: cn('flex flex-1 flex-row gap-6 md:flex-col lg:flex-row'),
content: cn('flex flex-col text-black dark:text-white'),
content: cn('flex flex-col text-black'),
title: cn('text-xl font-semibold'),
description: cn('text-gray-600 dark:text-gray-400'),
description: cn('text-gray-600'),
};
const gradients: [string, string][] = [
@ -103,16 +102,30 @@ function Highlight(
{props.documentationLink ? null : (
<div
className={cn(
'absolute inset-0 rounded-lg',
'active' in props && props.active ? 'opacity-20' : 'opacity-0 hover:opacity-10',
'absolute inset-0 lg:border-l-4 lg:border-b-0 border-b-2',
'active' in props && props.active ? 'opacity-100' : 'opacity-0 hover:opacity-50',
)}
style={{
backgroundColor: props.startColor,
borderColor: props.startColor,
}}
/>
)}
<div className={classes.content}>
<h3 className={cn(classes.title, 'text-lg')}>{props.title}</h3>
<div
className={cn(
classes.content,
'lg:w-auto w-full',
'onClick' in props ? 'gap-y-2' : 'gap-y-4',
)}
>
<h3
className={cn(
classes.title,
'lg:text-lg text-base',
'onClick' in props ? 'text-center lg:text-left' : '',
)}
>
{props.title}
</h3>
<p
className={cn(
classes.description,
@ -161,92 +174,112 @@ function Feature(props: {
return (
<>
<div className={cn(classes.feature, 'relative overflow-hidden pb-28 pt-20 sm:py-32')}>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative">
<div className="max-w-2xl md:mx-auto md:text-center xl:max-w-none">
<h2
className="text-4xl font-semibold leading-normal tracking-tight bg-clip-text text-transparent"
style={{ backgroundImage: `linear-gradient(-70deg, ${end}, ${start})` }}
>
{title}
</h2>
<div className="mt-6 text-lg tracking-tight text-gray-600 dark:text-gray-400">
{description}
<div className={cn(classes.feature, 'relative overflow-hidden')}>
<div>
<div
className="w-full absolute h-[1px] top-0 opacity-25"
style={{
backgroundImage: `linear-gradient(90deg, ${end}, ${start})`,
}}
/>
<div
className="absolute opacity-[0.15] w-[60vw] h-[255px] -left-[200px] -top-[200px] blur-3xl"
style={{
backgroundImage: `linear-gradient(180deg, ${end}, ${start})`,
}}
/>
<div
className="absolute opacity-[0.15] w-[60vw] h-[255px] -right-[200px] -top-[200px] blur-3xl"
style={{
backgroundImage: `linear-gradient(180deg, ${start}, ${end})`,
}}
/>
</div>
<div className="pb-28 pt-20 sm:py-32">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative">
<div className="max-w-2xl md:mx-auto md:text-center xl:max-w-none">
<h2
className="text-4xl font-semibold leading-normal tracking-tight bg-clip-text text-transparent"
style={{ backgroundImage: `linear-gradient(-70deg, ${end}, ${start})` }}
>
{title}
</h2>
<div className="mt-6 text-lg tracking-tight text-gray-600">{description}</div>
{documentationLink ? (
<div className="pt-12">
<Link
href={documentationLink}
className="group inline-flex font-semibold items-center transition hover:underline underline-offset-8 gap-x-2"
style={{
color: start,
}}
>
<div>
<BookIcon size={16} />
</div>
<div>Learn more</div>
</Link>
</div>
) : null}
</div>
{documentationLink ? (
<div className="pt-12">
<Link
href={documentationLink}
className="group inline-flex font-semibold items-center transition hover:underline underline-offset-8 gap-x-2"
style={{
color: start,
}}
>
<div>
<BookIcon size={16} />
</div>
<div>Learn more</div>
</Link>
{imagelessHighlights ? (
<div className="flex flex-col lg:flex-row justify-center mt-16 pt-10 sm:gap-y-6 md:mt-20 lg:pt-0">
{imagelessHighlights.map((highlight, i) => (
<Highlight {...highlight} endColor={end} startColor={start} key={i} />
))}
</div>
) : null}
</div>
{imagelessHighlights ? (
<div className="flex flex-col lg:flex-row justify-center mt-16 pt-10 sm:gap-y-6 md:mt-20 lg:pt-0">
{imagelessHighlights.map((highlight, i) => (
<Highlight {...highlight} endColor={end} startColor={start} key={i} />
))}
</div>
) : null}
{highlights ? (
<div className="mt-0 lg:mt-16 grid grid-cols-1 items-center gap-y-2 pt-10 sm:gap-y-6 md:mt-20 lg:grid-cols-12 lg:pt-0">
<div className="flex overflow-x-auto pb-4 sm:mx-0 sm:overflow-visible sm:pb-0 lg:col-span-5">
<div>
{highlights ? (
<div className="flex lg:flex-col lg:gap-y-12 gap-x-0 flex-row">
{highlights.map((highlight, i) => (
<Highlight
{...highlight}
endColor={end}
startColor={start}
key={i}
index={i}
active={activeHighlight === i}
onClick={setActiveHighlight}
/>
))}
</div>
) : null}
</div>
</div>
<div className="hidden lg:block" />
<div className="lg:col-span-6">
{highlights.map((highlight, i) => (
<div key={i} className={cn(activeHighlight === i ? 'block' : 'hidden')}>
<div className="relative sm:px-6 lg:hidden">
<p className="relative mx-auto max-w-2xl text-base sm:text-center">
{highlight.description}
</p>
</div>
<div
className="mt-10 w-[45rem] sm:w-auto lg:mt-0 lg:w-[67.8125rem] rounded-lg"
style={{ backgroundImage: `linear-gradient(-70deg, ${end}, ${start})` }}
>
<div className="lg:p-12 p-4">
<div className="rounded-xl overflow-hidden">
<Image
{...highlight.image}
className="w-full"
style={{ color: 'transparent' }}
alt={title}
{highlights ? (
<div className="mt-0 lg:mt-16 grid grid-cols-1 items-center gap-y-2 pt-10 sm:gap-y-6 md:mt-20 lg:grid-cols-12 lg:pt-0">
<div className="w-full lg:w-auto flex overflow-x-auto pb-4 sm:mx-0 sm:overflow-visible sm:pb-0 lg:col-span-5">
<div className="w-full">
{highlights ? (
<div className="flex lg:flex-col lg:gap-y-12 gap-x-0 flex-row justify-evenly">
{highlights.map((highlight, i) => (
<Highlight
{...highlight}
endColor={end}
startColor={start}
key={i}
index={i}
active={activeHighlight === i}
onClick={setActiveHighlight}
/>
))}
</div>
) : null}
</div>
</div>
<div className="hidden lg:block" />
<div className="lg:col-span-6">
{highlights.map((highlight, i) => (
<div key={i} className={cn(activeHighlight === i ? 'block' : 'hidden')}>
<div className="relative sm:px-6 lg:hidden">
<p className="relative mx-auto max-w-2xl text-base sm:text-center">
{highlight.description}
</p>
</div>
<div
className="mt-10 w-[45rem] sm:w-auto lg:mt-0 lg:w-[67.8125rem] rounded-lg"
style={{ backgroundImage: `linear-gradient(-70deg, ${end}, ${start})` }}
>
<div className="lg:p-12 p-4">
<div className="rounded-xl overflow-hidden">
<Image
{...highlight.image}
className="w-full"
style={{ color: 'transparent' }}
alt={title}
/>
</div>
</div>
</div>
</div>
</div>
))}
))}
</div>
</div>
</div>
) : null}
) : null}
</div>
</div>
</div>
</>
@ -270,7 +303,6 @@ export function IndexPage(): ReactElement {
className={cn(
'inline-block rounded-lg px-6 py-3 font-medium text-white shadow-sm',
'bg-yellow-500 hover:bg-yellow-500/75',
'dark:bg-yellow-600 dark:hover:bg-yellow-500/100',
)}
>
Start for free
@ -287,7 +319,10 @@ export function IndexPage(): ReactElement {
</>
</HeroLinks>
</Hero>
<div className="even:bg-gray-50 even:dark:bg-gray-900">
<div className="even:bg-gray-50 relative">
<div>
<div className="w-full absolute h-[1px] top-0 opacity-25 bg-gradient-to-r from-gray-300 via-gray-500 to-gray-300" />
</div>
<StatsList>
<StatsItem label="Happy users" value={2.9} suffix="K" decimal />
<StatsItem label="Registered Schemas" value={120} suffix="K" />
@ -327,48 +362,55 @@ export function IndexPage(): ReactElement {
]}
gradient={0}
/>
<div className={cn(classes.feature, 'py-24')}>
<h2 className="text-3xl font-semibold leading-normal tracking-tight text-center mb-12">
Perfect fit for your GraphQL Gateway
</h2>
<Highlights
items={[
{
title: 'Manage your Gateway',
description: (
<>
Connect to{' '}
<HighlightTextLink href="/docs/get-started/apollo-federation">
Apollo Federation
</HighlightTextLink>
,{' '}
<HighlightTextLink href="/docs/integrations/graphql-mesh">
GraphQL Mesh
</HighlightTextLink>
,{' '}
<HighlightTextLink href="/docs/integrations/schema-stitching">
Stitching
</HighlightTextLink>{' '}
and more.
</>
),
icon: <FiServer strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/get-started/apollo-federation',
},
{
title: 'Global Edge Network',
description: 'Access the registry from any place on earth within milliseconds.',
icon: <FiGlobe strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/features/high-availability-cdn',
},
{
title: 'Apollo GraphOS alternative',
description: 'GraphQL Hive is a drop-in replacement for Apollo GraphOS Studio.',
icon: <FiPackage strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/get-started/apollo-federation',
},
]}
/>
<div className={cn(classes.feature, 'relative overflow-hidden')}>
<div>
<div className="w-full absolute h-[1px] top-0 opacity-25 bg-gradient-to-r from-gray-300 via-gray-500 to-gray-300" />
<div className="absolute opacity-[0.15] w-[60vw] h-[255px] -left-[200px] -top-[200px] blur-3xl bg-gradient-to-b from-gray-50 to-gray-300" />
<div className="absolute opacity-[0.15] w-[60vw] h-[255px] -right-[200px] -top-[200px] blur-3xl bg-gradient-to-b from-gray-300 to-gray-50" />
</div>
<div className="py-24">
<h2 className="text-3xl font-semibold leading-normal tracking-tight text-center mb-12 text-black">
Perfect fit for your GraphQL Gateway
</h2>
<Highlights
items={[
{
title: 'Manage your Gateway',
description: (
<>
Connect to{' '}
<HighlightTextLink href="/docs/get-started/apollo-federation">
Apollo Federation
</HighlightTextLink>
,{' '}
<HighlightTextLink href="/docs/integrations/graphql-mesh">
GraphQL Mesh
</HighlightTextLink>
,{' '}
<HighlightTextLink href="/docs/integrations/schema-stitching">
Stitching
</HighlightTextLink>{' '}
and more.
</>
),
icon: <FiServer strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/get-started/apollo-federation',
},
{
title: 'Global Edge Network',
description: 'Access the registry from any place on earth within milliseconds.',
icon: <FiGlobe strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/features/high-availability-cdn',
},
{
title: 'Apollo GraphOS alternative',
description: 'GraphQL Hive is a drop-in replacement for Apollo GraphOS Studio.',
icon: <FiPackage strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/get-started/apollo-federation',
},
]}
/>
</div>
</div>
<Feature
title="GraphQL Observability"
@ -425,93 +467,125 @@ export function IndexPage(): ReactElement {
]}
/>
<div
className={cn('py-24')}
className="relative overflow-hidden"
style={{
backgroundImage: `linear-gradient(-70deg, ${gradients[4][1]}, ${gradients[4][0]})`,
}}
>
<div className="mx-auto max-w-lg text-center text-white">
<h2 className="text-3xl font-semibold leading-normal tracking-tight">
Get started today
</h2>
<p className="mt-4 text-lg tracking-tight">
Start with a free Hobby plan that fits perfectly most side projects or try our Pro
plan with 30 days trial period.
</p>
<a
href="https://app.graphql-hive.com"
className={cn(
'text-sm rounded-md px-6 py-3 mt-12 font-medium text-black shadow-sm',
'bg-white hover:bg-blue-50',
'inline-flex flex-row items-center gap-2',
)}
>
<FiLogIn /> Enter Hive
</a>
<div>
<div className="w-full absolute h-[1px] top-0 opacity-25 bg-blue-900" />
</div>
<div className="py-24">
<div className="mx-auto max-w-lg text-center text-white">
<h2 className="text-3xl font-semibold leading-normal tracking-tight">
Get started today
</h2>
<p className="mt-4 text-lg tracking-tight">
Start with a free Hobby plan that fits perfectly most side projects or try our Pro
plan with 30 days trial period.
</p>
<a
href="https://app.graphql-hive.com"
className={cn(
'text-sm rounded-md px-6 py-3 mt-12 font-medium text-black shadow-sm',
'bg-white hover:bg-blue-50',
'inline-flex flex-row items-center gap-2',
)}
>
<FiLogIn /> Enter Hive
</a>
</div>
</div>
</div>
<div className={cn(classes.feature, 'py-24')}>
<h2 className="text-3xl font-semibold leading-normal tracking-tight text-center mb-12">
Fits your infrastructure
</h2>
<Highlights
items={[
{
title: 'GitHub Integration',
description: 'Our CLI integrates smoothly with GitHub Actions / repositories.',
icon: <FiGithub strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/integrations/ci-cd#github-check-suites',
},
{
title: 'Works with every CI/CD',
description: 'Connect GraphQL Hive CLI to CI/CD of your choice.',
icon: <FiTruck strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/integrations/ci-cd',
},
{
title: 'On-premise or Cloud',
description:
'GraphQL Hive is MIT licensed, you can host it on your own infrastructure.',
icon: <FiServer strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/self-hosting/get-started',
},
]}
/>
<div className={cn(classes.feature, 'relative overflow-hidden')}>
<div>
<div className="w-full absolute h-[1px] top-0 opacity-25 bg-gradient-to-r from-gray-300 via-gray-500 to-gray-300" />
<div className="absolute opacity-[0.15] w-[60vw] h-[255px] -left-[200px] -top-[200px] blur-3xl bg-gradient-to-b from-gray-600 to-gray-900" />
<div className="absolute opacity-[0.15] w-[60vw] h-[255px] -right-[200px] -top-[200px] blur-3xl bg-gradient-to-b from-gray-900 to-gray-600" />
</div>
<div className="py-24">
<h2 className="text-3xl font-semibold leading-normal tracking-tight text-center mb-12 text-black">
Fits your infrastructure
</h2>
<Highlights
items={[
{
title: 'GitHub Integration',
description: 'Our CLI integrates smoothly with GitHub Actions / repositories.',
icon: <FiGithub strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/integrations/ci-cd#github-check-suites',
},
{
title: 'Works with every CI/CD',
description: 'Connect GraphQL Hive CLI to CI/CD of your choice.',
icon: <FiTruck strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/integrations/ci-cd',
},
{
title: 'On-premise or Cloud',
description:
'GraphQL Hive is MIT licensed, you can host it on your own infrastructure.',
icon: <FiServer strokeWidth={1} className="h-full w-full" />,
documentationLink: '/docs/self-hosting/get-started',
},
]}
/>
</div>
</div>
<div className={cn(classes.feature, 'py-24')}>
<div className="container mx-auto box-border flex flex-col gap-y-24 px-6">
<div className="text-center">
<h2
className="mb-6 bg-clip-text text-5xl font-semibold leading-normal text-transparent dark:text-transparent"
style={{
backgroundImage: `linear-gradient(-70deg, ${gradients[3][1]}, ${gradients[3][0]})`,
}}
>
Open-Source
</h2>
<p className="text-lg leading-7 text-gray-600 dark:text-gray-400">
Built entirely in public.
</p>
</div>
<div className="mx-auto box-border grid max-w-screen-lg grid-cols-2 gap-12 px-6">
{[
{
title: 'Public roadmap',
description: 'Influence the future of GraphQL Hive.',
},
{
title: 'Cloud and Self-Hosted',
description: 'MIT licensed, host it on your own infrastructure.',
},
{
title: 'Available for free',
description: 'Free Hobby plan that fits perfectly for most side projects.',
},
{
title: 'Community',
description: 'Implement your own features with our help.',
},
].map(renderFeatures)}
<div className={cn(classes.feature, 'relative overflow-hidden')}>
<div>
<div
className="w-full absolute h-[1px] top-0 opacity-25"
style={{
backgroundImage: `linear-gradient(90deg, ${gradients[3][1]}, ${gradients[3][0]})`,
}}
/>
<div
className="absolute opacity-[0.15] w-[60vw] h-[255px] -left-[200px] -top-[200px] blur-3xl"
style={{
backgroundImage: `linear-gradient(180deg, ${gradients[3][0]}, ${gradients[3][1]})`,
}}
/>
<div
className="absolute opacity-[0.15] w-[60vw] h-[255px] -right-[200px] -top-[200px] blur-3xl"
style={{
backgroundImage: `linear-gradient(180deg, ${gradients[3][1]}, ${gradients[3][0]})`,
}}
/>
</div>
<div className="py-24">
<div className="container mx-auto box-border flex flex-col gap-y-24 px-6">
<div className="text-center">
<h2
className="mb-6 bg-clip-text text-5xl font-semibold leading-normal text-transparent"
style={{
backgroundImage: `linear-gradient(-70deg, ${gradients[3][1]}, ${gradients[3][0]})`,
}}
>
Open-Source
</h2>
<p className="text-lg leading-7 text-gray-600">Built entirely in public.</p>
</div>
<div className="mx-auto box-border grid max-w-screen-lg grid-cols-2 gap-12 px-6">
{[
{
title: 'Public roadmap',
description: 'Influence the future of GraphQL Hive.',
},
{
title: 'Cloud and Self-Hosted',
description: 'MIT licensed, host it on your own infrastructure.',
},
{
title: 'Available for free',
description: 'Free Hobby plan that fits perfectly for most side projects.',
},
{
title: 'Community',
description: 'Implement your own features with our help.',
},
].map(renderFeatures)}
</div>
</div>
</div>
</div>

View file

@ -9,7 +9,7 @@ export function StatsItem(props: {
}) {
return (
<div>
<div className="font-bold text-5xl text-center">
<div className="font-bold text-5xl text-center text-black">
<CountUp
start={0}
end={props.value}
@ -22,9 +22,7 @@ export function StatsItem(props: {
/>
{props.suffix}+
</div>
<div className="text-gray-600 dark:text-gray-400 uppercase font-semibold text-center">
{props.label}
</div>
<div className="text-gray-600 uppercase font-semibold text-center">{props.label}</div>
</div>
);
}