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";
|
||||
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>
|
||||
);
|
||||
@@ -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