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:
+39
-306
@@ -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";
|
const TITLE = `${SITE_NAME}: senior engineer for AI-built software`;
|
||||||
import { Section } from "@/src/components/Section";
|
const DESCRIPTION =
|
||||||
import { H1, H3, LeadText, BodyText, Label } from "@/src/components/Typography";
|
"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.";
|
||||||
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";
|
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<div className="flex flex-col bg-white overflow-hidden relative">
|
<>
|
||||||
<AbstractCircuit />
|
<HomeJsonLd />
|
||||||
<HeroSection />
|
<Hero />
|
||||||
|
<ProofStrip />
|
||||||
{/* Section 02: Ihr Vorteil */}
|
<HowIShip />
|
||||||
<Section number="02" title="Ihr Vorteil" borderTop>
|
<WhatIDo />
|
||||||
<div className="space-y-16 md:space-y-32 relative z-10">
|
<CapacityBlocks />
|
||||||
<Reveal>
|
<WebsitesSection />
|
||||||
<H3 className="text-3xl md:text-6xl lg:text-8xl leading-none tracking-tighter">
|
<BuiltAndRunning />
|
||||||
Sie kümmern sich um Ihr Geschäft.{" "}
|
<FinalCta />
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
@@ -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'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"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user