feat: new homepage with hero, proof, offers and live references

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:20:05 +02:00
co-authored by Claude Sonnet 5.5
parent f258e2a483
commit c71916cf08
11 changed files with 411 additions and 306 deletions
+39 -306
View File
@@ -1,311 +1,44 @@
"use client";
import type { Metadata } from "next";
import { BuiltAndRunning } from "@/src/components/home/BuiltAndRunning";
import { CapacityBlocks } from "@/src/components/home/CapacityBlocks";
import { FinalCta } from "@/src/components/home/FinalCta";
import { Hero } from "@/src/components/home/Hero";
import { HomeJsonLd } from "@/src/components/home/HomeJsonLd";
import { HowIShip } from "@/src/components/home/HowIShip";
import { ProofStrip } from "@/src/components/home/ProofStrip";
import { WebsitesSection } from "@/src/components/home/WebsitesSection";
import { WhatIDo } from "@/src/components/home/WhatIDo";
import { SITE_NAME } from "@/src/content/site";
import { Reveal } from "@/src/components/Reveal";
import { Section } from "@/src/components/Section";
import { H1, H3, LeadText, BodyText, Label } from "@/src/components/Typography";
import { Button } from "@/src/components/Button";
import { HeroSection } from "@/src/components/HeroSection";
import { Availability } from "@/src/components/Availability";
import { AbstractCircuit, GradientMesh } from "@/src/components/Effects";
import { Marker } from "@/src/components/Marker";
import { GlitchText } from "@/src/components/GlitchText";
const TITLE = `${SITE_NAME}: senior engineer for AI-built software`;
const DESCRIPTION =
"Senior engineer for teams that build fast with AI and don't want chaos. Spec-first, test-driven, mutation-checked. Booked in weekly capacity blocks.";
export default function LandingPage() {
export const metadata: Metadata = {
title: { absolute: TITLE },
description: DESCRIPTION,
alternates: { canonical: "/" },
openGraph: {
title: TITLE,
description: DESCRIPTION,
url: "/",
type: "website",
siteName: SITE_NAME,
},
};
export default function HomePage() {
return (
<div className="flex flex-col bg-white overflow-hidden relative">
<AbstractCircuit />
<HeroSection />
{/* Section 02: Ihr Vorteil */}
<Section number="02" title="Ihr Vorteil" borderTop>
<div className="space-y-16 md:space-y-32 relative z-10">
<Reveal>
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
Sie kümmern sich um Ihr Geschäft.{" "}
<span className="text-slate-400">
Ich mich um Ihre <Marker delay={0.3}>Website.</Marker>
</span>
</H3>
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
{[
{
title: "Ein Ansprechpartner",
text: "Direkte Kommunikation. Keine Agentur, keine Projektmanager, kein Stille-Post-Effekt.",
},
{
title: "Fixpreis",
text: "Keine versteckten Kosten, keine Stundenzettel. Sie wissen exakt, was es kostet.",
},
{
title: "Schnelle Ergebnisse",
text: "Kein monatelanges Warten. Erste Entwürfe innerhalb weniger Tage.",
},
{
title: "Null Arbeit für Sie",
text: "Keine CMS-Einarbeitung. Änderungen oder Updates? Eine kurze E-Mail genügt.",
},
].map((item, i) => (
<Reveal key={i} delay={0.1 * i}>
<div className="space-y-4">
<div className="h-px w-full bg-slate-100 mb-6" />
<Label className="text-slate-900 text-lg md:text-xl font-medium tracking-tight">
{item.title}
</Label>
<BodyText className="text-slate-500 text-base md:text-lg">
{item.text}
</BodyText>
</div>
</Reveal>
))}
</div>
</div>
</Section>
{/* Section 03: Der Unterschied */}
<Section number="03" title="Der Unterschied" borderTop>
<div className="space-y-16 md:space-y-32">
<Reveal>
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
Warum nicht einfach{" "}
<span className="text-slate-400">eine Agentur?</span>
</H3>
</Reveal>
<div className="space-y-12 md:space-y-0">
{[
{
label: "Agentur",
text: "Projektmanager, Designer, Entwickler — Sie erklären alles drei Mal. Stundenabrechnungen, unklare Endkosten.",
},
{
label: "Baukasten (Wix, etc.)",
text: "Sie bauen selbst, kämpfen mit Templates und sind auf sich allein gestellt, wenn etwas nicht funktioniert.",
},
{
label: "Bei mir",
text: "Ein Ansprechpartner. Fixpreis. Ich baue, Sie lehnen sich zurück. Sie brauchen eine Änderung? Kurze Nachricht genügt.",
highlight: true,
},
].map((item, i) => (
<Reveal key={i} delay={0.1 * i}>
<div
className={`grid grid-cols-1 md:grid-cols-4 gap-4 md:gap-8 py-8 md:py-16 border-t ${item.highlight ? "border-slate-900" : "border-slate-100"}`}
>
<div className="md:col-span-1">
<Label
className={`text-lg md:text-xl ${item.highlight ? "text-slate-900 font-bold" : "text-slate-400"}`}
>
{item.label}
</Label>
</div>
<div className="md:col-span-3">
<LeadText
className={`text-xl md:text-3xl leading-snug ${item.highlight ? "text-slate-900" : "text-slate-500"}`}
>
{item.text}
</LeadText>
</div>
</div>
</Reveal>
))}
</div>
</div>
</Section>
{/* Section 04: Zielgruppe */}
<Section number="04" title="Für wen" borderTop>
<div className="space-y-16 md:space-y-32">
<Reveal>
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
Fokus auf das <span className="text-slate-400">Wesentliche.</span>
</H3>
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
<Reveal>
<div className="space-y-8">
<H3 className="text-2xl md:text-4xl">
Unternehmer & Geschäftsführer
</H3>
<LeadText className="text-slate-500">
Sie brauchen eine professionelle Website, die Ihr Unternehmen
repräsentiert — ohne sich selbst mit Technik beschäftigen zu
müssen. Ich übernehme alles.
</LeadText>
</div>
</Reveal>
<Reveal delay={0.2}>
<div className="space-y-8">
<H3 className="text-2xl md:text-4xl">
Selbstständige & kleine Teams
</H3>
<LeadText className="text-slate-500">
Sie wollen online sichtbar sein, aber keine Zeit in eine
Website stecken. Ich liefere eine fertige Lösung, damit Sie
sich auf Ihre Kunden konzentrieren können.
</LeadText>
</div>
</Reveal>
</div>
</div>
</Section>
{/* Section 05: Leistungen */}
<Section number="05" title="Leistungen" borderTop>
<div className="space-y-0">
{[
{
title: "Websites",
text: "Professionelle, schnelle Websites — individuell für Ihr Unternehmen gestaltet. Ohne Overhead.",
href: "/websites",
},
{
title: "Sorglos-Paket",
text: "Änderungen, Wartung, Sicherheit — alles inklusive. Sie sagen Bescheid, ich erledige das.",
href: "/contact",
},
{
title: "Web-Anwendungen",
text: "Kundenportale, interne Tools oder individuelle Lösungen für Ihr Business.",
href: "/showcase",
},
].map((service, i) => (
<Reveal key={i} delay={0.1 * i}>
<a href={service.href} className="group block">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-6 py-12 md:py-20 border-b border-slate-100 transition-colors hover:border-slate-300">
<div className="space-y-4 max-w-2xl">
<H3 className="text-3xl md:text-5xl group-hover:translate-x-2 transition-transform duration-500">
{service.title}
</H3>
<LeadText className="text-slate-400 group-hover:text-slate-600 transition-colors">
{service.text}
</LeadText>
</div>
<div className="text-slate-300 group-hover:text-slate-900 transition-colors pb-1">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
</div>
</div>
</a>
</Reveal>
))}
</div>
</Section>
{/* Section 06: Blog / Insights */}
<Section number="06" title="Einblicke" borderTop>
<div className="space-y-16 md:space-y-32">
<Reveal>
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
Wissen statt <span className="text-slate-400">Werbung.</span>
</H3>
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24">
<Reveal>
<div className="space-y-8">
<Label className="text-slate-400">Aktueller Artikel</Label>
<H3 className="text-2xl md:text-4xl leading-tight">
Warum Websites nach Updates oft brechen – und wie man das
verhindert.
</H3>
<div>
<Button
href="/blog/why-websites-break-after-updates"
variant="outline"
>
Artikel lesen
</Button>
</div>
</div>
</Reveal>
<Reveal delay={0.2}>
<div className="space-y-8 border-l border-slate-100 pl-8 md:pl-12 flex flex-col justify-end">
<LeadText className="text-slate-500">
Ich teile meine Gedanken zu digitaler Architektur, Fixpreisen
und warum weniger oft mehr ist.
</LeadText>
<a
href="/blog"
className="text-slate-900 font-bold hover:underline underline-offset-4"
>
Alle Artikel ansehen →
</a>
</div>
</Reveal>
</div>
</div>
</Section>
{/* Section 07: Contact */}
<Section
number="07"
title="Kontakt"
borderTop
effects={<GradientMesh variant="metallic" className="opacity-60" />}
>
<div className="relative py-12 md:py-32" id="contact">
<Reveal>
<div className="space-y-12 md:space-y-24 relative z-10">
<H1 className="text-5xl md:text-8xl lg:text-9xl leading-none tracking-tighter">
Bereit für den Start? <br />
<span className="text-slate-400">
<GlitchText delay={0.5} duration={1}>
Sagen Sie Hallo.
</GlitchText>
</span>
</H1>
<div className="flex flex-col md:flex-row gap-12 md:gap-24 items-start">
<div className="space-y-8 flex-1">
<LeadText className="text-2xl md:text-4xl text-slate-500 leading-snug">
Schreiben Sie mir kurz, was Sie vorhaben. Ich melde mich
innerhalb von 24 Stunden bei Ihnen.
</LeadText>
<div className="pt-4">
<Button
href="/contact"
size="large"
className="text-lg px-8 py-4"
>
Jetzt Nachricht schreiben
</Button>
</div>
</div>
<div className="w-full md:w-80 space-y-4 pt-4 md:pt-0">
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-slate-900 rounded-full"></div>
<Label className="text-slate-900 text-sm">
Verfügbarkeit
</Label>
</div>
<BodyText className="text-slate-500">
Aktuell nehme ich Projekte für{" "}
<span className="font-bold text-slate-900">
<Availability />
</span>{" "}
an.
</BodyText>
</div>
</div>
</div>
</Reveal>
</div>
</Section>
</div>
<>
<HomeJsonLd />
<Hero />
<ProofStrip />
<HowIShip />
<WhatIDo />
<CapacityBlocks />
<WebsitesSection />
<BuiltAndRunning />
<FinalCta />
</>
);
}
@@ -0,0 +1,28 @@
"use client";
import * as React from "react";
import { Pill } from "@/src/components/site";
import { AVAILABILITY_CONFIG } from "@/src/content/site";
import {
formatAvailability,
resolveAvailability,
} from "@/src/domain/availability";
const todayIso = (): string => new Date().toISOString().slice(0, 10);
/** Computes "today" in the browser so a statically rendered page never shows stale availability. */
export const AvailabilityPill: React.FC = () => {
const [today, setToday] = React.useState<string | null>(null);
React.useEffect(() => {
setToday(todayIso());
}, []);
if (today === null) return null;
return (
<Pill>
{formatAvailability(resolveAvailability(AVAILABILITY_CONFIG, today))}
</Pill>
);
};
@@ -0,0 +1,47 @@
import { Container } from "@/src/components/site";
type Reference = {
readonly name: string;
readonly href: string;
readonly built: string;
};
const REFERENCES: readonly Reference[] = [
{
name: "klz-cables.com",
href: "https://klz-cables.com",
built:
"multilingual (DE/EN) Next.js site with a headless CMS and generated PDF datasheets",
},
{
name: "e-tib.com",
href: "https://e-tib.com",
built:
"multilingual Next.js site with structured data, automated QA and CI/CD",
},
];
export const BuiltAndRunning: React.FC = () => (
<section className="border-b border-border py-10 md:py-14">
<Container>
<p className="!mb-0 text-sm leading-relaxed text-muted md:text-base">
<span className="mr-2 font-mono text-xs uppercase tracking-widest text-accent">
Built and running
</span>
{REFERENCES.map((ref, index) => (
<span key={ref.name}>
{index > 0 ? " · " : null}
<a
href={ref.href}
rel="noopener"
className="underline underline-offset-4 hover:text-accent"
>
{ref.name}
</a>{" "}
{ref.built}
</span>
))}
</p>
</Container>
</section>
);
@@ -0,0 +1,35 @@
import { Container, SectionHeader } from "@/src/components/site";
import { ENGAGEMENT_BLOCKS } from "@/src/content/site";
export const CapacityBlocks: React.FC = () => (
<section className="border-b border-border py-14 md:py-20">
<Container>
<SectionHeader
kicker="capacity blocks"
title="Book weekly capacity"
lead="You reserve hours per week for a month at a time. No fixed-scope surprises."
/>
<ul className="!m-0 mt-10 grid list-none gap-4 !p-0 md:grid-cols-3">
{ENGAGEMENT_BLOCKS.map((block) => (
<li
key={block.name}
className="!mb-0 rounded-lg border border-border bg-surface p-5"
>
<h3 className="!mb-1 !mt-0 !text-lg !leading-snug md:!text-xl lg:!text-xl">
{block.name}
</h3>
<p className="!mb-4 font-mono text-sm text-muted">
{block.hoursPerWeek}
</p>
<p className="!mb-0 font-mono text-lg font-semibold text-fg">
{block.pricePerMonth}
</p>
</li>
))}
</ul>
<p className="!mb-0 mt-6 text-sm text-muted">
2-week notice, transparent time tracking, no fixed-scope surprises.
</p>
</Container>
</section>
);
+20
View File
@@ -0,0 +1,20 @@
import { ButtonLink, Container } from "@/src/components/site";
export const FinalCta: React.FC = () => (
<section className="py-16 md:py-24">
<Container>
<h2 className="!mb-4 !mt-0 !text-3xl !leading-tight md:!text-4xl">
Tell me what you are building
</h2>
<p className="!mb-8 max-w-xl text-base text-muted md:text-lg">
A 20 minute call is enough to see whether a capacity block fits.
</p>
<div className="flex flex-col gap-3 sm:flex-row">
<ButtonLink href="/book">Book a 20 min call</ButtonLink>
<ButtonLink href="/notes" variant="secondary">
Read the notes
</ButtonLink>
</div>
</Container>
</section>
);
+58
View File
@@ -0,0 +1,58 @@
import { ButtonLink, Container, TerminalCard } from "@/src/components/site";
import { STACK } from "@/src/content/site";
import { AvailabilityPill } from "./AvailabilityPill";
const REPORT_LINES: readonly { label: string; value: string }[] = [
{ label: "spec", value: "12 cases written before code" },
{ label: "red", value: "12 failing for the right reason" },
{ label: "green", value: "12 passing" },
{ label: "mutation", value: "changed files checked, survivors fixed" },
{ label: "coverage", value: "changed lines reviewed for gaps" },
];
export const Hero: React.FC = () => (
<section className="border-b border-border py-14 md:py-24">
<Container>
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
<div>
<div className="mb-6 min-h-7">
<AvailabilityPill />
</div>
<h1 className="!mb-6 !text-4xl !leading-[1.1] md:!text-6xl">
AI makes code cheap.
<br />
<span className="text-accent">I make it correct.</span>
</h1>
<p className="!mb-8 max-w-xl text-base leading-relaxed text-muted md:text-lg">
Senior engineer for teams that build fast with AI and don&apos;t
want chaos. Spec-first, test-driven, mutation-checked. Booked in
weekly capacity blocks.
</p>
<div className="flex flex-col gap-3 sm:flex-row">
<ButtonLink href="/book">Book a 20 min call</ButtonLink>
<ButtonLink href="/process" variant="secondary">
See how I ship
</ButtonLink>
</div>
</div>
<TerminalCard title="delivery-report (illustrative example)">
<ul className="!m-0 list-none !p-0">
{REPORT_LINES.map((line) => (
<li key={line.label} className="!mb-1 flex gap-3">
<span className="w-20 shrink-0 text-accent">{line.label}</span>
<span className="text-muted">{line.value}</span>
</li>
))}
</ul>
<p className="!mb-0 !mt-4 text-xs text-faint">
Example format. Every real delivery lists the exact commands and
their results.
</p>
</TerminalCard>
</div>
<p className="!mb-0 mt-12 font-mono text-xs text-faint md:text-sm">
{STACK.join(" · ")}
</p>
</Container>
</section>
);
@@ -0,0 +1,21 @@
import { SITE_NAME, SITE_URL } from "@/src/content/site";
const STRUCTURED_DATA = {
"@context": "https://schema.org",
"@type": "ProfessionalService",
name: SITE_NAME,
url: SITE_URL,
description:
"Senior freelance engineer for teams that build fast with AI: web apps, AI-code rescue, automation and Web3 frontends.",
founder: { "@type": "Person", name: SITE_NAME },
} as const;
/** Serialized server-side from constants only; "<" is escaped to keep it inert inside a script tag. */
export const HomeJsonLd: React.FC = () => (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(STRUCTURED_DATA).replace(/</g, "\\u003c"),
}}
/>
);
+61
View File
@@ -0,0 +1,61 @@
import Link from "next/link";
import { Container, SectionHeader } from "@/src/components/site";
const STEPS: readonly { name: string; text: string }[] = [
{
name: "Spec first",
text: "The behavior is written down as concrete cases before any code.",
},
{
name: "Red",
text: "The tests run and fail for the right reason: missing behavior.",
},
{
name: "Green",
text: "The simplest clean implementation that makes them pass.",
},
{
name: "Mutation check",
text: "Mutation testing on the changed code shows whether the tests really catch bugs.",
},
{
name: "Delivery report",
text: "Every pull request lists the exact commands that were run and their results.",
},
];
export const HowIShip: React.FC = () => (
<section className="border-b border-border py-14 md:py-20">
<Container>
<SectionHeader
kicker="how I ship"
title="Fast with AI, checked by tests"
lead="AI writes the first draft. These five steps decide whether it ships."
/>
<ol className="!m-0 mt-10 grid list-none gap-4 !p-0 md:grid-cols-5">
{STEPS.map((step, index) => (
<li
key={step.name}
className="!mb-0 rounded-lg border border-border bg-surface p-4"
>
<p className="!mb-2 font-mono text-xs text-accent">
{String(index + 1).padStart(2, "0")}
</p>
<h3 className="!mb-2 !mt-0 !text-base !leading-snug md:!text-base lg:!text-base">
{step.name}
</h3>
<p className="!mb-0 text-sm text-muted">{step.text}</p>
</li>
))}
</ol>
<p className="!mb-0 mt-6 text-sm">
<Link
href="/process"
className="underline underline-offset-4 hover:text-accent"
>
Read the full process and a sample report
</Link>
</p>
</Container>
</section>
);
@@ -0,0 +1,23 @@
import { Container, Stat } from "@/src/components/site";
const FACTS: readonly { value: string; label: string }[] = [
{ value: "15 years", label: "in web, apprenticeship from 2011" },
{ value: "Team lead", label: "frontend team lead at an agency" },
{ value: "Since 2021", label: "working fully remote" },
{
value: "7 chains",
label: "Rust + Solidity DeFi system with a zero-survivor mutation gate",
},
];
export const ProofStrip: React.FC = () => (
<section aria-label="Background" className="border-b border-border py-10">
<Container>
<div className="grid grid-cols-2 gap-8 lg:grid-cols-4">
{FACTS.map((fact) => (
<Stat key={fact.value} value={fact.value} label={fact.label} />
))}
</div>
</Container>
</section>
);
@@ -0,0 +1,33 @@
import { Container, SectionHeader } from "@/src/components/site";
const SPRINT_POINTS: readonly string[] = [
"Time-boxed sprint with a clear scope list, from 3,800 EUR. Extra work is a follow-up block, not open scope.",
"Static Next.js site: no CMS and no server for you to run.",
"Hosting, SSL and domain renewal are included in a flat monthly plan.",
"Changes by email, no CMS training.",
];
export const WebsitesSection: React.FC = () => (
<section className="border-b border-border py-14 md:py-20">
<Container>
<SectionHeader
kicker="websites"
title="Websites, handled end to end"
lead="For clients who want a site and no involvement in the technical side."
/>
<ul className="!m-0 mt-8 max-w-2xl list-none !p-0">
{SPRINT_POINTS.map((point) => (
<li key={point} className="!mb-3 flex gap-3 text-base text-muted">
<span aria-hidden="true" className="font-mono text-accent">
+
</span>
<span>{point}</span>
</li>
))}
</ul>
<p className="!mb-0 mt-4 max-w-2xl font-mono text-sm text-faint">
Not part of it: email and DNS administration.
</p>
</Container>
</section>
);
+46
View File
@@ -0,0 +1,46 @@
import { Container, SectionHeader } from "@/src/components/site";
const OFFERS: readonly { title: string; text: string }[] = [
{
title: "Web and web apps",
text: "Next.js and TypeScript with strong design and frontend work, plus light backend for APIs, auth and integrations.",
},
{
title: "AI-code rescue",
text: "A 1-week entry block: audit of an AI-built codebase, test foundation, mutation baseline and a prioritized report.",
},
{
title: "CLI tools and automation",
text: "Command-line tools and automation, including AI agents and MCP servers.",
},
{
title: "Web3 frontends and systems",
text: "Wallet flows, dashboards and WebSockets, backed by a Rust and Solidity background.",
},
];
export const WhatIDo: React.FC = () => (
<section className="border-b border-border py-14 md:py-20">
<Container>
<SectionHeader kicker="what I do" title="What you can book" />
<ul className="!m-0 mt-10 grid list-none gap-4 !p-0 sm:grid-cols-2">
{OFFERS.map((offer) => (
<li
key={offer.title}
className="!mb-0 rounded-lg border border-border bg-surface p-5"
>
<h3 className="!mb-2 !mt-0 !text-lg !leading-snug md:!text-xl lg:!text-xl">
{offer.title}
</h3>
<p className="!mb-0 text-sm text-muted md:text-base">
{offer.text}
</p>
</li>
))}
</ul>
<p className="!mb-0 mt-6 font-mono text-sm text-faint">
Not offered: Windows apps, mobile apps, running your infrastructure.
</p>
</Container>
</section>
);