1
0
Fork 0

refactor: move content of src/ to root dir for readability

This commit is contained in:
Vojtěch Mareš 2022-12-06 13:02:09 +01:00
parent 87958d1d28
commit 532d1eba99
Signed by: vojtech.mares
GPG key ID: C6827B976F17240D
69 changed files with 5 additions and 4 deletions

64
components/Button.tsx Normal file
View file

@ -0,0 +1,64 @@
import Link from 'next/link'
import clsx from 'clsx'
import { ReactNode } from 'react'
const baseStyles = {
solid:
'group inline-flex items-center justify-center rounded-full py-2 px-4 text-sm font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2',
outline:
'group inline-flex ring-1 items-center justify-center rounded-full py-2 px-4 text-sm focus:outline-none',
}
const variantStyles = {
solid: {
slate:
'bg-slate-900 text-white hover:bg-slate-700 hover:text-slate-100 active:bg-slate-800 active:text-slate-300 focus-visible:outline-slate-900',
blue: 'bg-blue-600 text-white hover:text-slate-100 hover:bg-blue-500 active:bg-blue-800 active:text-blue-100 focus-visible:outline-blue-600',
white:
'bg-white text-slate-900 hover:bg-blue-50 active:bg-blue-200 active:text-slate-600 focus-visible:outline-white',
},
outline: {
slate:
'ring-slate-200 text-slate-700 hover:text-slate-900 hover:ring-slate-300 active:bg-slate-100 active:text-slate-600 focus-visible:outline-blue-600 focus-visible:ring-slate-300',
white:
'ring-slate-700 text-white hover:ring-slate-500 active:ring-slate-700 active:text-slate-400 focus-visible:outline-white',
blue: // Supress TypeScript error
'', // TODO: fix properly
},
}
type Props = {
variant?: 'solid'|'outline',
color?: 'slate'|'white'|'blue',
className?: string,
href?: string,
children?: ReactNode,
}
export function Button({
variant = 'solid',
color = 'slate',
className,
href,
children
}: Props) {
className = clsx(
baseStyles[variant],
variantStyles[variant][color],
className
)
if (href !== undefined) {
return (
<Link href={href} className={className}>
{children}
</Link>
)
} else {
return (
<button className={className}>
{children}
</button>
)
}
}

View file

@ -0,0 +1,37 @@
import Image from 'next/image'
import { Button } from '@/components/Button'
import { Container } from '@/components/Container'
import backgroundImage from '@/images/background-call-to-action.jpg'
export function CallToAction() {
return (
<section
id="get-started-today"
className="relative overflow-hidden bg-blue-600 py-32"
>
<Image
className="absolute top-1/2 left-1/2 max-w-none -translate-x-1/2 -translate-y-1/2"
src={backgroundImage}
alt=""
width={2347}
height={1244}
unoptimized
/>
<Container className="relative">
<div className="mx-auto max-w-xl text-center">
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl">
Posuňte svoji aplikaci dnes
</h2>
<p className="mt-4 text-lg tracking-tight text-white">
Je na čase pozvednout vaši infrastrukturu na dnešní standardy.
Vaše infrastruktura by měla nabídnout Vaši aplikaci světu, ne ji držet zpátky.
</p>
<Button href="https://calendly.com/vojtechmares/30min" color="white" className="mt-10">
Domluvme si schůzku
</Button>
</div>
</Container>
</section>
)
}

17
components/Container.tsx Normal file
View file

@ -0,0 +1,17 @@
import clsx from 'clsx'
import { ReactNode } from 'react'
type Props = {
className?: string,
children?: ReactNode
}
export function Container({ className, children }: Props ) {
return (
<div
className={clsx('mx-auto max-w-7xl px-4 sm:px-6 lg:px-8', className)}
>
{children}
</div>
)
}

169
components/Environment.tsx Normal file
View file

@ -0,0 +1,169 @@
import Image from 'next/image'
import { Container } from './Container'
import logoKubernetes from '@/images/logos/tools/kubernetes.svg'
import logok3s from '@/images/logos/tools/k3s.svg'
import logoRancher from '@/images/logos/tools/rancher.svg'
import logoAWS from '@/images/logos/tools/amazonaws.svg'
import logoDigitalOcean from '@/images/logos/tools/digitalocean.svg'
import logovmware from '@/images/logos/tools/vmware.svg'
const features = [
{
name: 'Kubernetes',
summary: 'Open Source systém pro automatizaci deploymentů, škálování a správu kontejnerizovaných aplikací.',
description:
'Kubernetes je na platformě nezávislý systém, díky kterému může běžet vaše aplikace u vás v datacentru, v public cloudu nebo třeba v okrajových lokalitách, a nebo třeba na všech najednou, bez problému.',
icon: logoKubernetes,
iconColor: '#326CE5',
},
{
name: 'k3s & rke2',
summary:
'k3s: lehká distrubuce Kubernetes; rke2: Kubernetes do vašeho datacentra.',
description:
'k3s i rke2 jsou velice snadno instalovatelné distribuce Kubernetes, které můžete nainstalovat úplně všude a přitom mít stále k dispozici celý Kubernetes ekosystém, bez kompromisů.',
icon: logok3s,
iconColor: '#FFC61C',
},
{
name: 'Rancher',
summary:
'Kubernetes jako služba, ve vašem datacentru.',
description:
'Platforma, pro vaše Kubernetes clustery. Komplexní řešení celého životního cyklu clusteru. Neřešte každodení problémy, nechte je řešit Rancher řešit za vás.',
icon: logoRancher,
iconColor: '#0075A8',
},
{
name: 'AWS',
summary:
'Největší veřejný cloud. Máte problém? AWS má na to službu.',
description:
'Amazon Web Services (AWS) je cloudový poskytovatel služeb, včetně ukládání obrovských objemů dat, výpočetního výkonu a sítí. To vše aby pomohli firmám i jednotlivcům růst. AWS je nejrozšířenější poskytovatel, který je cenově dostupný, flexibilní cesta, jak stavět a provozovat aplikace a služby.',
icon: logoAWS,
iconColor: '#232F3E',
},
// {
// name: 'Google Cloud Platform',
// summary:
// 'Organize all of your contacts, service providers, and invoices in one place.',
// description:
// 'This also isnt actually a feature, its just some friendly advice. We definitely recommend that you do this, youll feel really organized and professional.',
// icon: logoGCP,
// iconColor: '#4285F4',
// },
{
name: 'DigitalOcean',
summary:
'Jednoduchý cloudový poskytovatel, ve kterém se neztratíte.',
description:
'DigitalOcean patří k menším cloudovým poskytovatelům, avšak jejich portfolio vám pro vaši aplikaci bohatě stačí a nebudete se ztrácet v komplexitě velkých poskytovatelů se spoustou služeb.',
icon: logoDigitalOcean,
iconColor: '#0080FF',
},
// {
// name: 'OpenStack',
// summary:
// 'Organize all of your contacts, service providers, and invoices in one place.',
// description:
// 'This also isnt actually a feature, its just some friendly advice. We definitely recommend that you do this, youll feel really organized and professional.',
// icon: logoOpenStack,
// iconColor: '#ED1944',
// },
{
name: 'VMware',
summary:
'Populární řešení pro správu vaše datacentra, od virtuálních serverů až po úložiště.',
description:
'VMware je virtualizační platforma, která umožňuje vytvářet a spravovat virtuální servery a jejich fyzickém hardwaru. Zároveň umožňuje vytvářet oddělená prostředí například pro vývoj a ostrý provoz, tak i pro zcela různé aplikace.',
icon: logovmware,
iconColor: '#607078',
},
]
type FeatureType = {
name: string,
summary: string,
description: string,
icon: any,
iconColor: string,
}
type FeatureProps = {
feature: FeatureType
className?: string,
props?: any[],
}
function Feature({ feature, className, ...props }: FeatureProps) {
return (
<div
className={className}
{...props}
>
<Image src={feature.icon} className="w-32 rounded-lg p-2" color={feature.iconColor} alt="" />
<h3
className="mt-6 text-lg font-medium text-blue-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" />
</div>
))}
</div>
)
}
function FeaturesDesktop() {
return (
<div className="hidden lg:mt-20 lg:block">
<div className="grid grid-cols-3 gap-x-8 gap-y-4">
{features.map((feature) => (
<div key={feature.name}>
<Feature feature={feature} className="relative" />
</div>
))}
</div>
</div>
)
}
export function Environment() {
return (
<section
id="secondary-features"
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">
Jakákoliv platforma, kdekoliv
</h2>
<p className="mt-4 text-lg tracking-tight text-slate-700">
Od veřejného cloudu přes on-premise po serverless, se vším vám poradím.
</p>
</div>
<FeaturesMobile />
<FeaturesDesktop />
</Container>
</section>
)
}

110
components/Faqs.tsx Normal file
View file

@ -0,0 +1,110 @@
import Image from 'next/image'
import { Container } from '@/components/Container'
import backgroundImage from '@/images/background-faqs.jpg'
const faqs = [
[
{
question: 'Does TaxPal handle VAT?',
answer:
'Well no, but if you move your company offshore you can probably ignore it.',
},
{
question: 'Can I pay for my subscription via purchase order?',
answer: 'Absolutely, we are happy to take your money in all forms.',
},
{
question: 'How do I apply for a job at TaxPal?',
answer:
'We only hire our customers, so subscribe for a minimum of 6 months and then lets talk.',
},
],
[
{
question: 'What was that testimonial about tax fraud all about?',
answer:
'TaxPal is just a software application, ultimately your books are your responsibility.',
},
{
question:
'TaxPal sounds horrible but why do I still feel compelled to purchase?',
answer:
'This is the power of excellent visual design. You just cant resist it, no matter how poorly it actually functions.',
},
{
question:
'I found other companies called TaxPal, are you sure you can use this name?',
answer:
'Honestly not sure at all. We havent actually incorporated or anything, we just thought it sounded cool and made this website.',
},
],
[
{
question: 'How do you generate reports?',
answer:
'You just tell us what data you need a report for, and we get our kids to create beautiful charts for you using only the finest crayons.',
},
{
question: 'Can we expect more inventory features?',
answer: 'In life its really better to never expect anything at all.',
},
{
question: 'I lost my password, how do I get into my account?',
answer:
'Send us an email and we will send you a copy of our latest password spreadsheet so you can find your information.',
},
],
]
export function Faqs() {
return (
<section
id="faq"
aria-labelledby="faq-title"
className="relative overflow-hidden bg-slate-50 py-20 sm:py-32"
>
<Image
className="absolute top-0 left-1/2 max-w-none translate-x-[-30%] -translate-y-1/4"
src={backgroundImage}
alt=""
width={1558}
height={946}
unoptimized
/>
<Container className="relative">
<div className="mx-auto max-w-2xl lg:mx-0">
<h2
id="faq-title"
className="font-display text-3xl tracking-tight text-slate-900 sm:text-4xl"
>
Frequently asked questions
</h2>
<p className="mt-4 text-lg tracking-tight text-slate-700">
If you cant find what youre looking for, email our support team
and if youre lucky someone will get back to you.
</p>
</div>
<ul
role="list"
className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 lg:max-w-none lg:grid-cols-3"
>
{faqs.map((column, columnIndex) => (
<li key={columnIndex}>
<ul role="list" className="flex flex-col gap-y-8">
{column.map((faq, faqIndex) => (
<li key={faqIndex}>
<h3 className="font-display text-lg leading-7 text-slate-900">
{faq.question}
</h3>
<p className="mt-4 text-sm text-slate-700">{faq.answer}</p>
</li>
))}
</ul>
</li>
))}
</ul>
</Container>
</section>
)
}

149
components/Footer.tsx Normal file
View file

@ -0,0 +1,149 @@
import Link from 'next/link'
import { Container } from '@/components/Container'
// import { Logo } from '@/components/Logo'
// import { NavLink } from '@/components/NavLink'
import { Button } from '@/components/Button'
export function Footer() {
return (
<footer className="bg-slate-50">
<Container className="py-8">
<div className="py-4">
<div className="grid grid-cols-1 gap-y-6 lg:grid-cols-4 lg:gap-4">
<div>
<h4 className="text-lg font-medium" >Vojtěch Mareš</h4>
<ul className="list-none mt-4">
<li>
<Link href="tel:+420732490651" className="underline">+420 732 490 651</Link>
</li>
<li>
<Link href="mailto:iam@vojtechmares.com" className="underline">iam@vojtechmares.com</Link>
</li>
<li className="mt-4">
Company ID<br />
<code id="company-id">06999280</code>
</li>
<li className="mt-2">
VAT ID<br />
<code id="vat-id">CZ9709180063</code>
</li>
</ul>
</div>
<div>
{/* <h3 className="text-lg font-medium">Most favorite courses</h3>
<ul className="mt-4 list-disc pl-4">
<li>
<Link href="#" className="underline">Kubernetes</Link>
</li>
<li>
<Link href="#" className="underline">GitLab CI</Link>
</li>
<li>
<Link href="#" className="underline">Docker</Link>
</li>
<li>
<Link href="#" className="underline">Terraform</Link>
</li>
<li>
<Link href="#" className="underline">Rancher</Link>
</li>
</ul> */}
</div>
<div>
<h3 className="text-lg font-medium">Důležité odkazy</h3>
<ul className="mt-4 list-disc pl-4">
{/* <li>
<Link
href="https://devops-skoelni.cz/?utm_source=vojtechmares&utm_medium=vojtechmares-com-website&utm_content=link"
className="underline"
target="_blank"
>
DevOps-Skoleni.cz
</Link>
</li> */}
<li>
<Link
href="https://skoleni.io/?utm_source=vojtechmares&utm_medium=vojtechmares-com-website&utm_content=link"
className="underline"
target="_blank"
>
Skoleni.io
</Link>
</li>
<li>
<Link
href="https://devopsaci.cz/?utm_source=vojtechmares&utm_medium=vojtechmares-com-website&utm_content=link"
className="underline"
target="_blank"
>
DevOpsaci.cz
</Link>
</li>
</ul>
</div>
<div>
<h3 className="text-lg font-medium">Zaujal jsem vás?</h3>
<p className="mt-4">
Zaujal jsem vás avšak nevíte, jak přesně bych vám mohl pomoci?
Nebojte se zeptat a společně vymyslíme, jak vám mohu pomoci.
</p>
<Button href="https://calendly.com/vojtechmares/30min" className="mt-5">
Domluvme si schůzku
</Button>
</div>
</div>
</div>
<div className="flex flex-col items-center border-t border-slate-400/10 py-10 sm:flex-row-reverse sm:justify-between">
<div className="flex gap-x-6">
<Link
href="https://www.linkedin.com/in/vojtech-mares/"
target="_blank"
className="group"
aria-label="Vojta Mareš na LinkedIn"
>
<svg
// role="img"
// viewBox="0 0 24 24"
// xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700"
>
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
</Link>
<Link
href="https://twitter.com/vojtechmares_"
target="_blank"
className="group"
aria-label="Vojta Mareš na Twitter"
>
<svg
aria-hidden="true"
className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700"
>
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0 0 22 5.92a8.19 8.19 0 0 1-2.357.646 4.118 4.118 0 0 0 1.804-2.27 8.224 8.224 0 0 1-2.605.996 4.107 4.107 0 0 0-6.993 3.743 11.65 11.65 0 0 1-8.457-4.287 4.106 4.106 0 0 0 1.27 5.477A4.073 4.073 0 0 1 2.8 9.713v.052a4.105 4.105 0 0 0 3.292 4.022 4.093 4.093 0 0 1-1.853.07 4.108 4.108 0 0 0 3.834 2.85A8.233 8.233 0 0 1 2 18.407a11.615 11.615 0 0 0 6.29 1.84" />
</svg>
</Link>
<Link
href="https://github.com/vojtechmares"
target="_blank"
className="group"
aria-label="Vojta Mareš na GitHub"
>
<svg
aria-hidden="true"
className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700"
>
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
</svg>
</Link>
</div>
<p className="mt-6 text-sm text-slate-500 sm:mt-0">
Copyright &copy; {new Date().getFullYear()} Vojtěch Mareš. Všechna práva vyhrazena.
</p>
</div>
</Container>
</footer>
)
}

36
components/Header.tsx Normal file
View file

@ -0,0 +1,36 @@
import Link from "next/link"
import { NavLink } from "@/components/NavLink";
import { Container } from "@/components/Container"
import { Button } from "@/components/Button"
export function Header() {
return (
<>
<header className="py-10">
<Container>
<nav className="relative z-50 flex justify-between">
<div className="flex items-center md:gap-x-12">
<span className="text-2xl font-bold">
<Link href="/">Vojta Mareš</Link>
</span>
<div className="hidden md:flex md:gap-x-6">
{/* <NavLink href="#how-can-i-help">Jak vám můžu pomoci?</NavLink> */}
<NavLink href="/skoleni">Školení</NavLink>
{/* <NavLink href="/pripadove-studie">Případové studie</NavLink> */}
<NavLink href="https://vojtechmares.blog/">Blog</NavLink>
</div>
</div>
<div className="flex items-center gap-x-5 md:gap-x-8">
<Button href="mailto:iam@vojtechmares.com" color="blue">
<span className="text-lg">
Napište mi <span className="hidden lg:inline">ještě dnes</span>
</span>
</Button>
</div>
</nav>
</Container>
</header>
</>
);
}

78
components/Hero.tsx Normal file
View file

@ -0,0 +1,78 @@
import { Button } from '@/components/Button'
import { Container } from '@/components/Container'
export function Hero() {
return (
<Container className="pt-20 pb-16 text-center lg:pt-32">
<h1 className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-slate-900 sm:text-7xl">
DevOps{' '}
<span className="relative whitespace-nowrap text-blue-600">
<svg
aria-hidden="true"
viewBox="0 0 418 42"
className="absolute top-2/3 left-0 h-[0.58em] w-full fill-blue-300/70"
preserveAspectRatio="none"
>
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
</svg>
<span className="relative">jednoduše</span>
</span>{' '}
pro všechny.
</h1>
<p className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-slate-700">
Společně snížíme vaše náklady na infrasturkuturu,
zbavíme se technického dluhu a připravíme vaší IT infrastrukturu na rapidní růst.
</p>
<div className="mt-10 flex justify-center gap-x-6">
<Button href="https://calendly.com/vojtechmares/30min">Domluvme si schůzku</Button>
{/* <Button
href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
variant="outline"
>
<svg
aria-hidden="true"
className="h-3 w-3 flex-none fill-blue-600 group-active:fill-current"
>
<path d="m9.997 6.91-7.583 3.447A1 1 0 0 1 1 9.447V2.553a1 1 0 0 1 1.414-.91L9.997 5.09c.782.355.782 1.465 0 1.82Z" />
</svg>
<span className="ml-3">Watch video</span>
</Button> */}
</div>
{/* <div className="mt-36 lg:mt-44">
<p className="font-display text-base text-slate-900">
Trusted by these six companies so far
</p>
<ul
role="list"
className="mt-8 flex items-center justify-center gap-x-8 sm:flex-col sm:gap-x-0 sm:gap-y-10 xl:flex-row xl:gap-x-12 xl:gap-y-0"
>
{[
[
{ name: 'Transistor', logo: logoTransistor },
{ name: 'Tuple', logo: logoTuple },
{ name: 'StaticKit', logo: logoStaticKit },
],
[
{ name: 'Mirage', logo: logoMirage },
{ name: 'Laravel', logo: logoLaravel },
{ name: 'Statamic', logo: logoStatamic },
],
].map((group, groupIndex) => (
<li key={groupIndex}>
<ul
role="list"
className="flex flex-col items-center gap-y-8 sm:flex-row sm:gap-x-12 sm:gap-y-0"
>
{group.map((company) => (
<li key={company.name} className="flex">
<Image src={company.logo} alt={company.name} unoptimized />
</li>
))}
</ul>
</li>
))}
</ul>
</div> */}
</Container>
)
}

18
components/NavLink.tsx Normal file
View file

@ -0,0 +1,18 @@
import { ReactNode } from 'react';
import Link from 'next/link';
type Props = {
href: string,
children: ReactNode,
}
export function NavLink({href, children}: Props) {
return (
<Link
href={href}
className="inline-block rounded-lg py-1 px-2 text-lg font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-900"
>
{children}
</Link>
);
}

113
components/Pricing.tsx Normal file
View file

@ -0,0 +1,113 @@
import clsx from 'clsx'
import { Button } from '@/components/Button'
import { Container } from '@/components/Container'
type SwirlyDoodleProps = { className: string }
function SwirlyDoodle({ className }: SwirlyDoodleProps) {
return (
<svg
aria-hidden="true"
viewBox="0 0 281 40"
className={className}
preserveAspectRatio="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M240.172 22.994c-8.007 1.246-15.477 2.23-31.26 4.114-18.506 2.21-26.323 2.977-34.487 3.386-2.971.149-3.727.324-6.566 1.523-15.124 6.388-43.775 9.404-69.425 7.31-26.207-2.14-50.986-7.103-78-15.624C10.912 20.7.988 16.143.734 14.657c-.066-.381.043-.344 1.324.456 10.423 6.506 49.649 16.322 77.8 19.468 23.708 2.65 38.249 2.95 55.821 1.156 9.407-.962 24.451-3.773 25.101-4.692.074-.104.053-.155-.058-.135-1.062.195-13.863-.271-18.848-.687-16.681-1.389-28.722-4.345-38.142-9.364-15.294-8.15-7.298-19.232 14.802-20.514 16.095-.934 32.793 1.517 47.423 6.96 13.524 5.033 17.942 12.326 11.463 18.922l-.859.874.697-.006c2.681-.026 15.304-1.302 29.208-2.953 25.845-3.07 35.659-4.519 54.027-7.978 9.863-1.858 11.021-2.048 13.055-2.145a61.901 61.901 0 0 0 4.506-.417c1.891-.259 2.151-.267 1.543-.047-.402.145-2.33.913-4.285 1.707-4.635 1.882-5.202 2.07-8.736 2.903-3.414.805-19.773 3.797-26.404 4.829Zm40.321-9.93c.1-.066.231-.085.29-.041.059.043-.024.096-.183.119-.177.024-.219-.007-.107-.079ZM172.299 26.22c9.364-6.058 5.161-12.039-12.304-17.51-11.656-3.653-23.145-5.47-35.243-5.576-22.552-.198-33.577 7.462-21.321 14.814 12.012 7.205 32.994 10.557 61.531 9.831 4.563-.116 5.372-.288 7.337-1.559Z"
/>
</svg>
)
}
type PlanProps = {
name: string,
price: string,
description: string,
href: string,
featured?: boolean,
buttonText?: string
}
function Plan({ name, price, description, href, featured = false, buttonText = 'Napište mi' }: PlanProps) {
return (
<section
className={clsx(
'flex flex-col rounded-3xl px-6 sm:px-8',
featured ? 'order-first bg-blue-600 py-8 lg:order-none' : 'lg:py-8'
)}
>
<h3 className="mt-5 font-display text-4xl text-white">{name}</h3>
<p className="mt-4 font-display text-lg font-light tracking-tight text-white">
{price}
</p>
<p
className={clsx(
'mt-2 text-base',
featured ? 'text-white' : 'text-slate-400'
)}
>
{description}
</p>
<Button
href={href}
variant={featured ? 'solid' : 'outline'}
color="white"
className="mt-16"
aria-label={`Get started with the ${name} plan for ${price}`}
>
{buttonText}
</Button>
</section>
)
}
export function Pricing() {
return (
<section
id="pricing"
aria-label="Pricing"
className="bg-slate-900 py-20 sm:py-32"
>
<Container>
<div className="md:text-center">
<h3 className="font-display text-xl tracking-tight text-white sm:text-4xl">
<span className="relative whitespace-nowrap">
<SwirlyDoodle className="absolute top-1/2 left-0 h-[1em] w-full fill-blue-400" />
<span className="relative">Co pro Vás,</span>
</span>{' '}
můžu udělat.
</h3>
<p className="mt-4 text-lg text-slate-400">
It doesnt matter what size your company is, we will find a way to help you.
</p>
</div>
<div className="-mx-4 mt-16 grid max-w-2xl grid-cols-1 gap-y-10 sm:mx-auto lg:-mx-8 lg:max-w-none lg:grid-cols-3 xl:mx-0 xl:gap-x-8">
<Plan
name="Konzultace"
price="2 000 CZK za hodinu"
description="Chcete se poradit nebo si nevíte rady? Projdeme Váš současný stav a najdeme kde je problém a navrhneme řešení."
href="mailto:iam@vojtechmares.com"
/>
<Plan
featured
name="Firemní školení"
price="24 000 CZK za jednodenní školení"
description="Jednoduše, s názornými příklady, které si každý vyzkouší. Naučím váš tým nové technologii nebo prohloubíme stávající znalosti."
href="/skoleni"
buttonText="Seznam školení"
/>
<Plan
name="Implementace"
price="Cena dohodou"
description="Analyzujeme vaši situaci, projdeme možnosti, vybereme nejlepší řešení a společně jej nasadíme."
href="https://calendly.com/vojtechmares/30min"
buttonText="Domluvme si schůzku"
/>
</div>
</Container>
</section>
)
}

View file

@ -0,0 +1,75 @@
import Image from 'next/image'
import clsx from 'clsx'
import { Container } from '@/components/Container'
import backgroundImage from '@/images/background-features.jpg'
const steps = [
{
name: 'Analýza současného stavu',
description: 'Zjistíme kde jsou slabá místa vaší infrastruktury nebo aplikace, a nebo obojího.',
},
{
name: 'Návrh řešení',
description: 'Navrhnu, jak tato slabá místa odstranit, na co si dát pozor a naplánujeme případné další kroky. ',
},
{
name: 'Implementace',
description: 'Přesunu vaši aplikaci do Kubernetes, ať na vašem vlastním hardware nebo v public cloudu. Celá infrastruktura bude jasně deklarovaná jako kód pomocí Terraformu.',
},
{
name: 'Proškolení vašeho týmu',
description: 'Naučím váš tým používat moderní technologie, tak abyste mohli rozvíjet vaší aplikaci a byznys a technologie byly nástrojem k rozvoji, ne břemenem, které s sebou táhnete.',
},
]
export function PrimaryFeatures() {
return (
<section
id="features"
aria-label="Features for running your books"
className="relative overflow-hidden bg-blue-600 pt-20 pb-28 sm:py-32"
>
<Image
className="absolute top-1/2 left-1/2 max-w-none translate-x-[-44%] translate-y-[-42%]"
src={backgroundImage}
alt=""
width={2245}
height={1636}
unoptimized
/>
<Container className="relative">
<div className="max-w-2xl md:mx-auto md:text-center xl:max-w-none">
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl md:text-5xl">
Z nuly do cloudu.
</h2>
<p className="mt-6 text-lg tracking-tight text-blue-100">
Vše co budete potřebovat, od začátku do cíle.
</p>
</div>
<nav aria-label="Progress">
<ol role="list" className="overflow-hidden pt-2 mt-20 max-w-3xl mx-auto">
{steps.map((step, stepIdx) => (
<li key={step.name} className={clsx(stepIdx !== steps.length - 1 ? 'pb-10' : '', 'relative')}>
{stepIdx !== steps.length - 1 ? (
<div className="absolute top-4 left-6 -ml-px mt-0.5 h-full w-0.5 bg-white" aria-hidden="true" />
) : null}
<div className="group relative flex items-start">
<span className="flex h-9 items-center" aria-hidden="true">
<span className="relative z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 border-blue-600 bg-white">
<span className="h-3 w-3 rounded-full bg-blue-600" />
</span>
</span>
<span className="ml-4 flex min-w-0 flex-col">
<span className="text-2xl text-white font-normal">{step.name}</span>
<span className="text-lg text-blue-200">{step.description}</span>
</span>
</div>
</li>
))}
</ol>
</nav>
</Container>
</section>
)
}

221
components/TechStack.tsx Normal file
View file

@ -0,0 +1,221 @@
import { ReactNode } from 'react'
import Image, { StaticImageData } from 'next/image'
import { Tab } from '@headlessui/react'
import clsx from 'clsx'
import { Container } from '@/components/Container'
import screenshotArgoCD from '@/images/screenshots/argocd.png'
import screenshotGitLab from '@/images/screenshots/gitlab.png'
import screenshotTerraform from '@/images/screenshots/terraform.png'
import screenshotKubernetes from '@/images/screenshots/kubernetes.png'
import screenshotGrafana from '@/images/screenshots/grafana.png'
import screenshotPrometheus from '@/images/screenshots/prometheus.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 logoGrafana from '@/images/logos/tools/grafana.svg'
const features = [
{
name: 'Verzování',
summary:
'Spravujte svůj kód pomocí verzovacího nástroje Git.',
description:
'Mějte historii změn v aplikaci od začátku až do dnes, můžete se kdykoliv vrátit do bodu v čase. Řešte konflikty včas, předtím než je nasadíte do produkce a umožněte vaším programátorům spolupracovat na jednou, aniž by si překáželi.',
image: screenshotGitLab,
icon: logoGit,
},
{
name: 'Infastruktura jako kód',
summary: 'Mějte vaši infrastrukturu deklarativně definovanou a verzovanou, díky Terraformu.',
description:
'Vaši infrastrukturu můžete snadno přesunout k jinému poskytovateli, a nebo jen vytvořit nové prostředí pro zákazníka, aby si váš produkt vyzkoušel a to během minut.',
image: screenshotTerraform,
icon: logoTerraform,
},
{
name: 'Orchestrace',
summary:
'Nechte vaši aplikaci běžet napříč mnoha servery a škálovat dle potřeb.',
description:
'Kubernetes se stalo nejen standardem, ale i hlavní platformou pro vývoj aplikací ať SaaS nebo dodávaných třetím stranám. Jde o skvělou platformu pro provoz vaší aplikace ať ve veřejném cloudu nebo na vlastním hardwaru popř. on edge blízko koncových uživatelů.',
image: screenshotKubernetes,
icon: logoKubernetes,
},
{
name: 'Monitoring',
summary:
'Sledujte Vaši aplikaci, jak se chová v čase.',
description:
'Prometheus je standard pro monitoring aplikací, ať v Kubernetes ale i mimo. Sbírejte telemetrická data v čase o vaši aplikaci. Vyhodnoťte, kde má vaše aplikace slabá místa. Zároveň můžete tvořit pravidla, dle kterých vás AlertManager upozorní, když se něco pokazí.',
image: screenshotPrometheus,
icon: logoPrometheus,
},
{
name: 'Vizualizace',
summary:
'Od grafů zatížení procesoru po počet otevřených TCP spojení, vše jasně a přehledně.',
description:
'Grafana je skvělý nástroj pro vizualizaci dat z monitoringu, vytvořte si dashboardy pro jednotlivé části vaší aplikace, mějte pohromadě infrastrukturu, provoz na síti, dostupnost a třeba počet neúspěšných pokusů o přihlášení, zda vůči vaší aplikaci neprobíhá hackerský útok.',
image: screenshotGrafana,
icon: logoGrafana,
},
{
name: 'GitOps',
summary:
'Spravujte stav Vašich prostředí deklarativně, ať vždy víte, jaký je aktuální stav.',
description:
'ArgoCD je spolehlivý nástroj pro práci s Kubernetes a nasazováním změn a přitom si udržovat přehled o právě nasazených aplikacích, verzím a konfiguraci, snadno, soplehlivě, verzovaně.',
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">
Open Source DevOps stack
</h2>
<p className="mt-4 text-lg tracking-tight text-slate-700">
Věřím v Open Source technologie, prakticky všichni je denně využíváme
a jsou naší budoucností.
</p>
</div>
<FeaturesMobile />
<FeaturesDesktop />
</Container>
</section>
)
}

144
components/Testimonials.tsx Normal file
View file

@ -0,0 +1,144 @@
import Image from 'next/image'
import { Container } from '@/components/Container'
import avatarImage1 from '@/images/avatars/avatar-1.png'
import avatarImage2 from '@/images/avatars/avatar-2.png'
import avatarImage3 from '@/images/avatars/avatar-3.png'
import avatarImage4 from '@/images/avatars/avatar-4.png'
import avatarImage5 from '@/images/avatars/avatar-5.png'
const testimonials = [
[
{
content:
'TaxPal is so easy to use I cant help but wonder if its really doing the things the government expects me to do.',
author: {
name: 'Sheryl Berge',
role: 'CEO at Lynch LLC',
image: avatarImage1,
},
},
{
content:
'Im trying to get a hold of someone in support, Im in a lot of trouble right now and they are saying it has something to do with my books. Please get back to me right away.',
author: {
name: 'Amy Hahn',
role: 'Director at Velocity Industries',
image: avatarImage4,
},
},
],
[
{
content:
'The best part about TaxPal is every time I pay my employees, my bank balance doesnt go down like it used to. Looking forward to spending this extra cash when I figure out why my card is being declined.',
author: {
name: 'Leland Kiehn',
role: 'Founder of Kiehn and Sons',
image: avatarImage5,
},
},
{
content:
'There are so many things I had to do with my old software that I just dont do at all with TaxPal. Suspicious but I cant say I dont love it.',
author: {
name: 'Erin Powlowski',
role: 'COO at Armstrong Inc',
image: avatarImage2,
},
},
],
[
{
content:
'I used to have to remit tax to the EU and with TaxPal I somehow dont have to do that anymore. Nervous to travel there now though.',
author: {
name: 'Peter Renolds',
role: 'Founder of West Inc',
image: avatarImage3,
},
},
{
content:
'This is the fourth email Ive sent to your support team. I am literally being held in jail for tax fraud. Please answer your damn emails, this is important.',
author: {
name: 'Amy Hahn',
role: 'Director at Velocity Industries',
image: avatarImage4,
},
},
],
]
function QuoteIcon(props: any) {
return (
<svg aria-hidden="true" width={105} height={78} {...props}>
<path d="M25.086 77.292c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622C1.054 58.534 0 53.411 0 47.686c0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C28.325 3.917 33.599 1.507 39.324 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Zm54.24 0c-4.821 0-9.115-1.205-12.882-3.616-3.767-2.561-6.78-6.102-9.04-10.622-2.11-4.52-3.164-9.643-3.164-15.368 0-5.273.904-10.396 2.712-15.368 1.959-4.972 4.746-9.567 8.362-13.786a59.042 59.042 0 0 1 12.43-11.3C82.565 3.917 87.839 1.507 93.564 0l11.074 13.786c-6.479 2.561-11.677 5.951-15.594 10.17-3.767 4.219-5.65 7.835-5.65 10.848 0 1.356.377 2.863 1.13 4.52.904 1.507 2.637 3.089 5.198 4.746 3.767 2.41 6.328 4.972 7.684 7.684 1.507 2.561 2.26 5.5 2.26 8.814 0 5.123-1.959 9.19-5.876 12.204-3.767 3.013-8.588 4.52-14.464 4.52Z" />
</svg>
)
}
export function Testimonials() {
return (
<section
id="testimonials"
aria-label="What our customers are saying"
className="bg-slate-50 py-20 sm:py-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">
Loved by businesses worldwide.
</h2>
<p className="mt-4 text-lg tracking-tight text-slate-700">
Our software is so simple that people cant help but fall in love
with it. Simplicity is easy when you just skip tons of
mission-critical features.
</p>
</div>
<ul
role="list"
className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-6 sm:gap-8 lg:mt-20 lg:max-w-none lg:grid-cols-3"
>
{testimonials.map((column, columnIndex) => (
<li key={columnIndex}>
<ul role="list" className="flex flex-col gap-y-6 sm:gap-y-8">
{column.map((testimonial, testimonialIndex) => (
<li key={testimonialIndex}>
<figure className="relative rounded-2xl bg-white p-6 shadow-xl shadow-slate-900/10">
<QuoteIcon className="absolute top-6 left-6 fill-slate-100" />
<blockquote className="relative">
<p className="text-lg tracking-tight text-slate-900">
{testimonial.content}
</p>
</blockquote>
<figcaption className="relative mt-6 flex items-center justify-between border-t border-slate-100 pt-6">
<div>
<div className="font-display text-base text-slate-900">
{testimonial.author.name}
</div>
<div className="mt-1 text-sm text-slate-500">
{testimonial.author.role}
</div>
</div>
<div className="overflow-hidden rounded-full bg-slate-50">
<Image
className="h-14 w-14 object-cover"
src={testimonial.author.image}
alt=""
width={56}
height={56}
/>
</div>
</figcaption>
</figure>
</li>
))}
</ul>
</li>
))}
</ul>
</Container>
</section>
)
}