feat: freelance page, a story on the homepage, and design fixes

The homepage was too long and read as a list of sections. The technical
ones (project fit, tools, how a change is secured) move to a page of their
own, /freelance and /en/freelance, linked from the header, the footer and a
pointer on the homepage. The rest is reordered into one story (who, what you
need, examples, how working together goes, results, questions, contact) and
each section opens with a sentence that leads over from the one before.

routes.ts: a page may now have the same name in both languages. Before,
/en/<german name> was always moved to the English name, which for such a
page would have redirected the address onto itself.

Design: list titles such as "Passt gut" are real headings instead of small
grey labels. The automation examples, their description and the run are one
box, so it is clear they belong together. Digits on light backgrounds are
the blue of the panels instead of black. The square corner marks on the
rounded service cards are gone. The years in the career line are readable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 16:34:38 +02:00
co-authored by Claude Opus 5.5
parent 11e991e54a
commit c70833a725
17 changed files with 442 additions and 190 deletions
+13 -7
View File
@@ -25,9 +25,14 @@ App lives in `apps/web`. Concept and scope: `apps/web/plans/freelance-relaunch.m
- Never mention AI as the owner's tool, and no internal method vocabulary (specs, TDD, mutation, coverage,
pull requests). The automation service may name AI as part of what the client gets. Plain language.
- Modest and factual: no promises the owner did not confirm, no superlatives. See the concept, section 1.
- One page: everything lives on the homepage. The header has the language switch and "Get in touch", which
jumps to the contact section (`#contact`). Contact is one email address: no calendar integration, no form.
The only other pages are the imprint and the privacy notice. URLs of the previous site redirect to `/`
- Two content pages. The homepage tells one story from top to bottom (who, what you need, examples, how
working together goes, results, questions, contact); each section opens with a `bridge` sentence from
`story` in the content files that leads over from the one before. The technical sections (project fit,
tools, how a change is secured) live on `/freelance` (`/en/freelance`), for people who develop themselves
or lead a team. Do not move technical detail back onto the homepage.
- The header has the link to the freelance page, the language switch and "Get in touch", which jumps to the
contact section (`#contact`). Contact is one email address: no calendar integration, no form. The only
other pages are the imprint and the privacy notice. URLs of the previous site redirect to `/`
(see `next.config.mjs`).
- Title, description and link previews come from `site` in `src/content/de.ts` and `en.ts`.
The share image is a screenshot of the real hero, one per language: after changing the hero headline or
@@ -39,8 +44,8 @@ App lives in `apps/web`. Concept and scope: `apps/web/plans/freelance-relaunch.m
## Two languages (German is the main one)
- German lives at the root (`/`, `/impressum`, `/datenschutz`), English under `/en` (`/en/imprint`,
`/en/privacy`). The app directory renders both under `app/[locale]`; `proxy.ts` maps the public addresses
- German lives at the root (`/`, `/freelance`, `/impressum`, `/datenschutz`), English under `/en`
(`/en/freelance`, `/en/imprint`, `/en/privacy`). The app directory renders both under `app/[locale]`; `proxy.ts` maps the public addresses
onto it. All routing and detection rules are pure functions in `src/domain/routes.ts` and `locale.ts`.
- Detection: an address without a language follows the `lang` cookie (set only by the language switch), then
the browser's `Accept-Language`, then German. `/en/...` is never redirected.
@@ -54,8 +59,9 @@ App lives in `apps/web`. Concept and scope: `apps/web/plans/freelance-relaunch.m
- Building blocks live in `apps/web/src/components/bits/`: `DecodeText` (mono labels resolving from digits),
`BitWipe` (headline revealed behind a column of digits), `BitReveal` (picture assembling from bits),
`BinaryTicker`, `ScrollBits`, `BitBurst`, `BitCurtain` (the language switch transition). Reuse them
instead of inventing new effects.
`BinaryTicker`, `ScrollBits`, `BitBurst`, `BitCurtain` (the language switch transition), `BitAura` (a lit
edge that sheds digits; glow only on dark surfaces, on light ones just a few blue digits), `BitSeam`,
`BitGlow`. Reuse them instead of inventing new effects.
- `DecodeText` is for monospaced text only; in proportional type the changing glyph widths make text jump.
- Never put an in-view trigger on an element that clips itself away (clip-path): a fully clipped element
never counts as visible. Observe an unclipped wrapper (see `BitWipe`).