Files
Anelia-Wolf/docs/superpowers/plans/2026-09-02-anelia-wolf-build-plan.md
2026-09-06 17:21:52 +02:00

179 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Anelia Wolf Build-Plan & Komponenten-Vertrag
Stand: 2026-09-02 · Grundlage: `docs/superpowers/specs/2026-09-02-anelia-wolf-website-design.md`
(Abschnitt A = verbindliche Abweichungen, danach die Jury-Synthese). Dieser Plan ist der Vertrag,
nach dem parallel arbeitende Agenten bauen. **Pfade, Exportnamen und Props sind verbindlich.**
## 0. Bereits vorhanden (nicht neu anlegen, nur erweitern)
- `next.config.ts` (static export, trailingSlash), `src/app/globals.css` (Tokens, `.grain`, `.gold-text`,
`.rule-gold`, `[data-theme]`), `src/app/layout.tsx` (Fonts Jost/DM Mono/Bodoni Moda → `--font-jost`,
`--font-mono-face`, `--font-bodoni`; Tailwind `font-sans`/`font-mono`/`font-display`; Skip-Link; `SmoothScroll`).
- `src/lib/site.ts` (`site`, `nav`, `legal`), `src/lib/utils.ts` (`cn`), `src/hooks/usePrefersReducedMotion.ts`,
`src/components/providers/SmoothScroll.tsx` (Lenis + GSAP-Ticker, `window.lenis`).
- `src/components/brand/WolfMark.tsx` (SVG-Näherung des Monogramms; Klassen `.wolfmark-v`, `.wolfmark-wolf`,
Pfade mit `data-draw` + `pathLength="1"`, Gradient-Id `${idPrefix}wolf-gold`; Props `className`, `title`,
`idPrefix`, `strokeWidth`). **Nicht verändern.**
- `public/brand/README.md`, `public/photos/README.md` (Drop-in-Slots), `.claude/launch.json` (`anelia-dev`, Port 3000).
Pakete: next 16.3, react 19.2, tailwindcss 4.3, motion 13 (`motion/react`), gsap 3.15 (`gsap/ScrollTrigger`,
`gsap/SplitText`, `gsap/DrawSVGPlugin` alle frei), lenis 1.3, clsx, tailwind-merge. Nichts weiteres installieren
außer `server-only` (falls gewünscht).
Tailwind v4: Utilities aus den Tokens: `bg-ink bg-ink-2 bg-ivory bg-bone text-gold text-gold-deep text-grey-warm
text-grey-deep border-line-dark border-line-light` und Live-Tokens `bg-bg text-fg text-fg-muted text-accent
border-line bg-surface`. Schrift: `font-sans` (Jost), `font-mono` (DM Mono), `font-display` (Bodoni Moda).
## 1. Globale Regeln für alle Agenten
1. Next 16 App Router, statischer Export: Server-Komponenten standardmäßig; `"use client"` nur wo Hooks/DOM.
Keine `params`/`searchParams`, keine Route Handler, keine `fetch` zur Laufzeit, kein `localStorage`/`sessionStorage`/
`document.cookie`, keine externen Skripte/iframes/Fonts. Externe Links `target="_blank" rel="noopener noreferrer"`.
2. Alle GSAP-Nutzung in `useEffect`/`useLayoutEffect` mit `gsap.context()` + `ctx.revert()` im Cleanup; Plugins
nur über `src/lib/gsap.ts`. `gsap.matchMedia()` mit Bedingungen `reduce: "(prefers-reduced-motion: reduce)"`,
`desktop: "(min-width: 1024px) and (pointer: fine)"`.
3. Reduced motion: Endzustand sofort sichtbar. Ohne JS: Inhalte sichtbar (Initialzustände nur unter `html.js`
setzen `layout.tsx` fügt ein 1-Zeilen-Inline-Script hinzu, das `document.documentElement.classList.add("js")` setzt).
4. Bodoni Moda nie < 28 px; Gold-Text (`text-gold`, `.gold-text`) nur in `data-theme="dark"`; auf hell `text-gold-deep`.
Radius 0 überall, keine Schatten, kein Bold (max. 500), ein Italic-Wort pro Headline (`<em>` in `font-display italic`).
5. Alle Texte kommen aus `src/content/*.ts` kein Copy in JSX. Deutsche Anführungszeichen „…“, Gedankenstrich „–“.
6. Ein `<h1>` pro Seite, Landmarken (`header`, `main#inhalt`, `footer`), Alt-Texte, sichtbarer Fokus.
7. Jeder Agent arbeitet NUR in den ihm zugewiesenen Dateien. Fremde Dateien werden importiert, nicht editiert.
Am Ende `npx tsc --noEmit`; Fehler in eigenen Dateien beheben. Fehlende Module aus dem Vertrag (noch nicht
gebaut) sind der einzige erlaubte Fehlertyp und werden gemeldet.
8. Kein `next dev`/`next build` durch Foundation-/Page-Agenten (nur `tsc`). Build und Browser-QA übernehmen die
Integrations-/QA-Agenten.
## 2. Komponenten-Vertrag
### 2.1 `src/lib/gsap.ts` (F1)
```ts
export { gsap, ScrollTrigger, SplitText, DrawSVGPlugin }; // registriert, SSR-sicher (typeof window guard)
export const EASE = { out: "expo.out", inOut: "power3.inOut", none: "none" } as const;
export const DUR = { micro: 0.3, reveal: 0.6, lines: 0.9, door: 1.1, draw: 1.2, sheen: 0.8, rule: 0.9 } as const;
export const MM = { reduce: "(prefers-reduced-motion: reduce)", desktop: "(min-width: 1024px) and (pointer: fine)", touch: "(pointer: coarse)" } as const;
```
### 2.2 `src/lib/site.ts` (F2 erweitert)
```ts
export const flags = { showPlaceholders: true, showFaq: true, showStimmen: false, showSpeakerPdf: false,
showSecondTalk: false, show24hPromise: false, showIndustry: false, showTwins: true } as const;
export const routes = { home: "/", coaching: "/leistungen/coaching/", trainings: "/leistungen/trainings/",
keynotes: "/leistungen/keynotes/", impressum: "/impressum/", datenschutz: "/datenschutz/" } as const;
export const mailto = { kennenlernen: "mailto:info@anelia-wolf.com?subject=Kennenlerngespr%C3%A4ch",
training: "mailto:info@anelia-wolf.com?subject=Training%20anfragen", keynote: "mailto:info@anelia-wolf.com?subject=Keynote%20anfragen" } as const;
```
### 2.3 `src/lib/assets.ts` (F2, nur in Server-Komponenten importieren)
```ts
export function hasPublicFile(relPath: string): boolean; // fs.existsSync(path.join(process.cwd(), "public", relPath))
export function getPhotos(): { hero: string | null; portrait: string | null; stage: string | null };
export function getLogoSvg(): string | null; // Inhalt von public/brand/logo-mark.svg oder null
```
### 2.4 `src/lib/legal.ts` (F2)
```ts
export const LEGAL_TODO: string[]; // offene Pflichtangaben
export function ph(label: string): string; // liefert "[label]" Platzhalter-Text
```
### 2.5 Content (F2) `src/content/*.ts`, alle `as const`, Typen exportieren
- `home.ts`: `hero`, `positioning`, `services`, `principle`, `about`, `audience`, `contact` (+ `termine`) Copy 1:1 aus Spec §6.
- `coaching.ts`, `trainings.ts`, `keynotes.ts`: `{ meta: {title, description}, masthead, eyebrow, h1: {before, em, after},
subline, cta: {label, href, suffix?, micro?}, facts: {label, value}[], situations: {h2, rows[]}, questions?, modules,
rahmen, ablauf, faq, band: {h2, cta, micro} , ... }` Copy 1:1 aus Spec §7.
- `legal.ts`: Impressum- und Datenschutz-Abschnitte (`{ h2, paragraphs[] }[]`) aus `docs/superpowers/specs/legal-templates.md`,
Platzhalter über `ph()`; kein OS-Plattform-Link; „Bezeichnung WOLF-Prinzip“.
### 2.6 Motion-Primitive (F1) `src/components/motion/`
```tsx
<Rule variant?: "gold" | "line" = "line"; origin?: "left" | "center" | "right" | number /* px vom linken Rand */;
play?: "view" | "load" = "view"; delay?: number; className?: string /> // 1px, scaleX 0→1, once
<Lines as?: "h1"|"h2"|"h3"|"p"|"div"; play?: "view" | "load"; delay?; stagger?; className?>children</Lines>
// SplitText lines + mask; wartet document.fonts.ready (Timeout 1.5s); reduce → statisch; setzt aria-label
<Reveal as?; play?: "view"|"load"; delay?; y?=16; className?>children</Reveal> // motion whileInView, once
<RevealGroup stagger?=0.06 delay? className?><RevealItem as? className?>…</RevealItem></RevealGroup>
<Draw play?: "load" | "view" | "scrub"; delay?; duration?; sheen?: boolean; onComplete?; className?>{svg}</Draw>
// animiert alle [data-draw]-Pfade im Kind (stroke-dasharray/offset, pathLength=1); scrub: ScrollTrigger auf eigenem Element
<Door className? parallax?: boolean; priority?: boolean; src alt width height sizes? /> // <img> in clip-path-Maske, scale 1.06→1, 6% Parallax desktop
<ThemeTween /> // einmal pro Seite (template.tsx): tweent body-Variablen --bg --fg --fg-muted --accent --line --surface an [data-theme]-Grenzen (start "top 70%", end "top 30%", scrub 0.4)
<ScrollProgress /> // 1px --accent Linie unter dem Header
<Magnet strength?=0.3 className?>children</Magnet> // nur desktop+no-preference, ref + ein rAF
<TransitionLink href label? className? …LinkProps /> // Vorhang + router.push; Modifier/extern/Anker → normal
<Curtain /> // einmal in layout.tsx; hört auf transition-store
// src/components/motion/transition-store.ts: startTransition(href, label), subscribe(cb)
```
### 2.7 Brand (F1) `src/components/brand/`
```tsx
<BrandMark className? idPrefix? variant?: "full" | "v-only" /> // Server: getLogoSvg() ? inline SVG (Pfade bekommen data-draw, Klassen wie WolfMark) : <WolfMark/>
<DrawMark play? sheen? handoff?: boolean className?>{<BrandMark/>}</DrawMark> // Client-Hülle um BrandMark: Draw-Sequenz V-links → V-rechts → Wolf → (handoff-Linie nach unten) → Sheen
<Wordmark size?: "nav" | "footer"; href?="/" ; className? /> // HTML-Wordmark ANELIA WOLF + Regel + Unterzeile
```
### 2.8 Layout (F1) `src/components/layout/`
```tsx
<Header /> // Client; fixed; Zeile 1 Masthead (≥1024, kollabiert nach 80px), Zeile 2 Logo | 3 Nav-Links | Kontakt + Button (≥1280) | Menü (<1024)
<Masthead center: string; className? /> // drei Slots (Rolle | center | „Deutsch & Englisch“), Hairlines oben/unten
<OverlayMenu open onClose /> // Fullscreen, nummerierter Ledger, Fokus-Trap, Esc, inert
<Footer /> // Server; identisch auf allen Seiten
<Section id theme: "dark"|"light"; numeral?: string; total?: string; label?: string; className?; as?>children</Section>
// <section data-theme id> mit Container + 12er-Grid; sticky Mono-Ziffer links (≥1024) bzw. Zeile oben (<1024); Kinder im Grid (col-span über className)
<Container className?>children</Container> // max-w-[105rem] px-[clamp(20px,5vw,80px)]
<Folio current total next?: {label, href} className? /> // Hairline + „01 / 06“ + „Positionierung ↓“
```
`src/app/template.tsx` (F1): `motion.main#inhalt` opacity/y-Enter 0.6s, `onAnimationComplete → ScrollTrigger.refresh()`, mountet `<ThemeTween/>`, scrollt Lenis auf 0 bei Pfadwechsel.
`src/app/layout.tsx` (F1 erweitert): Inline-Script `html.js`, `<Header/>`, `{children}`, `<Footer/>`, `<Curtain/>`, `<ScrollProgress/>`, `MotionConfig reducedMotion="user"`.
### 2.9 UI / Editorial (F2) `src/components/ui/`
```tsx
<Eyebrow className?>text</Eyebrow> // Jost 500 caps ls .28em text-accent, 24px-Tick ::before
<Numeral value="01" className? /> // Bodoni 400 opsz 96, .gold-text auf dark / text-gold-700 auf light
<Marginalia items: {label: string; value: string; href?: string}[] className? />
<Ledger variant: "toc"|"werdegang"|"termine"|"situations"|"goals"; rows: LedgerRow[]; className? />
// toc-Row: {numeral, title, oneLiner, keywords[], link:{label, href}}; werdegang: {keyword, text}; termine: {date, format, title, venue, href?};
// situations: {numeral, text}; goals: {text}. toc-Zeile = eine <TransitionLink>; Hover ohne Reflow.
<ModuleTable modules: {title, goal, contents[], audience, meta}[] /> // Kopfzeile + <details> pro Modul, + → × per CSS
<SpecTable blocks: {title, goal, contents, audience, meta, tracks?: string[]}[] />
<Facts items: {label, value}[] className? /> // Mono-Stack mit Goldticks (Hero rechts oben)
<FactsStrip items: {label, value}[] /> // volle Breite, vertikale Hairlines (Speaker-Facts)
<Faq items: {q, a}[] /> // native details/summary
<Steps items: {numeral, title, text}[] />
<ContactLinks /> // Schreib mir / Ruf an / Vernetzen (aus site.ts)
<Columns items: {label, title, lines[], link:{label, href}}[] offsetMiddle? />
<Formula lines: {op?: "+"|"=", text, em?: string, goldEm?: boolean, definition, delivers}[] signoff link /> // WOLF-Prinzip; Pin nur desktop
<Quote text author role? />
<Portrait src alt aspect?: "4/5"|"3/5"|"3/4"|"16/10"; caption? sticky? priority? className? /> // nutzt <Door/>; rendert null bei src null
<Button href label suffix? micro? variant?: "primary"|"ghost" external? className? /> // Outline, Sheen-Hover, Mono-Suffix aus Maske
<TextLink href label external? className? /> // caps + →, Unterstreichung wächst von links
<CtaBand h2: {before, em, after}; cta: ButtonProps; micro? /> // dunkles Band inkl. <ContactLinks/>
<Interruption text mono? /> // 100svh ink, riesige Display-Zeile
```
### 2.10 Services gemeinsam (F2) `src/components/services/`
```tsx
<ServiceHero content={coaching|trainings|keynotes} photo?: string|null /> // Masthead(center), Eyebrow, H1 mit <em>, Subline, Button, Facts rechts, Folio 01/03
<ServiceLayout>{children}</ServiceLayout> // Sektionen hell mit Section-Wrapper; Kontakt-Band separat
```
### 2.11 Seiten (Phase 2)
- `src/app/page.tsx` + `src/components/home/{Hero,Positioning,ServicesIndex}.tsx` (P1)
- `src/components/home/{WolfPrinciple,About,Audience,ProcessContact}.tsx` (P2)
- `src/app/leistungen/coaching/page.tsx` + `src/components/services/coaching/*` (P3)
- `src/app/leistungen/trainings/page.tsx` + `src/components/services/trainings/*` (P4)
- `src/app/leistungen/keynotes/page.tsx` + `src/components/services/keynotes/*` (P5)
- `src/app/impressum/page.tsx`, `src/app/datenschutz/page.tsx`, `src/app/not-found.tsx`, `src/app/sitemap.ts`,
`src/app/robots.ts`, `src/app/icon.svg` (P6)
## 3. Phasen
1. **Foundation** (parallel): F1 = 2.1, 2.6, 2.7, 2.8 + `template.tsx` + `layout.tsx`-Erweiterung.
F2 = 2.22.5, 2.9, 2.10 + `src/hooks/useMediaQuery.ts`.
2. **Integration 1**: `tsc` + Verträge angleichen, `npm run build` grün mit Platzhalter-Startseite.
3. **Pages** (parallel): P1P6.
4. **Integration 2**: `tsc`, `lint`, `build`; alle 7 Routen im `out/`.
5. **Visual QA** (Loop bis trocken, max. 3 Runden): Dev-Server über `.claude/launch.json` (`anelia-dev`),
Screenshots 1440×900 + 390×844, Findings → Fix-Agenten (disjunkte Dateien) → erneut.
6. **Review**: A11y, Performance/Static-Export, Datenschutz (keine externen Requests, kein Storage), Copy-Check
gegen verbotene Phrasen → Fixes → finaler Build.