feat: German as the main language, English under /en, with a language switch

The site now exists in German (at the root, with /impressum and /datenschutz)
and English (/en). A request without a language follows the visitor's choice
from the language switch, then the browser language, then German; the old
English legal addresses move to /en for good.

All visible texts are data in src/content/de.ts and en.ts, so components hold
none of their own. The header carries a DE/EN switch whose bit curtain covers
the page while it is swapped and reopens at the same reading position. Title,
description, canonical, hreflang, Open Graph, the share image, the sitemap and
the structured data are per language. The privacy notice now describes the
cookie that remembers the chosen language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 13:07:22 +02:00
co-authored by Claude Opus 5.5
parent e89a248ca5
commit 75993100fa
72 changed files with 4418 additions and 1405 deletions
+22 -7
View File
@@ -25,22 +25,37 @@ 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 only "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 `/imprint` and `/privacy`. URLs of the previous site redirect to `/` (see `next.config.mjs`).
- Title, description and link previews come from `SITE_TITLE` and `SITE_DESCRIPTION` in `src/content/site.ts`.
The share image is a screenshot of the real hero: after changing the hero headline or its design, run
`pnpm --filter @mintel/web share-image` and commit the two PNGs in `app/(site)/`.
- 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 `/`
(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
its design, run `pnpm --filter @mintel/web share-image` and commit the PNGs in `src/assets/share/`.
- The email address is kept in parts (`src/content/site.ts`) and assembled in the browser by `EmailLink`.
Never write the full address into markup, metadata or copy.
- The hero design (binary-digit headline) is approved: do not change its design.
- Look at the result before reporting: take screenshots (desktop and mobile), do not ship blind.
## 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
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.
- Every visible text is data in `src/content/de.ts` and `en.ts` (type `Content` in `types.ts`). Components
hold no texts: server components take them from `contentFor(locale)`, client components from
`useContent()`. A new text goes into both files; German uses the formal "Sie".
- Never name a file `src/i18n.ts` or `i18n/request.ts`: `@mintel/next-config` would switch on next-intl.
- The privacy notice describes the language cookie; keep it true when the detection changes.
## Motion (binary micro-interactions)
- 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`. Reuse them instead of inventing new effects.
`BinaryTicker`, `ScrollBits`, `BitBurst`, `BitCurtain` (the language switch transition). 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`).