feat: care offer for websites, with its price and its limits

What happens after a website is live was one vague sentence about a monthly
fee to be agreed. It is now one clear offer in the website case and in the
questions: 89 EUR per month, cancel monthly, the site runs on servers in
Germany, small changes by email are included, no editing system and no login.
The limits are said as plainly as what is included: the first three months
are part of the project price, anything bigger gets a price first, replies
within two working days, no on-call, and the client can leave with everything.

The decision and the parts that are not on the website (the larger variant,
CMS as an exception, how the sites are served) are in the concept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 12:21:46 +02:00
co-authored by Claude Opus 5.5
parent 7a71296e6d
commit af66f61c26
6 changed files with 129 additions and 8 deletions
+14 -1
View File
@@ -88,7 +88,20 @@ Audit of an AI-built codebase, test foundation, mutation baseline, prioritized r
- Sprint: time-boxed package (example 40 h, 3,800 EUR) with a clear scope list. Extra work is a follow-up block, not open scope. 50% at start, 50% at launch.
- Site: static Next.js, no CMS, no server, no database.
- Hosting: managed platform (Cloudflare Pages or Vercel) in my account, flat monthly subscription (example 49-99 EUR) including hosting, SSL, domain renewal at cost, 1 h of changes per month. Deployment: git push builds automatically, client approves via a preview URL by email.
- Care after the launch (owner decision 2026-10-05, replaces the managed-platform idea): one offer,
"Betreuung", 89 EUR per month net, cancel monthly. Hosting on the owner's own servers in Germany (EU
hosting matters to German clients; no Vercel or Cloudflare), domain, security and updates, and small
changes by email (a text, a picture, opening hours; internally about 30 minutes a month). No CMS and no
login for the client. The first three months are included in the project price. Anything bigger (a new
page, a new feature) gets a price first. Reply within two working days, no on-call. The client can leave
and take everything along.
- Larger clients: the same care with all non-editorial changes included, about 250 EUR per month (one
existing client pays 3,000 EUR a year). Offered in the quote, not listed on the website.
- CMS only as an exception and at extra cost (file-based first; Payload only when the site lives on
maintained data such as a product catalogue).
- Operations: serve client sites as static files from one shared web server, not one container per client.
- Not on the website yet: "green hosting". The owner wants it as an argument; state it only once the
provider's proof (renewable energy) is at hand.
- Changes: by email or message, no CMS training.
- Domain: client is the registrant; transfer-out and source handover on request.
- **Email and DNS are out of scope.** Never switch nameservers. Only the two records `A/CNAME` for apex and `www` are changed, after exporting the existing records, and mail delivery is checked afterwards. Mailboxes stay with the client's provider.
+48 -1
View File
@@ -153,7 +153,7 @@ export const PathPicker: React.FC<{
pricing: "offer" | "estimate";
}> = ({ ids, pricing }) => {
const { services } = useContent();
const { estimate } = services;
const { estimate, care } = services;
const paths = ids.flatMap((id) =>
services.paths.filter((path) => path.id === id),
);
@@ -251,6 +251,53 @@ export const PathPicker: React.FC<{
<PathStory path={path} active={selected} />
</div>
{pricing === "offer" && path.id === "website" && (
<div className="mt-12 rounded-3xl border border-slate-200 p-6 md:mt-16 md:p-10">
<p className={`${MONO} m-0`}>{care.label}</p>
<div className="mt-6 grid grid-cols-1 gap-10 md:grid-cols-12 md:gap-12">
<div className="md:col-span-7">
<p className="m-0 font-sans text-2xl font-bold leading-tight tracking-tight text-slate-900 md:text-4xl">
{care.title}
</p>
<p className="m-0 mt-4 max-w-2xl font-sans text-base leading-relaxed text-slate-500 md:text-lg">
{care.text}
</p>
</div>
<div className="md:col-span-5">
<p className="m-0 font-sans text-base text-slate-500 md:text-lg">
{care.price.label}
</p>
<p className="m-0 mt-2 font-sans text-3xl font-black tracking-tighter text-slate-900 md:text-5xl">
{care.price.amount}
</p>
<p className={`${MONO} m-0 mt-2`}>{care.price.note}</p>
</div>
</div>
<div className="mt-10 grid grid-cols-1 gap-10 border-t border-slate-200 pt-8 md:grid-cols-2 md:gap-12">
{[
{ title: care.includedLabel, items: care.included },
{ title: care.termsLabel, items: care.terms },
].map((group) => (
<div key={group.title}>
<p className="m-0 font-sans text-lg font-bold tracking-tight text-slate-900 md:text-xl">
{group.title}
</p>
<ul className="m-0 mt-3 list-none p-0">
{group.items.map((item) => (
<li
key={item}
className="border-b border-slate-100 py-3 font-sans text-base leading-relaxed text-slate-600 md:text-lg"
>
{item}
</li>
))}
</ul>
</div>
))}
</div>
</div>
)}
{pricing === "offer" ? (
<div className="mt-12 flex flex-col gap-8 rounded-3xl bg-slate-50 p-6 md:mt-16 md:flex-row md:items-center md:justify-between md:gap-12 md:p-10">
<div className="max-w-2xl">
+26 -2
View File
@@ -78,6 +78,30 @@ export const de: Content = {
tablistLabel: "Was brauchen Sie?",
youGet: "Das bekommen Sie",
howItRuns: "So läuft es ab",
care: {
label: "Nach dem Start",
title: "Ich kümmere mich weiter um Ihre Website, wenn Sie das möchten.",
text: "Sie müssen kein System bedienen und nichts selbst pflegen. Wenn etwas zu ändern ist, schreiben Sie mir eine E-Mail.",
price: {
label: "Betreuung",
amount: "89 EUR",
note: "im Monat, netto, monatlich kündbar",
},
includedLabel: "Das ist enthalten",
included: [
"Die Website läuft auf Servern in Deutschland",
"Adresse, Sicherheit und Technik sind erledigt",
"Kleine Änderungen: ein Text, ein Bild, neue Öffnungszeiten",
"Kein Redaktionssystem, kein Login",
],
termsLabel: "Gut zu wissen",
terms: [
"Die ersten drei Monate sind im Projektpreis enthalten.",
"Größeres, etwa eine neue Seite, bekommt vorher einen Preis.",
"Ich antworte innerhalb von zwei Werktagen. Eine Rufbereitschaft gibt es nicht.",
"Wenn Sie gehen möchten, bekommen Sie alles mit. Die Website gehört Ihnen.",
],
},
offer: {
title: "Sie bekommen ein Angebot, bevor etwas kostet.",
text: "Schreiben Sie mir kurz, was Sie brauchen. Wir sprechen etwa 20 Minuten, danach bekommen Sie ein schriftliches Angebot. Ich fange erst an, wenn Sie zugestimmt haben.",
@@ -252,7 +276,7 @@ export const de: Content = {
],
orientation: "Ab 3.800 EUR, einmalig",
orientationNote:
"Das gilt für eine typische Unternehmenswebsite. Hosting und kleine Änderungen danach kosten einen monatlichen Betrag, den wir vor dem Start vereinbaren.",
"Das gilt für eine typische Unternehmenswebsite. Die Betreuung danach kostet 89 EUR im Monat.",
},
],
scenes: {
@@ -769,7 +793,7 @@ export const de: Content = {
{
question: "Wer kümmert sich danach um die Website?",
answer:
"Ich, wenn Sie das möchten. Hosting und kleine Änderungen danach kosten einen monatlichen Betrag, den wir vor dem Start vereinbaren. E-Mail-Postfächer bleiben bei Ihrem Anbieter.",
"Ich, wenn Sie das möchten. Für 89 EUR im Monat läuft die Website auf Servern in Deutschland, und kleine Änderungen sind enthalten: Sie schreiben mir eine E-Mail. Ein Redaktionssystem brauchen Sie nicht. E-Mail-Postfächer bleiben bei Ihrem Anbieter.",
},
{
question: "Wie erreiche ich Sie?",
+26 -2
View File
@@ -77,6 +77,30 @@ export const en: Content = {
tablistLabel: "What do you need?",
youGet: "What you get",
howItRuns: "How it runs",
care: {
label: "After the launch",
title: "I keep looking after your website, if you want that.",
text: "You do not have to operate a system or maintain anything yourself. When something needs to change, you write me an email.",
price: {
label: "Care",
amount: "89 EUR",
note: "per month, net, cancel monthly",
},
includedLabel: "What is included",
included: [
"The website runs on servers in Germany",
"Address, security and technology are taken care of",
"Small changes: a text, a picture, new opening hours",
"No editing system, no login",
],
termsLabel: "Good to know",
terms: [
"The first three months are included in the project price.",
"Anything bigger, such as a new page, gets a price first.",
"I reply within two working days. There is no on-call service.",
"If you want to leave, you take everything with you. The website is yours.",
],
},
offer: {
title: "You get an offer before anything costs money.",
text: "Write me briefly what you need. We talk for about 20 minutes, then you get a written offer. I only start once you have agreed.",
@@ -248,7 +272,7 @@ export const en: Content = {
],
orientation: "From 3,800 EUR, one-off",
orientationNote:
"This is for a typical company website. Hosting and small changes afterwards cost a monthly fee that we agree on before we start.",
"This is for a typical company website. Care afterwards costs 89 EUR per month.",
},
],
scenes: {
@@ -749,7 +773,7 @@ export const en: Content = {
{
question: "Who looks after the website afterwards?",
answer:
"I do, if you want that. Hosting and small changes afterwards cost a monthly fee that we agree on before we start. Email inboxes stay with your provider.",
"I do, if you want that. For 89 EUR per month the website runs on servers in Germany, and small changes are included: you write me an email. You do not need an editing system. Email inboxes stay with your provider.",
},
{
question: "How do I reach you?",
+12
View File
@@ -188,6 +188,18 @@ export type Content = {
readonly tablistLabel: string;
readonly youGet: string;
readonly howItRuns: string;
/** What happens to a website after it is live: the care offer, with its price. */
readonly care: {
readonly label: string;
readonly title: string;
readonly text: string;
readonly price: PathPrice;
readonly includedLabel: string;
readonly included: readonly string[];
readonly termsLabel: string;
/** The limits, said plainly. */
readonly terms: readonly string[];
};
/** The homepage's way to a price: ask for an offer. No figures there. */
readonly offer: TitledText & { readonly cta: string };
/** How a visitor gets to a price: the homepage shows this instead of a price list. */