The site now exists in German (at the root, with /impressum and /datenschutz) and English (/en). A request without a language follows the visitor's choice from the language switch, then the browser language, then German; the old English legal addresses move to /en for good. All visible texts are data in src/content/de.ts and en.ts, so components hold none of their own. The header carries a DE/EN switch whose bit curtain covers the page while it is swapped and reopens at the same reading position. Title, description, canonical, hreflang, Open Graph, the share image, the sitemap and the structured data are per language. The privacy notice now describes the cookie that remembers the chosen language. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
253 lines
8.8 KiB
TypeScript
253 lines
8.8 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 { WorkShowcase } from "@/src/components/illustrations/WorkShowcase";
|
|
import { Reveal } from "@/src/components/Reveal";
|
|
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, 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>
|
|
<div className="mt-16 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>
|
|
))}
|
|
</div>
|
|
</Block>
|
|
|
|
<Block
|
|
index="04"
|
|
label={technology.label}
|
|
title={<Title title={technology.title} />}
|
|
>
|
|
<div 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>
|
|
))}
|
|
</div>
|
|
|
|
<Reveal>
|
|
<div className="mt-16 rounded-3xl bg-slate-50 p-6 md:mt-24 md:p-10">
|
|
<p className={`${MONO} m-0`}>{technology.techLabel}</p>
|
|
<div className="mt-8 grid grid-cols-1 gap-10 sm:grid-cols-2 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-16 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="05"
|
|
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>
|
|
<div 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>
|
|
))}
|
|
</div>
|
|
</Block>
|
|
|
|
<Block
|
|
index="06"
|
|
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="07" label={faq.label} title={<Title title={faq.title} />}>
|
|
<Reveal>
|
|
<Faq items={faq.items} />
|
|
</Reveal>
|
|
</Block>
|
|
|
|
<ClosingCta contact={content.contact} />
|
|
</div>
|
|
);
|
|
}
|