Files
mintel.me/apps/web/app/[locale]/freelance/page.tsx
T
mmintelandClaude Opus 5.5 f3797bfe47 feat: separate questions for clients and for teams
The homepage answered questions that only concern teams booking by the hour
(notice period and billing by effort, joining an existing team, what is out
of scope). Those are now on the freelance page. The homepage answers what a
client commissioning a website or automation asks: is it a problem that I am
not technical, what does it cost, who owns it, who looks after the website
afterwards, how do I reach you.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:08:48 +02:00

262 lines
9.2 KiB
TypeScript

import type { Metadata } from "next";
import { DecodeText } from "@/src/components/bits/DecodeText";
import { Block } from "@/src/components/home/Block";
import { ClosingCta } from "@/src/components/home/ClosingCta";
import { Faq } from "@/src/components/home/Faq";
import { BitAura } from "@/src/components/bits/BitAura";
import { PathPicker } from "@/src/components/home/PathPicker";
import { TopicButton } from "@/src/components/home/TopicButton";
import { ContactDiagram } from "@/src/components/illustrations/ContactDiagram";
import { PipelineRun } from "@/src/components/illustrations/PipelineRun";
import { Reveal } from "@/src/components/Reveal";
import { SwipeRow } from "@/src/components/SwipeRow";
import { contentFor } from "@/src/content";
import { DEFAULT_LOCALE, parseLocale } from "@/src/domain/locale";
import { languageAlternates, pathFor } from "@/src/domain/routes";
type Props = { readonly params: Promise<{ readonly locale: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const locale = parseLocale((await params).locale) ?? DEFAULT_LOCALE;
const { freelance } = contentFor(locale);
return {
title: { absolute: freelance.metaTitle },
description: freelance.metaDescription,
alternates: {
canonical: pathFor("freelance", locale),
languages: languageAlternates("freelance"),
},
};
}
const MUTED = "text-slate-400";
const BODY = "m-0 font-sans text-lg leading-relaxed text-slate-500 md:text-xl";
const LIST_TITLE = "m-0 font-sans text-xl font-bold tracking-tight md:text-2xl";
/**
* The page for people who develop themselves or lead a team: which projects
* fit, the tools, and how a change is secured before it goes live.
*/
export default async function FreelancePage({ params }: Props) {
const locale = parseLocale((await params).locale) ?? DEFAULT_LOCALE;
const content = contentFor(locale);
const { freelance, technology } = content;
const { booking } = freelance;
const { services } = content;
const { working } = content;
return (
<div className="relative flex flex-col bg-white">
<section className="bg-white">
<div className="mx-auto w-full max-w-[1400px] px-5 pb-4 pt-14 md:px-12 md:pb-10 md:pt-28">
<p className="m-0 font-mono text-[11px] uppercase tracking-[0.25em] text-slate-500">
{freelance.label}
</p>
<h1 className="m-0 mt-8 max-w-6xl font-sans text-4xl font-bold leading-[0.95] tracking-tighter text-slate-900 md:text-7xl lg:text-[6.5rem]">
{freelance.title.lead}{" "}
<span className={MUTED}>{freelance.title.rest}</span>
</h1>
<p className={`${BODY} mt-10 max-w-3xl md:mt-14`}>
{freelance.intro}
</p>
</div>
</section>
<Block
index="01"
label={technology.label}
title={
<>
{technology.title.lead}{" "}
<span className={MUTED}>{technology.title.rest}</span>
</>
}
>
<SwipeRow className="grid grid-cols-1 gap-12 md:grid-cols-3 md:gap-10">
{technology.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"}`}
>
<h3
className={`${LIST_TITLE} ${i === 2 ? "text-slate-400" : "text-slate-900"}`}
>
{group.title}
</h3>
<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>
))}
</SwipeRow>
<Reveal>
<div className="mt-12 rounded-3xl bg-slate-50 p-6 md:mt-24 md:p-10">
<h3 className={`${LIST_TITLE} text-slate-900`}>
{technology.techLabel}
</h3>
<div className="mt-6 grid grid-cols-1 gap-7 sm:grid-cols-2 md:mt-8 md:gap-10 lg:grid-cols-4">
{technology.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"
>
<DecodeText text={item} trigger="both" />
</li>
))}
</ul>
</div>
))}
</div>
</div>
</Reveal>
</Block>
<Block
index="02"
label={services.label}
title={
<>
{freelance.cases.lead}{" "}
<span className={MUTED}>{freelance.cases.rest}</span>
</>
}
>
<Reveal>
<PathPicker ids={["developer", "rescue"]} pricing="estimate" />
</Reveal>
</Block>
<Block
index="03"
label={freelance.pipelineLabel}
title={technology.pipelineTitle}
>
<Reveal>
<p className={`${BODY} mb-12 max-w-3xl`}>
{technology.pipelineIntro}
</p>
</Reveal>
<Reveal>
<PipelineRun />
</Reveal>
</Block>
<Block
index="04"
label={working.label}
title={
<>
{working.title.lead}{" "}
<span className={MUTED}>{working.title.rest}</span>
</>
}
>
<Reveal>
<BitAura source="bottom" tone="light" className="mb-16 md:mb-24">
<div className="overflow-hidden rounded-3xl bg-slate-900 p-4 md:p-10">
<ContactDiagram />
</div>
</BitAura>
</Reveal>
<SwipeRow className="grid grid-cols-1 gap-x-12 gap-y-12 md:grid-cols-2 md:gap-y-16">
{working.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>
))}
</SwipeRow>
</Block>
<Block
index="05"
label={booking.label}
title={
<>
{booking.title.lead}{" "}
<span className={MUTED}>{booking.title.rest}</span>
</>
}
>
<Reveal>
<div className="grid grid-cols-1 gap-12 md:grid-cols-12 md:gap-12">
<div className="md:col-span-6">
<p className={BODY}>{booking.text}</p>
<ul className="m-0 mt-8 list-none p-0">
{booking.terms.map((term) => (
<li
key={term}
className="border-t border-slate-200 py-4 font-sans text-lg text-slate-900 md:text-xl"
>
{term}
</li>
))}
</ul>
</div>
<div className="md:col-span-6">
<div className="rounded-3xl bg-slate-50 p-6 md:p-10">
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2">
{[booking.rate, booking.example].map((price) => (
<div key={price.label}>
<p className="m-0 font-sans text-base text-slate-500 md:text-lg">
{price.label}
</p>
<p className="m-0 mt-2 font-sans text-3xl font-black tracking-tighter text-slate-900 md:text-5xl">
{price.amount}
</p>
<p className="m-0 mt-2 font-mono text-[11px] uppercase tracking-[0.2em] text-slate-500">
{price.note}
</p>
</div>
))}
</div>
<div className="mt-10 border-t border-slate-200 pt-8">
<TopicButton topic="developer">{booking.cta}</TopicButton>
</div>
</div>
</div>
</div>
</Reveal>
</Block>
<Block
index="06"
label={content.faq.label}
title={
<>
{freelance.faq.title.lead}{" "}
<span className={MUTED}>{freelance.faq.title.rest}</span>
</>
}
>
<Reveal>
<Faq items={freelance.faq.items} />
</Reveal>
</Block>
<ClosingCta contact={content.contact} />
</div>
);
}