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>
315 lines
11 KiB
TypeScript
315 lines
11 KiB
TypeScript
import type { Metadata } from "next";
|
|
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 { 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: "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: "/" },
|
|
};
|
|
|
|
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 />
|
|
<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>
|
|
);
|
|
}
|