diff --git a/CLAUDE.md b/CLAUDE.md index ec92da4..1578a79 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,8 +76,9 @@ App lives in `apps/web`. Social media videos live in `apps/video` (Remotion, sta or person, with pins), `Track` (straight runs, 45 degree bends), `Pad` (solder point), `Bit` (what travels). New drawings are built from these, not from pills, curves and round dots. - Two audiences, two pages (owner, 2026-10-05). The homepage is for people who commission a result and have - no software of their own: a website or process automation (plus web presentations). It shows no prices - and no hourly terms, only "ask for an offer". Everything for companies that book by the hour is on the + no software of their own: a website or process automation (plus web presentations). It shows no project prices + and no hourly terms, only "ask for an offer"; the one price there is the care offer for websites (89 EUR + per month, see the concept, section 5). Everything for companies that book by the hour is on the freelance page only: the developer case, making an existing app reliable, how working together goes, project fit, tools, safeguards, rates and booking. Do not put any of that back on the homepage. - `DecodeText` is for monospaced text only; in proportional type the changing glyph widths make text jump. diff --git a/apps/web/plans/freelance-relaunch.md b/apps/web/plans/freelance-relaunch.md index 4392ce2..c566d36 100644 --- a/apps/web/plans/freelance-relaunch.md +++ b/apps/web/plans/freelance-relaunch.md @@ -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. diff --git a/apps/web/src/components/home/PathPicker.tsx b/apps/web/src/components/home/PathPicker.tsx index 82ac89c..ba1c7f4 100644 --- a/apps/web/src/components/home/PathPicker.tsx +++ b/apps/web/src/components/home/PathPicker.tsx @@ -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<{ + {pricing === "offer" && path.id === "website" && ( +
+

{care.label}

+
+
+

+ {care.title} +

+

+ {care.text} +

+
+
+

+ {care.price.label} +

+

+ {care.price.amount} +

+

{care.price.note}

+
+
+
+ {[ + { title: care.includedLabel, items: care.included }, + { title: care.termsLabel, items: care.terms }, + ].map((group) => ( +
+

+ {group.title} +

+
    + {group.items.map((item) => ( +
  • + {item} +
  • + ))} +
+
+ ))} +
+
+ )} + {pricing === "offer" ? (
diff --git a/apps/web/src/content/de.ts b/apps/web/src/content/de.ts index 32ed4ce..8c680d8 100644 --- a/apps/web/src/content/de.ts +++ b/apps/web/src/content/de.ts @@ -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?", diff --git a/apps/web/src/content/en.ts b/apps/web/src/content/en.ts index fce618d..139d590 100644 --- a/apps/web/src/content/en.ts +++ b/apps/web/src/content/en.ts @@ -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?", diff --git a/apps/web/src/content/types.ts b/apps/web/src/content/types.ts index b836b4d..f8af0e1 100644 --- a/apps/web/src/content/types.ts +++ b/apps/web/src/content/types.ts @@ -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. */