feat: initial commit
This commit is contained in:
commit
604c280f8d
85 changed files with 4436 additions and 0 deletions
222
src/components/TechStack.tsx
Normal file
222
src/components/TechStack.tsx
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
import { ReactNode } from 'react'
|
||||
import Image, { StaticImageData } from 'next/image'
|
||||
import { Tab } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Container } from './Container'
|
||||
|
||||
import screenshotContacts from '@/images/screenshots/contacts.png'
|
||||
|
||||
import screenshotArgoCD from '@/images/screenshots/argocd.png'
|
||||
import screenshotGitLab from '@/images/screenshots/gitlab.png'
|
||||
import screenshotGit from '@/images/screenshots/git.png'
|
||||
import screenshotTerraform from '@/images/screenshots/terraform.png'
|
||||
import screenshotKubernetes from '@/images/screenshots/kubernetes.png'
|
||||
|
||||
import logoTerraform from '@/images/logos/tools/terraform.svg'
|
||||
import logoGit from '@/images/logos/tools/git.svg'
|
||||
import logoKubernetes from '@/images/logos/tools/kubernetes.svg'
|
||||
import logoPrometheus from '@/images/logos/tools/prometheus.svg'
|
||||
import logoArgo from '@/images/logos/tools/argo.svg'
|
||||
import logoGitLab from '@/images/logos/tools/gitlab.svg'
|
||||
import logoGrafana from '@/images/logos/tools/grafana.svg'
|
||||
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: 'Version Control',
|
||||
summary:
|
||||
'Never lose track of what’s in stock with accurate inventory tracking.',
|
||||
description:
|
||||
'We don’t offer this as part of our software but that statement is inarguably true. Accurate inventory tracking would help you for sure.',
|
||||
image: screenshotGitLab,
|
||||
icon: logoGit,
|
||||
},
|
||||
{
|
||||
name: 'Infrastructure as code',
|
||||
summary: 'Stay on top of things with always up-to-date reporting features.',
|
||||
description:
|
||||
'We talked about reporting in the section above but we needed three items here, so mentioning it one more time for posterity.',
|
||||
image: screenshotTerraform,
|
||||
icon: logoTerraform,
|
||||
},
|
||||
{
|
||||
name: 'Orchestration',
|
||||
summary:
|
||||
'Organize all of your contacts, service providers, and invoices in one place.',
|
||||
description:
|
||||
'This also isn’t actually a feature, it’s just some friendly advice. We definitely recommend that you do this, you’ll feel really organized and professional.',
|
||||
image: screenshotKubernetes,
|
||||
icon: logoKubernetes,
|
||||
},
|
||||
{
|
||||
name: 'Monitoring',
|
||||
summary:
|
||||
'Organize all of your contacts, service providers, and invoices in one place.',
|
||||
description:
|
||||
'This also isn’t actually a feature, it’s just some friendly advice. We definitely recommend that you do this, you’ll feel really organized and professional.',
|
||||
image: screenshotContacts,
|
||||
icon: logoPrometheus,
|
||||
},
|
||||
{
|
||||
name: 'Visualization',
|
||||
summary:
|
||||
'Organize all of your contacts, service providers, and invoices in one place.',
|
||||
description:
|
||||
'This also isn’t actually a feature, it’s just some friendly advice. We definitely recommend that you do this, you’ll feel really organized and professional.',
|
||||
image: screenshotContacts,
|
||||
icon: logoGrafana,
|
||||
},
|
||||
{
|
||||
name: 'GitOps',
|
||||
summary:
|
||||
'Organize all of your contacts, service providers, and invoices in one place.',
|
||||
description:
|
||||
'This also isn’t actually a feature, it’s just some friendly advice. We definitely recommend that you do this, you’ll feel really organized and professional.',
|
||||
image: screenshotArgoCD,
|
||||
icon: logoArgo,
|
||||
},
|
||||
]
|
||||
|
||||
type FeatureType = {
|
||||
name: string|ReactNode,
|
||||
summary: string,
|
||||
description: string,
|
||||
image: StaticImageData,
|
||||
icon: any,
|
||||
}
|
||||
|
||||
type FeatureProps = {
|
||||
feature: FeatureType,
|
||||
isActive: boolean,
|
||||
className?: string,
|
||||
props?: any,
|
||||
}
|
||||
|
||||
function Feature({ feature, isActive, className, ...props }: FeatureProps ) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(className, !isActive && 'opacity-75 hover:opacity-100')}
|
||||
{...props}
|
||||
>
|
||||
<Image src={feature.icon} className={clsx(
|
||||
'w-32 rounded-lg p-2 flex justify-center',
|
||||
// isActive ? 'fill-blue-600' : 'fill-slate-500'
|
||||
)} alt="" />
|
||||
<h3
|
||||
className={clsx(
|
||||
'mt-6 text-lg font-medium',
|
||||
isActive ? 'text-blue-600' : 'text-slate-600'
|
||||
)}
|
||||
>
|
||||
{feature.name}
|
||||
</h3>
|
||||
<p className="mt-2 font-display text-xl text-slate-900">
|
||||
{feature.summary}
|
||||
</p>
|
||||
<p className="mt-4 text-sm text-slate-600">{feature.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FeaturesMobile() {
|
||||
return (
|
||||
<div className="-mx-4 mt-20 flex flex-col gap-y-10 overflow-hidden px-4 sm:-mx-6 sm:px-6 lg:hidden">
|
||||
{features.map((feature) => (
|
||||
<div key={feature.name}>
|
||||
<Feature feature={feature} className="mx-auto max-w-2xl" isActive />
|
||||
<div className="relative mt-10 pb-10">
|
||||
<div className="absolute -inset-x-4 bottom-0 top-8 bg-slate-200 sm:-inset-x-6" />
|
||||
<div className="relative mx-auto w-[52.75rem] overflow-hidden rounded-xl bg-white shadow-lg shadow-slate-900/5 ring-1 ring-slate-500/10">
|
||||
<Image
|
||||
className="w-full"
|
||||
src={feature.image}
|
||||
alt=""
|
||||
sizes="52.75rem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FeaturesDesktop() {
|
||||
return (
|
||||
<Tab.Group as="div" className="hidden lg:mt-20 lg:block">
|
||||
{({ selectedIndex }) => (
|
||||
<>
|
||||
<Tab.List className="grid grid-cols-3 gap-x-8 gap-y-4">
|
||||
{features.map((feature, featureIndex) => (
|
||||
<Feature
|
||||
key={feature.name}
|
||||
feature={{
|
||||
...feature,
|
||||
name: (
|
||||
<Tab className="[&:not(:focus-visible)]:focus:outline-none">
|
||||
<span className="absolute inset-0" />
|
||||
{feature.name}
|
||||
</Tab>
|
||||
),
|
||||
}}
|
||||
isActive={featureIndex === selectedIndex}
|
||||
className="relative"
|
||||
/>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels className="relative mt-20 overflow-hidden rounded-4xl bg-slate-200 px-14 py-16 xl:px-16">
|
||||
<div className="-mx-5 flex">
|
||||
{features.map((feature, featureIndex) => (
|
||||
<Tab.Panel
|
||||
static
|
||||
key={feature.name}
|
||||
className={clsx(
|
||||
'px-5 transition duration-500 ease-in-out [&:not(:focus-visible)]:focus:outline-none',
|
||||
featureIndex !== selectedIndex && 'opacity-60'
|
||||
)}
|
||||
style={{ transform: `translateX(-${selectedIndex * 100}%)` }}
|
||||
aria-hidden={featureIndex !== selectedIndex}
|
||||
>
|
||||
<div className="w-[52.75rem] overflow-hidden rounded-xl bg-white shadow-lg shadow-slate-900/5 ring-1 ring-slate-500/10">
|
||||
<Image
|
||||
className="w-full"
|
||||
src={feature.image}
|
||||
alt=""
|
||||
sizes="52.75rem"
|
||||
/>
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
))}
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-4xl ring-1 ring-inset ring-slate-900/10" />
|
||||
</Tab.Panels>
|
||||
</>
|
||||
)}
|
||||
</Tab.Group>
|
||||
)
|
||||
}
|
||||
|
||||
export function TechStack() {
|
||||
return (
|
||||
<section
|
||||
id="tech-stack"
|
||||
aria-label="Features for simplifying everyday business tasks"
|
||||
className="pt-20 pb-14 sm:pb-20 sm:pt-32 lg:pb-32"
|
||||
>
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl md:text-center">
|
||||
<h2 className="font-display text-3xl tracking-tight text-slate-900 sm:text-4xl">
|
||||
The open source DevOps stack
|
||||
</h2>
|
||||
<p className="mt-4 text-lg tracking-tight text-slate-700">
|
||||
Because you’d probably be a little confused if we suggested you
|
||||
complicate your everyday business tasks instead.
|
||||
</p>
|
||||
</div>
|
||||
<FeaturesMobile />
|
||||
<FeaturesDesktop />
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in a new issue