On a phone the homepage was about 29 screens long; it is now about 22. The desktop layout is unchanged to the pixel. Lists of equal items (automation notes, project fit, principles, reasons for a web presentation, by hand vs. automated) are swiped through side by side (SwipeRow) instead of stacked. The four service cards form a compact 2x2 grid, step lists spell out only the current step, the workflow legend and the per-project task lists are left out, and sections use less padding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
338 lines
12 KiB
TypeScript
338 lines
12 KiB
TypeScript
import type { Metadata } from "next";
|
|
import { BitAura } from "@/src/components/bits/BitAura";
|
|
import { DecodeText } from "@/src/components/bits/DecodeText";
|
|
import { Block } from "@/src/components/home/Block";
|
|
import { ClosingCta } from "@/src/components/home/ClosingCta";
|
|
import { Faq } from "@/src/components/home/Faq";
|
|
import { HeroStatement } from "@/src/components/home/HeroStatement";
|
|
import { HomeJsonLd } from "@/src/components/home/HomeJsonLd";
|
|
import { PathPicker } from "@/src/components/home/PathPicker";
|
|
import { PortraitSection } from "@/src/components/home/PortraitSection";
|
|
import { StickyBook } from "@/src/components/home/StickyBook";
|
|
import { AutomationFlow } from "@/src/components/illustrations/AutomationFlow";
|
|
import { CareerLine } from "@/src/components/illustrations/CareerLine";
|
|
import { ContactDiagram } from "@/src/components/illustrations/ContactDiagram";
|
|
import { PipelineRun } from "@/src/components/illustrations/PipelineRun";
|
|
import {
|
|
PresentationCompare,
|
|
ReasonDrawing,
|
|
} from "@/src/components/illustrations/WebPresentation";
|
|
import { WorkShowcase } from "@/src/components/illustrations/WorkShowcase";
|
|
import { Reveal } from "@/src/components/Reveal";
|
|
import { SwipeRow } from "@/src/components/SwipeRow";
|
|
import { contentFor } from "@/src/content";
|
|
import type { SplitTitle } from "@/src/content/types";
|
|
import { DEFAULT_LOCALE, parseLocale } from "@/src/domain/locale";
|
|
import { languageAlternates, pathFor } from "@/src/domain/routes";
|
|
|
|
type Props = { readonly params: Promise<{ readonly locale: string }> };
|
|
|
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
const locale = parseLocale((await params).locale) ?? DEFAULT_LOCALE;
|
|
const { site } = contentFor(locale);
|
|
return {
|
|
title: { absolute: site.title },
|
|
description: site.description,
|
|
alternates: {
|
|
canonical: pathFor("home", locale),
|
|
languages: languageAlternates("home"),
|
|
},
|
|
};
|
|
}
|
|
|
|
const MUTED = "text-slate-400";
|
|
const BODY = "m-0 font-sans text-lg leading-relaxed text-slate-500 md:text-xl";
|
|
const MONO = "font-mono text-[11px] uppercase tracking-[0.2em] text-slate-500";
|
|
|
|
const Title: React.FC<{ title: SplitTitle; muted?: string }> = ({
|
|
title,
|
|
muted = MUTED,
|
|
}) => (
|
|
<>
|
|
{title.lead} <span className={muted}>{title.rest}</span>
|
|
</>
|
|
);
|
|
|
|
export default async function LandingPage({ params }: Props) {
|
|
const locale = parseLocale((await params).locale) ?? DEFAULT_LOCALE;
|
|
const content = contentFor(locale);
|
|
const {
|
|
hello,
|
|
services,
|
|
automation,
|
|
presentations,
|
|
technology,
|
|
working,
|
|
work,
|
|
faq,
|
|
} = content;
|
|
|
|
return (
|
|
<div className="relative flex flex-col bg-white">
|
|
<HomeJsonLd locale={locale} />
|
|
<HeroStatement hero={content.hero} />
|
|
<StickyBook />
|
|
|
|
<PortraitSection
|
|
id="hello"
|
|
index="01"
|
|
label={hello.label}
|
|
title={hello.title}
|
|
portraitAlt={hello.portraitAlt}
|
|
>
|
|
<p className="m-0 font-sans text-2xl leading-snug text-white md:text-4xl">
|
|
{hello.lead}
|
|
</p>
|
|
{hello.paragraphs.map((paragraph, i) => (
|
|
<p
|
|
key={paragraph}
|
|
className={`m-0 font-sans text-lg leading-relaxed text-slate-300 md:text-xl ${i === 0 ? "mt-8" : "mt-6"}`}
|
|
>
|
|
{paragraph}
|
|
</p>
|
|
))}
|
|
</PortraitSection>
|
|
|
|
<section className="relative overflow-clip bg-slate-900">
|
|
<div className="mx-auto w-full max-w-[1400px] px-5 pb-20 pt-4 md:px-12 md:pb-36 md:pt-0">
|
|
<p className="m-0 mb-10 font-mono text-[11px] uppercase tracking-[0.2em] text-slate-400">
|
|
{hello.careerLabel}
|
|
</p>
|
|
<CareerLine stations={hello.career} />
|
|
</div>
|
|
</section>
|
|
|
|
<Block
|
|
index="02"
|
|
label={services.label}
|
|
title={<Title title={services.title} />}
|
|
>
|
|
<Reveal>
|
|
<PathPicker />
|
|
</Reveal>
|
|
</Block>
|
|
|
|
<Block
|
|
index="03"
|
|
label={automation.label}
|
|
title={<Title title={automation.title} />}
|
|
>
|
|
<Reveal>
|
|
<p className={`${BODY} mb-12 max-w-3xl`}>{automation.intro}</p>
|
|
</Reveal>
|
|
<Reveal>
|
|
<AutomationFlow />
|
|
</Reveal>
|
|
<SwipeRow className="mt-12 grid grid-cols-1 gap-12 md:mt-24 md:grid-cols-3 md:gap-10">
|
|
{automation.notes.map((group, i) => (
|
|
<Reveal key={group.title} delay={0.1 * i}>
|
|
<div className="border-t border-slate-900 pt-6">
|
|
<p className={`${MONO} m-0`}>{group.title}</p>
|
|
<ul className="m-0 mt-4 list-none p-0">
|
|
{group.items.map((item) => (
|
|
<li
|
|
key={item}
|
|
className="border-b border-slate-100 py-4 font-sans text-lg text-slate-900 md:text-xl"
|
|
>
|
|
{item}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</Reveal>
|
|
))}
|
|
</SwipeRow>
|
|
</Block>
|
|
|
|
<Block
|
|
index="04"
|
|
label={presentations.label}
|
|
tone="dark"
|
|
title={<Title title={presentations.title} muted="text-slate-500" />}
|
|
>
|
|
<Reveal>
|
|
<p className="m-0 mb-12 max-w-3xl font-sans text-lg leading-relaxed text-slate-300 md:mb-16 md:text-xl">
|
|
{presentations.intro}
|
|
</p>
|
|
</Reveal>
|
|
<Reveal>
|
|
<BitAura source="around" tone="dark">
|
|
<div className="rounded-3xl border border-slate-700 bg-slate-950 px-4 py-8 md:px-10 md:py-12">
|
|
<PresentationCompare />
|
|
</div>
|
|
</BitAura>
|
|
</Reveal>
|
|
|
|
<SwipeRow className="mt-12 grid grid-cols-1 gap-x-10 gap-y-14 md:mt-28 md:grid-cols-2 lg:grid-cols-4">
|
|
{presentations.reasons.map((reason, i) => (
|
|
<Reveal key={reason.title} delay={0.08 * i}>
|
|
<div className="rounded-2xl border border-slate-800 bg-slate-950/60 p-4">
|
|
<ReasonDrawing index={i} />
|
|
</div>
|
|
<h3 className="m-0 mt-6 font-sans text-2xl font-bold tracking-tight text-white">
|
|
{reason.title}
|
|
</h3>
|
|
<p className="m-0 mt-3 font-sans text-base leading-relaxed text-slate-300 md:text-lg">
|
|
{reason.text}
|
|
</p>
|
|
</Reveal>
|
|
))}
|
|
</SwipeRow>
|
|
|
|
<div className="mt-16 grid grid-cols-1 gap-12 md:mt-28 md:grid-cols-12 md:gap-12">
|
|
<Reveal className="md:col-span-6">
|
|
<div className="border-t border-slate-700 pt-6">
|
|
<p className="m-0 font-mono text-[11px] uppercase tracking-[0.2em] text-slate-400">
|
|
{presentations.goodFor.title}
|
|
</p>
|
|
<ul className="m-0 mt-4 list-none p-0">
|
|
{presentations.goodFor.items.map((item) => (
|
|
<li
|
|
key={item}
|
|
className="border-b border-slate-800 py-4 font-sans text-lg text-slate-100 md:text-xl"
|
|
>
|
|
{item}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</Reveal>
|
|
<Reveal delay={0.1} className="md:col-span-6">
|
|
<div className="rounded-3xl border border-slate-700 bg-slate-950/60 p-6 md:p-10">
|
|
<h3 className="m-0 font-sans text-2xl font-bold tracking-tight text-white md:text-3xl">
|
|
{presentations.honest.title}
|
|
</h3>
|
|
<p className="m-0 mt-4 font-sans text-base leading-relaxed text-slate-300 md:text-lg">
|
|
{presentations.honest.text}
|
|
</p>
|
|
<a
|
|
href="#contact"
|
|
className="bit-cta bit-cta-light mt-8 inline-flex items-center gap-3 rounded-full bg-white px-6 py-3 font-sans text-[11px] font-bold uppercase tracking-[0.15em] text-slate-900 no-underline transition-transform duration-300 hover:-translate-y-0.5"
|
|
>
|
|
{presentations.cta} <span aria-hidden="true">→</span>
|
|
</a>
|
|
</div>
|
|
</Reveal>
|
|
</div>
|
|
</Block>
|
|
|
|
<Block
|
|
index="05"
|
|
label={technology.label}
|
|
title={<Title title={technology.title} />}
|
|
>
|
|
<SwipeRow className="grid grid-cols-1 gap-12 md:grid-cols-3 md:gap-10">
|
|
{technology.fit.map((group, i) => (
|
|
<Reveal key={group.title} delay={0.1 * i}>
|
|
<div
|
|
className={`border-t pt-6 ${i === 2 ? "border-slate-300" : "border-slate-900"}`}
|
|
>
|
|
<p className={`${MONO} m-0`}>{group.title}</p>
|
|
<ul className="m-0 mt-4 list-none p-0">
|
|
{group.items.map((item) => (
|
|
<li
|
|
key={item}
|
|
className={`border-b border-slate-100 py-4 font-sans text-lg md:text-xl ${
|
|
i === 2 ? "text-slate-400" : "text-slate-900"
|
|
}`}
|
|
>
|
|
{item}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</Reveal>
|
|
))}
|
|
</SwipeRow>
|
|
|
|
<Reveal>
|
|
<div className="mt-12 rounded-3xl bg-slate-50 p-6 md:mt-24 md:p-10">
|
|
<p className={`${MONO} m-0`}>{technology.techLabel}</p>
|
|
<div className="mt-6 grid grid-cols-1 gap-7 sm:grid-cols-2 md:mt-8 md:gap-10 lg:grid-cols-4">
|
|
{technology.tech.map((group) => (
|
|
<div key={group.area}>
|
|
<p className="m-0 font-sans text-xl font-bold tracking-tight text-slate-900 md:text-2xl">
|
|
{group.area}
|
|
</p>
|
|
<ul className="m-0 mt-4 flex list-none flex-wrap gap-2 p-0">
|
|
{group.items.map((item) => (
|
|
<li
|
|
key={item}
|
|
className="rounded-full border border-slate-200 bg-white px-4 py-2 font-mono text-[11px] uppercase tracking-[0.12em] text-slate-700"
|
|
>
|
|
<DecodeText text={item} trigger="both" />
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</Reveal>
|
|
|
|
<div className="mt-12 md:mt-24">
|
|
<Reveal>
|
|
<h3 className="m-0 font-sans text-3xl font-bold tracking-tighter text-slate-900 md:text-5xl">
|
|
{technology.pipelineTitle}
|
|
</h3>
|
|
<p className={`${BODY} mb-12 mt-6 max-w-3xl`}>
|
|
{technology.pipelineIntro}
|
|
</p>
|
|
</Reveal>
|
|
<Reveal>
|
|
<PipelineRun />
|
|
</Reveal>
|
|
</div>
|
|
</Block>
|
|
|
|
<Block
|
|
index="06"
|
|
label={working.label}
|
|
title={<Title title={working.title} />}
|
|
>
|
|
<Reveal>
|
|
<BitAura source="bottom" tone="light" className="mb-16 md:mb-24">
|
|
<div className="overflow-hidden rounded-3xl bg-slate-900 p-4 md:p-10">
|
|
<ContactDiagram />
|
|
</div>
|
|
</BitAura>
|
|
</Reveal>
|
|
<SwipeRow className="grid grid-cols-1 gap-x-12 gap-y-12 md:grid-cols-2 md:gap-y-16">
|
|
{working.principles.map((item, i) => (
|
|
<Reveal key={item.title} delay={0.08 * i}>
|
|
<div className="border-t border-slate-900 pt-6">
|
|
<h3 className="m-0 font-sans text-3xl font-bold tracking-tight text-slate-900 md:text-4xl">
|
|
{item.title}
|
|
</h3>
|
|
<p className={`${BODY} mt-4`}>{item.text}</p>
|
|
</div>
|
|
</Reveal>
|
|
))}
|
|
</SwipeRow>
|
|
</Block>
|
|
|
|
<Block
|
|
index="07"
|
|
label={work.label}
|
|
tone="dark"
|
|
title={<Title title={work.title} muted="text-slate-500" />}
|
|
>
|
|
<WorkShowcase />
|
|
<Reveal>
|
|
<p className="m-0 mt-20 max-w-3xl border-t border-slate-800 pt-8 font-sans text-lg leading-relaxed text-slate-300 md:mt-28 md:text-xl">
|
|
{work.note}
|
|
</p>
|
|
</Reveal>
|
|
</Block>
|
|
|
|
<Block index="08" label={faq.label} title={<Title title={faq.title} />}>
|
|
<Reveal>
|
|
<Faq items={faq.items} />
|
|
</Reveal>
|
|
</Block>
|
|
|
|
<ClosingCta contact={content.contact} />
|
|
</div>
|
|
);
|
|
}
|