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:
@@ -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