feat: rebuild mintel.me as a single-page freelance site
The site now presents one freelance developer on one page: hero, introduction, four typical cases (developer for a product, make an app reliable, automate a process, a website) with what you get, how it runs and what it costs, a process automation section, technology and project fit, how I work, two live references, questions and contact. - Hero and closing headline are drawn from running binary digits. - Each case explains its four steps with an animated drawing; the automation section shows example workflows with work travelling between systems; a pipeline run shows what a test catches before a release. - Contact is one email address, kept in parts and assembled in the browser so it never appears in the page source (src/domain/email.ts, spec first). - Imprint and privacy pages added. - The old site (blog, case studies, contact form, fixed-price pages, video rendering and related scripts) is removed. Its URLs redirect to the homepage. - The deploy smoke test checks the legal pages and the redirects instead of the removed case study. - Dev scripts use docker compose v2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+303
-33
@@ -1,44 +1,314 @@
|
||||
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 { 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 { 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";
|
||||
|
||||
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.";
|
||||
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,
|
||||
type WorkItem,
|
||||
} from "@/src/components/illustrations/WorkShowcase";
|
||||
import { Reveal } from "@/src/components/Reveal";
|
||||
import { CAREER, FIT, TECH } from "@/src/content/about";
|
||||
import { AUTOMATION_NOTES } from "@/src/content/automation";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: { absolute: TITLE },
|
||||
description: DESCRIPTION,
|
||||
title: "Marc Mintel — Senior developer for web apps and websites",
|
||||
description:
|
||||
"Freelance developer with 15 years of experience. I build web apps, interfaces and websites: by the hour for product teams, or a complete website for your business.",
|
||||
alternates: { canonical: "/" },
|
||||
openGraph: {
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
url: "/",
|
||||
type: "website",
|
||||
siteName: SITE_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
export default function HomePage() {
|
||||
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 PRINCIPLES = [
|
||||
{
|
||||
title: "One contact",
|
||||
text: "You talk to me, and I am the one who does the work.",
|
||||
},
|
||||
{
|
||||
title: "Hours you can check",
|
||||
text: "I track my time. The monthly invoice shows what the hours were spent on.",
|
||||
},
|
||||
{
|
||||
title: "Careful work",
|
||||
text: "I test what I build before I hand it over, and I say so when something is not finished or I am not sure.",
|
||||
},
|
||||
{
|
||||
title: "What I build is yours",
|
||||
text: "Code and design belong to you. You can hand them to another developer.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const WORK: readonly WorkItem[] = [
|
||||
{
|
||||
name: "klz-cables.com",
|
||||
href: "https://klz-cables.com",
|
||||
client: "Cable supplier",
|
||||
text: "The company website of a supplier of energy cables, in German and English.",
|
||||
did: [
|
||||
"Product catalogue with categories and detail pages",
|
||||
"Datasheets generated from the product data",
|
||||
"Content the client can edit in a CMS",
|
||||
],
|
||||
tech: ["Next.js", "TypeScript", "Headless CMS", "Two languages"],
|
||||
desktop: { src: "/work/klz-long.webp", width: 1200, height: 1680 },
|
||||
mobile: { src: "/work/klz-mobile.webp", width: 390, height: 3600 },
|
||||
},
|
||||
{
|
||||
name: "e-tib.com",
|
||||
href: "https://e-tib.com",
|
||||
client: "Cable network construction",
|
||||
text: "The company website of a group in cable network construction, in German and English.",
|
||||
did: [
|
||||
"Pages for services, references, careers and events",
|
||||
"The same content in two languages",
|
||||
"Works on phone, tablet and desktop",
|
||||
],
|
||||
tech: ["Next.js", "TypeScript", "Headless CMS", "Two languages"],
|
||||
desktop: { src: "/work/etib-long.webp", width: 1200, height: 4333 },
|
||||
mobile: { src: "/work/etib-mobile.webp", width: 390, height: 3600 },
|
||||
},
|
||||
];
|
||||
|
||||
export default function LandingPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex flex-col bg-white">
|
||||
<HomeJsonLd />
|
||||
<Hero />
|
||||
<ProofStrip />
|
||||
<HowIShip />
|
||||
<WhatIDo />
|
||||
<CapacityBlocks />
|
||||
<WebsitesSection />
|
||||
<BuiltAndRunning />
|
||||
<FinalCta />
|
||||
</>
|
||||
<HeroStatement />
|
||||
<StickyBook />
|
||||
|
||||
<PortraitSection
|
||||
id="hello"
|
||||
index="01"
|
||||
label="Hello"
|
||||
title={<>I am Marc.</>}
|
||||
>
|
||||
<p className="m-0 font-sans text-2xl leading-snug text-white md:text-4xl">
|
||||
I have been building for the web for 15 years: first in agencies,
|
||||
later as a senior developer, and now on my own.
|
||||
</p>
|
||||
<p className="m-0 mt-8 font-sans text-lg leading-relaxed text-slate-300 md:text-xl">
|
||||
My strength is the part people see and use: design and interface. I am
|
||||
also at home with what lies behind it, so I can build a complete
|
||||
application and not just its surface.
|
||||
</p>
|
||||
<p className="m-0 mt-6 font-sans text-lg leading-relaxed text-slate-300 md:text-xl">
|
||||
You work with me directly. Whether you run a product team or are
|
||||
commissioning something like this for the first time.
|
||||
</p>
|
||||
</PortraitSection>
|
||||
|
||||
<section className="bg-white">
|
||||
<div className="mx-auto w-full max-w-[1400px] px-5 py-16 md:px-12 md:py-28">
|
||||
<p className={`${MONO} m-0 mb-10`}>The path so far</p>
|
||||
<CareerLine stations={CAREER} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Block
|
||||
index="02"
|
||||
label="Services"
|
||||
title={
|
||||
<>
|
||||
What do you need? <span className={MUTED}>Four typical cases.</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Reveal>
|
||||
<PathPicker />
|
||||
</Reveal>
|
||||
</Block>
|
||||
|
||||
<Block
|
||||
index="03"
|
||||
label="Process automation"
|
||||
title={
|
||||
<>
|
||||
Let the systems <span className={MUTED}>do the copying.</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Reveal>
|
||||
<p className={`${BODY} mb-12 max-w-3xl`}>
|
||||
In most companies, people move information from one tool to another:
|
||||
read it here, type it in there, send an email about it. Automation
|
||||
connects those tools so that the work passes from one to the next on
|
||||
its own. Three typical examples:
|
||||
</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"
|
||||
title={
|
||||
<>
|
||||
Which projects <span className={MUTED}>I am right for.</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-12 md:grid-cols-3 md:gap-10">
|
||||
{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`}>What I work with</p>
|
||||
<div className="mt-8 grid grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{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"
|
||||
>
|
||||
{item}
|
||||
</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">
|
||||
What happens before a change goes live.
|
||||
</h3>
|
||||
<p className={`${BODY} mb-12 mt-6 max-w-3xl`}>
|
||||
Software is connected: a change in one place can break another. I
|
||||
secure what I build with automated tests, and every change has to
|
||||
pass them before it is released. This is what such a run looks
|
||||
like.
|
||||
</p>
|
||||
</Reveal>
|
||||
<Reveal>
|
||||
<PipelineRun />
|
||||
</Reveal>
|
||||
</div>
|
||||
</Block>
|
||||
|
||||
<Block
|
||||
index="05"
|
||||
label="Working with me"
|
||||
title={
|
||||
<>
|
||||
How I work. <span className={MUTED}>Nothing unusual.</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Reveal>
|
||||
<div className="mb-16 overflow-hidden rounded-3xl bg-slate-900 p-4 md:mb-24 md:p-10">
|
||||
<ContactDiagram />
|
||||
</div>
|
||||
</Reveal>
|
||||
<div className="grid grid-cols-1 gap-x-12 gap-y-12 md:grid-cols-2 md:gap-y-16">
|
||||
{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="My work"
|
||||
title={
|
||||
<>
|
||||
Two sites I built. <span className={MUTED}>Both are live.</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<WorkShowcase items={WORK} />
|
||||
<Reveal>
|
||||
<p
|
||||
className={`${BODY} mt-20 max-w-3xl border-t border-slate-200 pt-8 md:mt-28`}
|
||||
>
|
||||
Besides client work I build my own software, among other things a
|
||||
system in Rust and Solidity that runs on seven blockchains.
|
||||
</p>
|
||||
</Reveal>
|
||||
</Block>
|
||||
|
||||
<Block
|
||||
index="07"
|
||||
label="Questions"
|
||||
title={
|
||||
<>
|
||||
Good to know <span className={MUTED}>before we talk.</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Reveal>
|
||||
<Faq />
|
||||
</Reveal>
|
||||
</Block>
|
||||
|
||||
<ClosingCta />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user