Compare commits

...

14 commits

Author SHA1 Message Date
b95e4874f1 Add dashboard, API routes, Olga route, and supporting files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 17:02:50 +02:00
b9f2b8175a Fix FlipCard Finanzmanagement: Cashflow-Formulierung
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:00:10 +02:00
66b263b6a8 Update Woche 8, Experten, Starttermin und FlipCard
- 6 Experten (war 5), Expertenteam aktualisiert: Manuela Ludewig (Wo. 7), Pia Nestler Pioneer Club (Wo. 8)
- Woche 8 Timeline: Finanzmanagement für Selbstständige statt SEO
- FlipCard SEO & Google → Finanzmanagement
- Starttermin überall: 1. August 2026 (war 1. Juli 2027)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:54:47 +02:00
1c698fe4e2 Woche 7: Manuela Ludewig → Professioneller Vertriebscoach TBA
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 20:38:55 +02:00
374cd39407 Fix: Programm-Name mit Doppelpunkt (nicht Gedankenstrich)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 20:36:41 +02:00
5406bed6c9 Neuer Programm-Name: 8-Wochen Praxis-Bootcamp überall
- H1, Title-Tag, Meta-Description, Value Stack, Fließtext
- layout.tsx, page.tsx vollständig aktualisiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 20:35:38 +02:00
580cad5b25 Neue Wochenstruktur: Woche 2 Startangebot + Woche 3/4 angepasst
- Woche 2 neu: "Was verkaufst du — und womit fängst du an?"
- Woche 3: Angebot ausbauen + Positionierungsformel (war Woche 2)
- Woche 4: Verkaufsseite + Kanal-Strategie + 90-Tage-Plan (merged)
- Manuela Ludewig in Woche 7 (statt TBA)
- Coach-Sektion: Einstiegsangebot in Katjas Themenbereich

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 20:31:28 +02:00
18a5a175c7 update: Preis, Gradient-Heading, Vertriebler und kleinere Texte
- Preis: 250€/500€ → 280€ netto / 560€ netto überall
- Bonus-Heading: "HEUTE" mit Indigo-Violet Gradient
- Manuela Ludewig → TBA · Professioneller Vertriebler (page.tsx + WeekTimeline)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:13:55 +02:00
e5f053b3c4 redesign: Bonus-Karten als Flip-Cards mit minimalem Icon-Design
- Neue BonusCards.tsx Komponente mit Flip-Mechanismus
- Vorderseite: kleines dezentes Icon oben + großer fetter Titel als Hauptelement
- Rückseite: Indigo/Violet Gradient + Detailbeschreibung
- FlipCards-Heading: "Transformation/Kennst du das?" → "Ergebnisse/Diese Ergebnisse kannst du vom Programm erwarten"
- Bonus-Heading: neu mit "HEUTE" + Subzeile
- Unused Icon-Imports aus page.tsx entfernt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:05:44 +02:00
0e01b4c672 feat: Testimonials mit echten Google-Bewertungen befüllen
- 4 echte Bewertungen: Varvara Beloussova, Anke Rühle-Metz, Onlinewerbevideo, Ines Thiele-Seidel
- Varvara als Featured-Karte (volle Breite) + 3er-Grid für die anderen
- Reviewer-Fotos als runde Avatare (public/testimonials/)
- Heading und Subtext ohne "Katja" — Market Compass Branding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 13:40:15 +02:00
4f006d6d3a feat: FlipCards topic labels + content update
- Replace generic "Kennst du das?" with topic-specific labels per card
- Topics: Positionierung, Content-Plan, Verkaufsseite, Vertrieb, SEO & Google, Video & Social Media
- Remove Marketing/Vertrieb card, add Verkaufsseite card
- Update card 1 copy to focus on application over theory
- Update card 6 to video/content creation focus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 10:55:08 +02:00
79f03d429d fix: mobile mountain animation + white text on dark backgrounds
- MountainClimb: mobile SVG viewport now follows the person (pan/zoom)
- MountainClimb: restored desktop SVG, removed card-only mobile fallback
- page.tsx: inline styles for all text in mc-dark-bg sections (white)
- page.tsx: remove "Nur noch 8 frei" badge
- page.tsx: fix negative copy ("kein neues Wissen") → positive umsetzungsfokus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 10:37:50 +02:00
9f09b4a3e7 Move Meet the Coach section: after MountainClimb, before WeekTimeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 00:19:52 +02:00
6afb4151cf Mountain: 8 shorter steps + summit celebration, replace Kacheln section
- Replace 11 full-sentence steps with 8 two-line pairs (title + sub)
- Reduce waypoints from 11 to 8, recalculate smooth bezier ridge
- Summit card: gradient indigo→violet bubble with celebration text
- Figure lifts flag and bounces at summit (atSummit)
- Move MountainClimb into "Nach 8 Wochen nimmst du mit:" section
- Remove redundant static IconCard grid (8 Kacheln)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 23:58:18 +02:00
20 changed files with 2619 additions and 181 deletions

View file

@ -11,7 +11,8 @@
"dependencies": {
"next": "16.2.4",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"recharts": "^3.8.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",

View file

@ -17,6 +17,9 @@ importers:
react-dom:
specifier: 19.2.4
version: 19.2.4(react@19.2.4)
recharts:
specifier: ^3.8.1
version: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)
devDependencies:
'@tailwindcss/postcss':
specifier: ^4
@ -429,9 +432,26 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
'@reduxjs/toolkit@2.12.0':
resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==}
peerDependencies:
react: ^16.9.0 || ^17.0.0 || ^18 || ^19
react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
peerDependenciesMeta:
react:
optional: true
react-redux:
optional: true
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
@ -530,6 +550,33 @@ packages:
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
'@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
'@types/d3-color@3.1.3':
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
'@types/d3-ease@3.0.2':
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
'@types/d3-interpolate@3.0.4':
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
'@types/d3-path@3.1.1':
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
'@types/d3-scale@4.0.9':
resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
'@types/d3-shape@3.1.8':
resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
'@types/d3-time@3.0.4':
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
'@types/d3-timer@3.0.2':
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@ -550,6 +597,9 @@ packages:
'@types/react@19.2.14':
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
'@types/use-sync-external-store@0.0.6':
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
'@typescript-eslint/eslint-plugin@8.59.1':
resolution: {integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -841,6 +891,10 @@ packages:
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@ -861,6 +915,50 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
d3-array@3.2.4:
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
engines: {node: '>=12'}
d3-color@3.1.0:
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
engines: {node: '>=12'}
d3-ease@3.0.1:
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
engines: {node: '>=12'}
d3-format@3.1.2:
resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
engines: {node: '>=12'}
d3-interpolate@3.0.1:
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
engines: {node: '>=12'}
d3-path@3.1.0:
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
engines: {node: '>=12'}
d3-scale@4.0.2:
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
engines: {node: '>=12'}
d3-shape@3.2.0:
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
engines: {node: '>=12'}
d3-time-format@4.1.0:
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
engines: {node: '>=12'}
d3-time@3.1.0:
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
engines: {node: '>=12'}
d3-timer@3.0.1:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@ -893,6 +991,9 @@ packages:
supports-color:
optional: true
decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@ -958,6 +1059,9 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
es-toolkit@1.46.1:
resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@ -1086,6 +1190,9 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
eventemitter3@5.0.4:
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@ -1235,6 +1342,12 @@ packages:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
immer@10.2.0:
resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==}
immer@11.1.8:
resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
@ -1247,6 +1360,10 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
internmap@2.0.3:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
is-array-buffer@3.0.5:
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
@ -1677,10 +1794,38 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-redux@9.3.0:
resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==}
peerDependencies:
'@types/react': ^18.2.25 || ^19
react: ^18.0 || ^19
redux: ^5.0.0
peerDependenciesMeta:
'@types/react':
optional: true
redux:
optional: true
react@19.2.4:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
recharts@3.8.1:
resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==}
engines: {node: '>=18'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
redux-thunk@3.1.0:
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
peerDependencies:
redux: ^5.0.0
redux@5.0.1:
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
reflect.getprototypeof@1.0.10:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
@ -1689,6 +1834,9 @@ packages:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
reselect@5.1.1:
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@ -1842,6 +1990,9 @@ packages:
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
engines: {node: '>=6'}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tinyglobby@0.2.16:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
@ -1913,6 +2064,14 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
use-sync-external-store@1.6.0:
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
victory-vendor@37.3.6:
resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@ -2303,8 +2462,24 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {}
'@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4)':
dependencies:
'@standard-schema/spec': 1.1.0
'@standard-schema/utils': 0.3.0
immer: 11.1.8
redux: 5.0.1
redux-thunk: 3.1.0(redux@5.0.1)
reselect: 5.1.1
optionalDependencies:
react: 19.2.4
react-redux: 9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1)
'@rtsao/scc@1.1.0': {}
'@standard-schema/spec@1.1.0': {}
'@standard-schema/utils@0.3.0': {}
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@ -2383,6 +2558,30 @@ snapshots:
tslib: 2.8.1
optional: true
'@types/d3-array@3.2.2': {}
'@types/d3-color@3.1.3': {}
'@types/d3-ease@3.0.2': {}
'@types/d3-interpolate@3.0.4':
dependencies:
'@types/d3-color': 3.1.3
'@types/d3-path@3.1.1': {}
'@types/d3-scale@4.0.9':
dependencies:
'@types/d3-time': 3.0.4
'@types/d3-shape@3.1.8':
dependencies:
'@types/d3-path': 3.1.1
'@types/d3-time@3.0.4': {}
'@types/d3-timer@3.0.2': {}
'@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {}
@ -2401,6 +2600,8 @@ snapshots:
dependencies:
csstype: 3.2.3
'@types/use-sync-external-store@0.0.6': {}
'@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@ -2706,6 +2907,8 @@ snapshots:
client-only@0.0.1: {}
clsx@2.1.1: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@ -2724,6 +2927,44 @@ snapshots:
csstype@3.2.3: {}
d3-array@3.2.4:
dependencies:
internmap: 2.0.3
d3-color@3.1.0: {}
d3-ease@3.0.1: {}
d3-format@3.1.2: {}
d3-interpolate@3.0.1:
dependencies:
d3-color: 3.1.0
d3-path@3.1.0: {}
d3-scale@4.0.2:
dependencies:
d3-array: 3.2.4
d3-format: 3.1.2
d3-interpolate: 3.0.1
d3-time: 3.1.0
d3-time-format: 4.1.0
d3-shape@3.2.0:
dependencies:
d3-path: 3.1.0
d3-time-format@4.1.0:
dependencies:
d3-time: 3.1.0
d3-time@3.1.0:
dependencies:
d3-array: 3.2.4
d3-timer@3.0.1: {}
damerau-levenshtein@1.0.8: {}
data-view-buffer@1.0.2:
@ -2752,6 +2993,8 @@ snapshots:
dependencies:
ms: 2.1.3
decimal.js-light@2.5.1: {}
deep-is@0.1.4: {}
define-data-property@1.1.4:
@ -2888,6 +3131,8 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
es-toolkit@1.46.1: {}
escalade@3.2.0: {}
escape-string-regexp@4.0.0: {}
@ -3097,6 +3342,8 @@ snapshots:
esutils@2.0.3: {}
eventemitter3@5.0.4: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.1:
@ -3241,6 +3488,10 @@ snapshots:
ignore@7.0.5: {}
immer@10.2.0: {}
immer@11.1.8: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@ -3254,6 +3505,8 @@ snapshots:
hasown: 2.0.3
side-channel: 1.1.0
internmap@2.0.3: {}
is-array-buffer@3.0.5:
dependencies:
call-bind: 1.0.9
@ -3664,8 +3917,43 @@ snapshots:
react-is@16.13.1: {}
react-redux@9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1):
dependencies:
'@types/use-sync-external-store': 0.0.6
react: 19.2.4
use-sync-external-store: 1.6.0(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
redux: 5.0.1
react@19.2.4: {}
recharts@3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1):
dependencies:
'@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4)
clsx: 2.1.1
decimal.js-light: 2.5.1
es-toolkit: 1.46.1
eventemitter3: 5.0.4
immer: 10.2.0
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
react-is: 16.13.1
react-redux: 9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1)
reselect: 5.1.1
tiny-invariant: 1.3.3
use-sync-external-store: 1.6.0(react@19.2.4)
victory-vendor: 37.3.6
transitivePeerDependencies:
- '@types/react'
- redux
redux-thunk@3.1.0(redux@5.0.1):
dependencies:
redux: 5.0.1
redux@5.0.1: {}
reflect.getprototypeof@1.0.10:
dependencies:
call-bind: 1.0.9
@ -3686,6 +3974,8 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
reselect@5.1.1: {}
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@ -3898,6 +4188,8 @@ snapshots:
tapable@2.3.3: {}
tiny-invariant@1.3.3: {}
tinyglobby@0.2.16:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
@ -4013,6 +4305,27 @@ snapshots:
dependencies:
punycode: 2.3.1
use-sync-external-store@1.6.0(react@19.2.4):
dependencies:
react: 19.2.4
victory-vendor@37.3.6:
dependencies:
'@types/d3-array': 3.2.2
'@types/d3-ease': 3.0.2
'@types/d3-interpolate': 3.0.4
'@types/d3-scale': 4.0.9
'@types/d3-shape': 3.1.8
'@types/d3-time': 3.0.4
'@types/d3-timer': 3.0.2
d3-array: 3.2.4
d3-ease: 3.0.1
d3-interpolate: 3.0.1
d3-scale: 4.0.2
d3-shape: 3.2.0
d3-time: 3.1.0
d3-timer: 3.0.1
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0

View file

@ -1,3 +1,6 @@
ignoredBuiltDependencies:
allowBuilds:
sharp: set this to true or false
unrs-resolver: set this to true or false
onlyBuiltDependencies:
- sharp
- unrs-resolver

BIN
public/olga.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

5
public/pm-kritisch.json Normal file
View file

@ -0,0 +1,5 @@
{
"stand": "2026-05-18T22:00:00Z",
"kritisch": [],
"allesOk": true
}

BIN
public/testimonials/anke.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/testimonials/varvara.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1,17 @@
import { NextResponse } from 'next/server'
import { fetchAllTodos } from '@/lib/notion-pm'
export const revalidate = 300
export async function GET() {
try {
const todos = await fetchAllTodos()
return NextResponse.json({
todos,
timestamp: new Date().toISOString(),
count: todos.length,
})
} catch (err) {
return NextResponse.json({ error: String(err) }, { status: 500 })
}
}

View file

@ -0,0 +1,192 @@
"use client";
import { useState } from "react";
import {
PackageIcon, PhoneIcon, VideoCameraIcon,
MapIcon, ChatIcon, HandshakeIcon,
} from "./Icons";
const BONUSES = [
{
n: 1,
icon: <PackageIcon className="w-6 h-6" />,
title: "Sofort loslegen: 7 fertige Vorlagen",
desc: "7 Vorlagen zum Direkt-Ausfüllen: Wunschkunden-Persona · Positionierungsformel · Verkaufsseiten-Struktur · 90-Tage-Marketingplan · Kanal-Entscheidungsmatrix · Marketing-Audit · Kurztext für Website & Social Media",
val: "497 €",
},
{
n: 2,
icon: <PhoneIcon className="w-6 h-6" />,
title: "Kunden gewinnen über Instagram",
desc: "Wie du über Social Media planbar Anfragen bekommst — Schritt für Schritt erklärt, direkt umsetzbar.",
val: "297 €",
},
{
n: 3,
icon: <VideoCameraIcon className="w-6 h-6" />,
title: "Professionell filmen — mit dem Handy",
desc: "Kein teures Equipment. Die Grundlagen von den Video-Experten — die du zuhause selbst anwenden kannst und die deine Content-Qualität sofort verbesserst.",
val: "197 €",
},
{
n: 4,
icon: <MapIcon className="w-6 h-6" />,
title: "Welcher Social Media Kanal passt zu dir?",
desc: "Kein Rätselraten mehr — du weißt auf welchem Social Media Kanal deine Wunschkunden aktiv sind, und wo es sich wirklich lohnt Zeit zu investieren.",
val: "147 €",
},
{
n: 5,
icon: <ChatIcon className="w-6 h-6" />,
title: "1 Monat Begleitung nach dem Programm",
desc: "Die Messenger-Community bleibt einen Monat nach dem Programm offen. Fragen zur Umsetzung, offener Austausch, Motivation.",
val: "97 €",
},
{
n: 6,
icon: <HandshakeIcon className="w-6 h-6" />,
title: "Abschluss-Netzwerkevent (geplant)",
desc: "Nach der letzten Session kommen alle zusammen. Persönliches Feedback von Katja. Neue Kontakte. Exklusiv für den Pilotdurchlauf.",
val: "197 €",
},
];
const faceBase: React.CSSProperties = {
position: "absolute",
inset: 0,
borderRadius: "1rem",
display: "flex",
flexDirection: "column",
padding: "24px",
WebkitBackfaceVisibility: "hidden",
backfaceVisibility: "hidden",
};
function BonusCard({ n, icon, title, desc, val }: {
n: number; icon: React.ReactNode; title: string; desc: string; val: string;
}) {
const [flipped, setFlipped] = useState(false);
return (
<div
style={{ perspective: "1000px", height: "280px", cursor: "pointer" }}
onClick={() => setFlipped((f) => !f)}
onMouseEnter={() => setFlipped(true)}
onMouseLeave={() => setFlipped(false)}
role="button"
tabIndex={0}
onKeyDown={(e) => e.key === "Enter" && setFlipped((f) => !f)}
>
<div
style={{
position: "relative",
width: "100%",
height: "100%",
transformStyle: "preserve-3d",
transition: "transform 0.55s cubic-bezier(0.4, 0.2, 0.2, 1)",
transform: flipped ? "rotateY(180deg)" : "rotateY(0deg)",
}}
>
{/* VORDERSEITE */}
<div
style={{
...faceBase,
background: "#ffffff",
border: "1px solid #eae8f8",
boxShadow: "inset 0 3px 0 #6366f1, 0 2px 12px rgba(99,102,241,0.06)",
}}
>
{/* Oben: Icon (minimal) + Bonus-Label */}
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<span style={{ color: "#a5b4fc" }}>{icon}</span>
<span style={{
fontSize: "0.6rem", fontWeight: 800, letterSpacing: "0.12em",
textTransform: "uppercase", borderRadius: "99px",
padding: "3px 10px", background: "#eef2ff", color: "#6366f1",
}}>
Bonus {n}
</span>
</div>
{/* Spacer */}
<div style={{ flex: 1 }} />
{/* Titel — Hauptelement */}
<p style={{
fontSize: "1.05rem", fontWeight: 800, color: "#0f172a",
lineHeight: 1.3, marginBottom: "16px",
}}>
{title}
</p>
{/* Wert-Badge */}
<span style={{
fontSize: "0.75rem", fontWeight: 700, color: "#059669",
background: "#ecfdf5", border: "1px solid #bbf7d0",
borderRadius: "99px", padding: "4px 14px",
alignSelf: "flex-start",
}}>
Wert: {val}
</span>
{/* Hint */}
<div style={{
marginTop: "16px", fontSize: "0.62rem", display: "flex",
alignItems: "center", gap: "4px", color: "#c7d2fe",
}}>
<svg width="11" height="11" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
drehe mich um
</div>
</div>
{/* RÜCKSEITE */}
<div
style={{
...faceBase,
background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
boxShadow: "0 4px 24px rgba(99,102,241,0.35)",
transform: "rotateY(180deg)",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
gap: "14px",
}}
>
<span style={{
fontSize: "0.62rem", fontWeight: 800, letterSpacing: "0.12em",
textTransform: "uppercase", borderRadius: "99px",
padding: "3px 10px", background: "rgba(255,255,255,0.2)", color: "#fff",
}}>
Was du erhältst:
</span>
<p style={{ fontSize: "0.85rem", lineHeight: 1.65, color: "#fff", fontWeight: 500 }}>
{desc}
</p>
<span style={{
fontSize: "0.62rem", display: "flex", alignItems: "center",
gap: "4px", color: "rgba(255,255,255,0.65)", marginTop: "auto",
}}>
<svg width="11" height="11" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
inklusive im Pilotprogramm
</span>
</div>
</div>
</div>
);
}
export default function BonusCards() {
return (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{BONUSES.map((b) => (
<BonusCard key={b.n} {...b} />
))}
</div>
);
}

View file

@ -4,32 +4,38 @@ import { useState } from "react";
const PAIRS = [
{
before: "\u201EIch wei\xDF viel \u2014 aber niemand fragt an.\u201C",
after: "Positionierung in einem Satz. Ein System das planbar Anfragen bringt.",
topic: "Positionierung",
before: "\u201EIch habe viel \xFCber Marketing gelernt \u2014 aber ich wei\xDF nicht wie ich es bei mir anwende.\u201C",
after: "Wir bauen dein pers\xF6nliches System \u2014 du setzt um was du schon wei\xDFt.",
},
{
topic: "Content-Plan",
before: "\u201EIch poste regelm\xe4\xDFig. Nichts passiert.\u201C",
after: "Content-Plan f\xFCr alle Plattformen \u2014 du wei\xDFt was, wann und warum.",
},
{
before: "\u201EIch wei\xDF nicht wie Marketing und Vertrieb zusammenh\xe4ngen.\u201C",
after: "Marketing-Systemkarte. Alle Teile greifen ineinander.",
topic: "Verkaufsseite",
before: "\u201EIch wei\xDF nicht wie ich eine Verkaufslandingpage baue.\u201C",
after: "Du bekommst Schritt-f\xFCr-Schritt Anleitung \u2014 und baust deine eigene Verkaufsseite.",
},
{
topic: "Vertrieb",
before: "\u201EIch warte dass Kunden kommen.\u201C",
after: "Verkaufsskript. Direktansprache. Du gehst aktiv auf Kunden zu.",
},
{
before: "\u201EIch werde online nicht gefunden.\u201C",
after: "Google Business optimiert. Deine Seite f\xFCr dein Haupt-Keyword.",
topic: "Finanzmanagement",
before: "\u201EIch habe keinen \xDCberblick \xFCber meinen Cashflow \u2014 und keine Finanzgrundlage f\xFCr planbaren Gesch\xe4ftsausbau.\u201C",
after: "Ausgaben, Steuern, Verm\xF6gensaufbau \u2014 du wei\xDFt was du hast, was du brauchst, und wie du es meisterst.",
},
{
before: "\u201ENach jedem Kurs fange ich wieder bei null an.\u201C",
after: "Ein System aus Marketing, Vertrieb und Social Media \u2014 das weiterl\xe4uft.",
topic: "Video & Social Media",
before: "\u201EIch will Content machen \u2014 aber ich wei\xDF nicht wo ich anfangen soll: welche Kamera, wie schneiden?\u201C",
after: "Handy-Video-Setup + Schnitt-Grundlagen. Du gehst raus und drehst deinen ersten Reel.",
},
];
function FlipCard({ before, after }: { before: string; after: string }) {
function FlipCard({ topic, before, after }: { topic: string; before: string; after: string }) {
const [flipped, setFlipped] = useState(false);
const faceBase: React.CSSProperties = {
@ -82,7 +88,7 @@ function FlipCard({ before, after }: { before: string; after: string }) {
textTransform: "uppercase", borderRadius: "99px",
padding: "3px 10px", background: "#eef2ff", color: "#4338ca",
}}>
Kennst du das?
{topic}
</span>
<p style={{ fontSize: "0.9rem", lineHeight: 1.5, fontStyle: "italic", color: "#475569" }}>
@ -143,9 +149,9 @@ export default function FlipCards() {
<div className="text-center mb-12">
<span className="inline-flex items-center gap-2 bg-indigo-50 text-indigo-700 text-xs font-bold uppercase tracking-widest rounded-full px-4 py-2 border border-indigo-100">
<span className="w-2 h-2 rounded-full bg-indigo-500 animate-pulse" />
Transformation
Ergebnisse
</span>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">Kennst du das?</h2>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">Diese Ergebnisse kannst du vom Programm erwarten</h2>
<p className="mt-2 text-slate-500 text-sm">
Fahre über eine Karte oder tippe drauf um zu sehen was sich verändert.
</p>
@ -153,7 +159,7 @@ export default function FlipCards() {
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{PAIRS.map((pair, i) => (
<FlipCard key={i} before={pair.before} after={pair.after} />
<FlipCard key={i} topic={pair.topic} before={pair.before} after={pair.after} />
))}
</div>

View file

@ -2,32 +2,31 @@
import { useState, useEffect, useRef } from "react";
const STEPS = [
"Deinen Wunschkunden klar definiert \u2014 wer er ist, was er braucht, was er schon versucht hat",
"Dein Angebot auf den Punkt gebracht \u2014 was du anbietest, welches Problem du l\u00f6st",
"Deine Positionierung in einem Satz \u2014 klar, sofort verst\u00e4ndlich, f\u00fcr alle Kan\u00e4le nutzbar",
"Die komplette Struktur f\u00fcr deine Verkaufsseite \u2014 mit Feedback zur Umsetzung",
"Deine Marketing-Systemkarte \u2014 wie Sichtbarkeit, Vertrauen, Anfrage und Kauf zusammenh\u00e4ngen",
"Deinen 90-Tage-Marketingplan + Entscheidung welche 2 Kan\u00e4le du fokussierst",
"Deinen 4-Wochen Content-Plan + Themen die zeigen wof\u00fcr du stehst \u2014 f\u00fcr alle Plattformen",
"Deinen Launch-Plan \u2014 wie du dein Angebot aktiv \u00fcber Social Media in den Markt bringst",
"Video-Setup erkl\u00e4rt \u2014 professionelle Videos mit dem Handy",
"Dein pers\u00f6nliches Verkaufsskript f\u00fcr Direktansprache und Netzwerk",
"Dein Google Business Profil optimiert + SEO-Grundlagen f\u00fcr deine Website",
interface Step {
title: string;
sub: string;
}
const STEPS: Step[] = [
{ title: "Klare Wunschkunden-Persona", sub: "+ vollst\u00e4ndiger Marketing-Audit" },
{ title: "Was verkaufst du?", sub: "EIN klares Startangebot \u2014 konkret und buchbar" },
{ title: "Angebot ausgebaut + Positionierung", sub: "Positionierungsformel in einem Satz" },
{ title: "Verkaufsseite + 90-Tage-Plan", sub: "2 fokussierte Kan\u00e4le entschieden" },
{ title: "Content-Plan 4 Wochen", sub: "Was, wann und warum du postest" },
{ title: "Social Media Verkauf", sub: "+ professionelles Video-Setup mit dem Handy" },
{ title: "Dein Verkaufsskript", sub: "F\u00fcr Direktansprache und Netzwerkveranstaltungen" },
{ title: "Google Business optimiert", sub: "+ SEO-Grundlagen f\u00fcr deine Website" },
];
// Waypoints along the mountain ridge (SVG user units, viewBox 940 × 580)
const WP = [
{ x: 65, y: 520, lean: 8 },
{ x: 155, y: 500, lean: 9 },
{ x: 248, y: 477, lean: 12 },
{ x: 338, y: 449, lean: 13 },
{ x: 425, y: 416, lean: 16 },
{ x: 508, y: 378, lean: 19 },
{ x: 587, y: 334, lean: 20 },
{ x: 660, y: 290, lean: 19 },
{ x: 727, y: 252, lean: 18 },
{ x: 787, y: 220, lean: 18 },
{ x: 65, y: 520, lean: 8 },
{ x: 195, y: 488, lean: 12 },
{ x: 325, y: 450, lean: 15 },
{ x: 450, y: 407, lean: 17 },
{ x: 565, y: 358, lean: 20 },
{ x: 670, y: 303, lean: 22 },
{ x: 757, y: 248, lean: 24 },
{ x: 840, y: 192, lean: 0 },
] as const;
@ -37,25 +36,24 @@ const VH = 580;
// Smooth quadratic-bezier path through all waypoints (midpoint technique)
const RIDGE =
"M 65 520" +
" Q 110 510 155 500" +
" Q 201.5 488.5 248 477" +
" Q 293 463 338 449" +
" Q 381.5 432.5 425 416" +
" Q 466.5 397 508 378" +
" Q 547.5 356 587 334" +
" Q 623.5 312 660 290" +
" Q 693.5 271 727 252" +
" Q 757 236 787 220" +
" Q 813.5 206 840 192";
" Q 130 504 195 488" +
" Q 260 469 325 450" +
" Q 387.5 428.5 450 407" +
" Q 507.5 382.5 565 358" +
" Q 617.5 330.5 670 303" +
" Q 713.5 275.5 757 248" +
" Q 798.5 220 840 192";
// Filled mountain silhouette (ridge + right slope + base)
const FILL =
`M 0 ${VH} L 0 540 L ` +
RIDGE.slice(2) + // "65 520 Q …" — continues from first waypoint to summit
RIDGE.slice(2) +
` L 940 265 L 940 ${VH} Z`;
const CARD_W = 234;
const CARD_H = 128;
const CARD_H = 110;
const SUMMIT_W = 294;
const SUMMIT_H = 80;
export default function MountainClimb() {
const [step, setStep] = useState(-1);
@ -90,12 +88,21 @@ export default function MountainClimb() {
const atSummit = step === STEPS.length - 1;
const wp = step >= 0 ? WP[step] : WP[0];
// Card x: centred on person, clamped inside viewBox
// Regular card positioning
const rawCX = wp.x - CARD_W / 2;
const cardX = Math.min(Math.max(rawCX, 8), VW - CARD_W - 8);
// Card y: above figure head (figure is 3× scaled → head at ~87 SVG units above foot)
const cardY = Math.max(wp.y - CARD_H - 110, 4);
// Summit card positioning
const summitRawCX = wp.x - SUMMIT_W / 2;
const summitCardX = Math.min(Math.max(summitRawCX, 8), VW - SUMMIT_W - 8);
const summitCardY = Math.max(wp.y - SUMMIT_H - 120, 4);
// Mobile pan: viewport 380×440 follows the person
const MOB_W = 380, MOB_H = 440;
const mobPanX = Math.min(0, Math.max(-(VW - MOB_W), -(wp.x - MOB_W / 2)));
const mobPanY = Math.min(0, Math.max(-(VH - MOB_H), -(wp.y - Math.round(MOB_H * 0.68))));
const replay = () => {
started.current = true;
setStep(0);
@ -108,14 +115,14 @@ export default function MountainClimb() {
<div className="text-center mb-12">
<span className="inline-flex items-center gap-2 bg-indigo-50 text-indigo-700 text-xs font-bold uppercase tracking-widest rounded-full px-4 py-2 border border-indigo-100">
<span className="w-2 h-2 rounded-full bg-indigo-500 animate-pulse" />
Komplettpaket
Deine Ergebnisse
</span>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">
Was du in 8 Wochen baust und mit nach Hause nimmst
Nach 8 Wochen nimmst du mit:
</h2>
</div>
{/* ── Mountain animation (md+) ── */}
{/* ── Mountain animation (desktop only) ── */}
<div
className="hidden md:block"
onClick={() => !atSummit && setPaused((p) => !p)}
@ -182,8 +189,8 @@ export default function MountainClimb() {
</g>
)}
{/* ── Text card ── */}
{step >= 0 && (
{/* ── Regular step card ── */}
{step >= 0 && !atSummit && (
<>
{/* Dashed connector: card → figure head */}
<line
@ -217,20 +224,75 @@ export default function MountainClimb() {
letterSpacing: "0.12em",
textTransform: "uppercase",
color: "#6366f1",
marginBottom: "5px",
marginBottom: "4px",
}}
>
Schritt {step + 1} / {STEPS.length}
</div>
<div
style={{
fontSize: "12.5px",
color: "#334155",
lineHeight: 1.55,
fontWeight: 500,
fontSize: "13px",
color: "#0f172a",
fontWeight: 700,
lineHeight: 1.3,
marginBottom: "4px",
}}
>
{STEPS[step]}
{STEPS[step].title}
</div>
<div
style={{
fontSize: "11px",
color: "#64748b",
lineHeight: 1.4,
}}
>
{STEPS[step].sub}
</div>
</div>
</foreignObject>
</>
)}
{/* ── Summit celebration card ── */}
{atSummit && (
<>
<line
x1={summitCardX + SUMMIT_W / 2}
y1={summitCardY + SUMMIT_H}
x2={wp.x}
y2={wp.y - 90}
stroke="#a5b4fc"
strokeWidth="1.5"
strokeDasharray="4 3"
/>
<foreignObject
key="summit"
x={summitCardX}
y={summitCardY}
width={SUMMIT_W}
height={SUMMIT_H}
className="mc-fade-in"
style={{ overflow: "visible" }}
>
<div
style={{
background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
borderRadius: "14px",
padding: "16px 18px",
boxShadow: "0 8px 32px rgba(99,102,241,0.45)",
textAlign: "center",
}}
>
<div
style={{
fontSize: "15px",
fontWeight: 800,
color: "#ffffff",
lineHeight: 1.35,
}}
>
Ich habe jetzt ein Kundengewinnungssystem
</div>
</div>
</foreignObject>
@ -241,7 +303,7 @@ export default function MountainClimb() {
{/* Pause hint */}
{step >= 0 && !atSummit && (
<p className="text-center text-xs text-slate-400 mt-2 select-none">
{paused ? "▶ Klicken zum Fortsetzen" : "⏸ Klicken zum Pausieren"}
{paused ? "\u25B6 Klicken zum Fortsetzen" : "\u23F8 Klicken zum Pausieren"}
</p>
)}
@ -252,23 +314,127 @@ export default function MountainClimb() {
onClick={replay}
className="text-sm text-indigo-600 border border-indigo-200 rounded-full px-5 py-2 hover:bg-indigo-50 transition-colors"
>
Nochmal ansehen \u21BA
</button>
</div>
)}
</div>
{/* ── Mobile: same SVG, viewport follows the person ── */}
<div
className="md:hidden"
onClick={() => !atSummit && setPaused((p) => !p)}
style={{ cursor: atSummit ? "default" : "pointer" }}
>
<svg
viewBox={`0 0 ${MOB_W} ${MOB_H}`}
className="w-full rounded-2xl"
style={{ display: "block", overflow: "hidden", background: "linear-gradient(160deg, #f5f4ff, #f4f7fa)" }}
aria-hidden="true"
>
<defs>
<linearGradient id="mgFillMob" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#e0e7ff" stopOpacity="0.55" />
<stop offset="100%" stopColor="#ede9fe" stopOpacity="0.12" />
</linearGradient>
</defs>
{/* Pan group — smoothly follows the person */}
<g style={{ transform: `translate(${mobPanX}px, ${mobPanY}px)`, transition: "transform 0.75s cubic-bezier(0.4,0.2,0.2,1)" }}>
{/* Mountain */}
<path d={FILL} fill="url(#mgFillMob)" />
<path d={RIDGE} fill="none" stroke="#a5b4fc" strokeWidth="2.5" strokeLinecap="round" />
{/* Dots */}
{WP.map((w, i) => (
<circle key={i} cx={w.x} cy={w.y}
r={step >= 0 && i <= step ? 5.5 : 3.5}
fill={step >= 0 && i <= step ? "#6366f1" : "#c7d2fe"}
style={{ transition: "r 0.3s, fill 0.3s" }}
/>
))}
{/* Stick figure */}
{step >= 0 && (
<g
transform={`translate(${wp.x}, ${wp.y}) rotate(${wp.lean})`}
style={{ transition: "transform 0.75s cubic-bezier(0.4,0.2,0.2,1)" }}
>
<g transform="scale(3)" className={atSummit ? "mc-bounce" : ""}
style={{ transformBox: "fill-box", transformOrigin: "50% 100%" }}>
<circle cx="0" cy="-29" r="7.5" fill="none" stroke="#4f46e5" strokeWidth="0.5" />
<line x1="0" y1="-21" x2="0" y2="-3" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
<line x1="0" y1="-16" x2="-10" y2="-8" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
<line x1="0" y1="-16" x2="10" y2="-24" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
<line x1="0" y1="-3" x2="-7" y2="10" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
<line x1="0" y1="-3" x2="7" y2="10" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
{atSummit && (
<g className="mc-fade-in" transform="translate(9, -25)">
<line x1="0" y1="0" x2="0" y2="-22" stroke="#6366f1" strokeWidth="0.5" strokeLinecap="round" />
<polygon points="0,-22 18,-15 0,-8" fill="#6366f1" />
</g>
)}
</g>
</g>
)}
{/* Step card */}
{step >= 0 && !atSummit && (
<>
<line x1={cardX + CARD_W / 2} y1={cardY + CARD_H} x2={wp.x} y2={wp.y - 90}
stroke="#c7d2fe" strokeWidth="1" strokeDasharray="4 3" />
<foreignObject key={step} x={cardX} y={cardY} width={CARD_W} height={CARD_H}
className="mc-fade-in" style={{ overflow: "visible" }}>
<div className="bg-white rounded-xl border border-indigo-100 shadow-lg" style={{ padding: "10px 12px" }}>
<div style={{ fontSize: "9px", fontWeight: 800, letterSpacing: "0.12em", textTransform: "uppercase", color: "#6366f1", marginBottom: "4px" }}>
Schritt {step + 1} / {STEPS.length}
</div>
<div style={{ fontSize: "13px", color: "#0f172a", fontWeight: 700, lineHeight: 1.3, marginBottom: "4px" }}>
{STEPS[step].title}
</div>
<div style={{ fontSize: "11px", color: "#64748b", lineHeight: 1.4 }}>
{STEPS[step].sub}
</div>
</div>
</foreignObject>
</>
)}
{/* Summit card */}
{atSummit && (
<>
<line x1={summitCardX + SUMMIT_W / 2} y1={summitCardY + SUMMIT_H} x2={wp.x} y2={wp.y - 90}
stroke="#a5b4fc" strokeWidth="1.5" strokeDasharray="4 3" />
<foreignObject key="summit-mob" x={summitCardX} y={summitCardY} width={SUMMIT_W} height={SUMMIT_H}
className="mc-fade-in" style={{ overflow: "visible" }}>
<div style={{ background: "linear-gradient(135deg, #6366f1, #8b5cf6)", borderRadius: "14px", padding: "16px 18px", boxShadow: "0 8px 32px rgba(99,102,241,0.45)", textAlign: "center" }}>
<div style={{ fontSize: "15px", fontWeight: 800, color: "#ffffff", lineHeight: 1.35 }}>
Ich habe jetzt ein Kundengewinnungssystem
</div>
</div>
</foreignObject>
</>
)}
</g>
</svg>
{/* Hints */}
{step >= 0 && !atSummit && (
<p className="text-center text-xs text-slate-400 mt-2 select-none">
{paused ? "▶ Tippen zum Fortsetzen" : "⏸ Tippen zum Pausieren"}
</p>
)}
{atSummit && (
<div className="text-center mt-3">
<button onClick={replay} className="text-sm text-indigo-600 border border-indigo-200 rounded-full px-5 py-2 hover:bg-indigo-50 transition-colors">
Nochmal ansehen
</button>
</div>
)}
</div>
{/* ── Mobile fallback: numbered list ── */}
<div className="md:hidden space-y-3">
{STEPS.map((text, i) => (
<div key={i} className="mc-card p-4 flex items-start gap-3">
<span className="w-7 h-7 rounded-full bg-gradient-to-br from-indigo-500 to-violet-500 text-white text-xs font-bold flex items-center justify-center flex-shrink-0 mt-0.5">
{i + 1}
</span>
<p className="text-slate-700 text-sm leading-relaxed">{text}</p>
</div>
))}
</div>
</div>
</section>
);

View file

@ -21,21 +21,21 @@ interface Module {
const MODULES: Module[] = [
{
label: "Modul 1 — Wochen 1 & 2",
headline: "Wunschkunde, Angebot, Positionierung",
headline: "Wunschkunde, Markt + dein erstes klares Angebot",
weeks: [
{
week: "Woche 1",
title: "Dein Wunschkunde + dein Markt",
body: "Du schaust konkret hin: Wer ist dein Wunschkunde wirklich? Welches Problem hat er — und was hat er schon alles versucht? Was kostet ihn dieses Problem jeden Monat? Du machst einen ehrlichen Marketing-Audit: Was läuft, was nicht — und warum.",
body: "Erst weißt du für wen — dann weißt du womit du anfängst. Du schaust konkret hin: Wer ist dein Wunschkunde wirklich? Welches Problem hat er — und was hat er schon alles versucht? Was kostet ihn das jeden Monat? Du machst einen ehrlichen Marketing-Audit: Was läuft, was nicht — und warum. Diese Woche ist die Grundlage für dein Einstiegsangebot in Woche 2.",
vorlagen: "Marketing-Audit-Vorlage · Wunschkunden-Persona-Vorlage",
result: "Klarheit über deinen Wunschkunden — konkret, nicht allgemein.",
result: "Klarheit über deinen Wunschkunden — konkret, nicht allgemein. Basis für Woche 2.",
},
{
week: "Woche 2",
title: "Dein unwiderstehliches Angebot + deine Positionierung",
body: "Du legst dein Angebot auf den Tisch: Was bietest du genau an? Welches Problem löst du? Dann entwickelst du deine Positionierungsformel — für wen, welches Problem, was macht dich anders. In einem Satz. Du übst ihn live in der Gruppe.",
vorlagen: "Positionierungsformel-Vorlage · Kurztext für Website, Instagram und LinkedIn",
result: "Dein Angebot klar auf den Punkt — erklärt in einem Satz.",
title: "Was verkaufst du — und womit fängst du an?",
body: "Schluss mit 'Ich habe so viel anzubieten — wo fange ich an?' Du schaust auf alles was du anbietest und wählst EIN klares Startangebot aus: konkret, klar, sofort buchbar. Welches Problem löst es? Für wen? Was macht es wert? Ab jetzt weißt du genau: das ist mein erstes Produkt.",
vorlagen: "Startangebot-Vorlage · Angebotsportfolio-Canvas",
result: "EIN klares Startangebot — definiert, priorisiert, bereit zum Verkauf.",
},
],
},
@ -45,17 +45,17 @@ const MODULES: Module[] = [
weeks: [
{
week: "Woche 3",
title: "Deine Verkaufsseite",
body: "Du lernst wie eine Verkaufsseite aufgebaut sein muss — die 7 Pflicht-Elemente, ihre Reihenfolge, die Psychologie dahinter. Du bekommst die komplette Vorlage. Du setzt es direkt auf deiner Seite um — Katja gibt Feedback.",
vorlagen: "Verkaufsseiten-Vorlage + alle Textelemente · 7-Elemente-Checkliste",
result: "Vollständige Struktur für deine Verkaufsseite — fertig zum Umsetzen.",
title: "Angebot ausbauen + deine Positionierungsformel",
body: "Du baust dein Startangebot weiter aus: Was sind die Extras die es unwiderstehlich machen? Dann entwickelst du deine Positionierungsformel — für wen, welches Problem, was macht dich anders. In einem Satz. Du übst ihn live in der Gruppe.",
vorlagen: "Positionierungsformel-Vorlage · Kurztext für Website, Instagram und LinkedIn",
result: "Angebot vollständig ausgebaut — Positionierung in einem Satz, für alle Kanäle.",
},
{
week: "Woche 4",
title: "Deine Kanal-Strategie + 90-Tage-Marketingplan",
body: "Welche 2 Kanäle fokussierst du — auf Basis deiner Zielgruppe, nicht dem was gerade alle machen. Du baust deine Marketing-Systemkarte und startest deinen 90-Tage-Plan.",
vorlagen: "Kanal-Entscheidungsmatrix · Marketing-Systemkarte · 90-Tage-Marketingplan",
result: "Gewusst welche 2 Kanäle du fokussierst — und 90-Tage-Plan begonnen.",
title: "Verkaufsseite + Kanal-Strategie + 90-Tage-Marketingplan",
body: "Du bekommst die Struktur für deine Verkaufsseite — 7 Pflicht-Elemente, Vorlage, direkt einsetzbar. Dann entscheidest du: Welche 2 Kanäle fokussierst du auf Basis deiner Zielgruppe? Du baust deine Marketing-Systemkarte und startest deinen 90-Tage-Plan.",
vorlagen: "Verkaufsseiten-Vorlage · Kanal-Entscheidungsmatrix · Marketing-Systemkarte · 90-Tage-Marketingplan",
result: "Verkaufsseite strukturiert + 2 Kanäle entschieden + 90-Tage-Plan begonnen.",
},
],
},
@ -81,7 +81,7 @@ const MODULES: Module[] = [
},
{
week: "Woche 7",
expert: "Manuela Ludewig · Vertrieb",
expert: "Professioneller Vertriebscoach · TBA",
title: "Verkauf + Direktansprache",
body: "Dein persönliches Verkaufsskript für die Direktansprache — angepasst auf deine Positionierung. Du übst es live in der Gruppe mit konkretem Plan für deine nächsten Kontakte.",
vorlagen: "Persönliches Verkaufsskript · Follow-up-Template · Event-Recherche-Guide",
@ -89,11 +89,11 @@ const MODULES: Module[] = [
},
{
week: "Woche 8",
expert: "Katja Pestereva · Market Compass",
title: "Sichtbarkeit auf Google + Abschluss",
body: "Du optimierst dein Google Business Profil Schritt für Schritt in der Gruppe. Du bringst deine Website auf die Grundlagen die Google belohnt. Abschlussrunde: Was hat sich in 8 Wochen verändert?",
vorlagen: "SEO-Checkliste · Keyword-Recherche-Vorlage · Website-Leitfaden",
result: "Google Business vollständig optimiert + Seite für dein Haupt-Keyword.",
expert: "Pia Nestler · Pioneer Club",
title: "Finanzmanagement für Selbstständige",
body: "Du schaust zum ersten Mal klar auf deine Zahlen: Was gibst du aus? Was brauchst du wirklich — für ein gutes Leben, für Steuern, für Vermögensaufbau? Aus diesen Zahlen rechnest du rückwärts: Wie viele Kunden brauchst du — und zu welchem Preis? Das ist keine Buchhaltungs-Stunde. Das ist die Grundlage für dein Angebot und dein Leben als freier Unternehmer.",
vorlagen: "Leitfaden für Vermögensaufbau · Persönlicher Finanzmanagement-Rechner",
result: "Du kennst deine Zahl — weißt was du verdienen musst, und warum dein Preis stimmt.",
},
],
},

View file

@ -0,0 +1,877 @@
'use client'
import { useState } from 'react'
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, Cell,
} from 'recharts'
import { Todo } from '@/lib/notion-pm'
import { KritischData } from '../page'
// ── MC CI ─────────────────────────────────────────────────────────────────────
const MC_BLUE = '#3B82F6'
const MC_PURPLE = '#8B5CF6'
const KAT_COLOR: Record<string, string> = {
'Kundenarbeit': '#3B82F6',
'Produkt & Launch': '#8B5CF6',
'Marketing & Content': '#EC4899',
'Vertrieb & Akquise': '#F97316',
'Technik & Tools': '#6B7280',
'Finanzen & Recht': '#EAB308',
'Organisation & Admin': '#A16207',
'Privat': '#22C55E',
'Diana': '#EF4444',
'Me Time': '#94A3B8',
}
function kc(k: string | null) { return k ? (KAT_COLOR[k] ?? MC_BLUE) : MC_BLUE }
// ── Capacity constants ────────────────────────────────────────────────────────
// Business: MoFr 7h/Tag × 5 = 35h + WE Standard 5h = 40h/Woche
// Privat: MoFr 1.5h/Tag × 5 = 7.5h + WE 7h (34h/Tag) = 14.5h/Woche
const BUSINESS_CAP = 40
const PRIVAT_CAP = 14.5
const REAL_CAP = BUSINESS_CAP + PRIVAT_CAP // 54.5h gesamt (für Machbarkeit)
const BUSINESS_KAT = new Set([
'Kundenarbeit', 'Produkt & Launch', 'Marketing & Content',
'Vertrieb & Akquise', 'Technik & Tools', 'Finanzen & Recht', 'Organisation & Admin',
])
const PRIVAT_KAT = new Set(['Privat', 'Diana', 'Me Time'])
// ── Helpers ───────────────────────────────────────────────────────────────────
const isErledigt = (t: Todo) => (t.status ?? '').toLowerCase().includes('erledigt')
const daysFromNow = (d: string) => {
const now = new Date(); now.setHours(0,0,0,0)
return Math.floor((new Date(d).getTime() - now.getTime()) / 86_400_000)
}
const round1 = (n: number) => Math.round(n * 10) / 10
// Wochenplan-Filter: Geplanter Tag muss in den nächsten 7 Tagen liegen.
// Kein Geplanter Tag aber Im Wochenplan = true → manuell gesetzt, zählt trotzdem.
const isThisWeek = (t: Todo) => {
if (!t.geplanterTag) return t.imWochenplan
const d = daysFromNow(t.geplanterTag)
return d >= 0 && d <= 6
}
// ── Analytics ─────────────────────────────────────────────────────────────────
function kapazitaet(todos: Todo[]) {
const week = todos.filter(t => isThisWeek(t) && !isErledigt(t))
const businessH = round1(week.filter(t => BUSINESS_KAT.has(t.kategorie ?? '')).reduce((s,t) => s + (t.zeitH ?? 0), 0))
const privatH = round1(week.filter(t => PRIVAT_KAT.has(t.kategorie ?? '')).reduce((s,t) => s + (t.zeitH ?? 0), 0))
const businessPct = Math.round((businessH / BUSINESS_CAP) * 100)
const privatPct = Math.round((privatH / PRIVAT_CAP) * 100)
const bColor = businessPct <= 80 ? '#22C55E' : businessPct <= 100 ? '#EAB308' : '#EF4444'
const pColor = privatPct <= 80 ? '#22C55E' : privatPct <= 100 ? '#EAB308' : '#EF4444'
// legacy compat for other functions that use .geplant/.pct/.color/.label
const geplant = round1(businessH + privatH)
const pct = Math.round((geplant / REAL_CAP) * 100)
const color = pct <= 80 ? '#22C55E' : pct <= 100 ? '#EAB308' : '#EF4444'
const label = pct <= 80 ? 'Im Rahmen' : pct <= 100 ? 'Grenzwertig' : 'Überlastet'
return { businessH, privatH, businessPct, privatPct, bColor, pColor, geplant, pct, color, label }
}
function machbarkeit(todos: Todo[]) {
const week = todos
.filter(t => isThisWeek(t) && !isErledigt(t))
.sort((a,b) => {
// höchste Prio zuerst
const prio = (x: Todo) => x.prioritaet === 'Hoch' ? 0 : x.prioritaet === 'Mittel' ? 1 : 2
return prio(a) - prio(b)
})
let budget = REAL_CAP
const machbar: Todo[] = []
const overflow: Todo[] = []
for (const t of week) {
const h = t.zeitH ?? 0.5
if (budget >= h) { machbar.push(t); budget -= h }
else overflow.push(t)
}
const score = week.length === 0 ? 100
: Math.round((machbar.length / week.length) * 100)
return { machbar, overflow, score, restH: round1(budget) }
}
function energiebloecke(todos: Todo[]) {
const open = todos.filter(t => !isErledigt(t) && isThisWeek(t))
const fokus = open.filter(t => (t.zeitH ?? 0) >= 2 || t.prioritaet === 'Hoch')
const leicht = open.filter(t => !fokus.includes(t) && (t.zeitH ?? 1) < 1)
const mittel = open.filter(t => !fokus.includes(t) && !leicht.includes(t))
return { fokus, mittel, leicht }
}
function carryoverRate(todos: Todo[]) {
// Todos mit vergangener Deadline die NICHT erledigt sind
const withDeadline = todos.filter(t => t.deadline)
const overdue = withDeadline.filter(t => !isErledigt(t) && daysFromNow(t.deadline!) < 0)
const rate = withDeadline.length === 0 ? 0
: Math.round((overdue.length / withDeadline.length) * 100)
return { rate, overdueCount: overdue.length, total: withDeadline.length }
}
function deadlineRisiko(todos: Todo[]) {
const projekte: Record<string, {riskCount: number; total: number}> = {}
for (const t of todos.filter(t => !isErledigt(t))) {
if (!projekte[t.projekt]) projekte[t.projekt] = { riskCount: 0, total: 0 }
projekte[t.projekt].total++
if (t.deadline) {
const d = daysFromNow(t.deadline)
if (d < 0 || d <= 7) projekte[t.projekt].riskCount++
}
}
return Object.entries(projekte)
.map(([p, v]) => ({
projekt: p,
risikoPct: v.total === 0 ? 0 : Math.round((v.riskCount / v.total) * 100),
riskCount: v.riskCount,
}))
.filter(r => r.riskCount > 0)
.sort((a,b) => b.risikoPct - a.risikoPct)
.slice(0, 5)
}
interface AthenaEmpfehlung {
icon: string
text: string
color: string
items?: string[] // aufklappbare Liste — eine Aufgabe pro Zeile
}
// Sortiert nach nächstem anstehendem Datum (geplanterTag → deadline → hinten)
function sortByNextDate(a: Todo, b: Todo): number {
const dateOf = (t: Todo) => t.geplanterTag ?? t.deadline ?? null
const da = dateOf(a), db = dateOf(b)
if (da && db) return new Date(da).getTime() - new Date(db).getTime()
if (da) return -1
if (db) return 1
return 0
}
function athenaEmpfehlungen(todos: Todo[]): AthenaEmpfehlung[] {
const tips: AthenaEmpfehlung[] = []
const kap = kapazitaet(todos)
const mb = machbarkeit(todos)
const cr = carryoverRate(todos)
// 1. Überlastung
if (kap.pct > 100) {
const overflow = mb.overflow
if (overflow.length > 0) {
tips.push({
icon: '↩',
text: `${overflow.length} Todo${overflow.length > 1 ? 's' : ''} auf nächste Woche verschieben`,
color: '#EF4444',
items: overflow.map(t => `${t.name.slice(0,50)} (${t.zeitH ?? '?'}h)`),
})
}
}
// 2. Hohe Carry-over Rate
if (cr.rate > 30) {
tips.push({
icon: '⚡',
text: `Carry-over Rate ${cr.rate}% — ${cr.overdueCount} überfällige Todos priorisieren`,
color: '#EAB308',
})
}
// 3. Kundenarbeit unterrepräsentiert
const weekH = (kat: string) => todos
.filter(t => isThisWeek(t) && !isErledigt(t) && t.kategorie === kat)
.reduce((s,t) => s + (t.zeitH ?? 0), 0)
const kundenH = weekH('Kundenarbeit')
const totalWeekH = todos.filter(t => isThisWeek(t) && !isErledigt(t))
.reduce((s,t) => s + (t.zeitH ?? 0), 0)
if (totalWeekH > 0 && kundenH / totalWeekH < 0.25) {
tips.push({
icon: '👥',
text: `Kundenarbeit nur ${Math.round((kundenH/totalWeekH)*100)}% — prüfe ob Kunden-Todos fehlen`,
color: MC_BLUE,
})
}
// 4. Fokus-Todos ohne feste Zeit
const fokusOhneZeit = todos.filter(t =>
isThisWeek(t) && !isErledigt(t) && t.prioritaet === 'Hoch' && !t.zeitH
)
if (fokusOhneZeit.length > 0) {
tips.push({
icon: '🎯',
text: `${fokusOhneZeit.length} Hoch-Prio Todos ohne Zeitschätzung`,
color: MC_PURPLE,
items: fokusOhneZeit.map(t => t.name.slice(0, 52)),
})
}
// 5. Me Time fehlt
const meTodos = todos.filter(t => !isErledigt(t) && t.kategorie === 'Me Time')
if (meTodos.length === 0) {
tips.push({
icon: '🧘',
text: `Me Time Zeitslot noch offen — bitte definieren`,
color: '#94A3B8',
})
}
// 6. Business-Puffer: nächstanstehende Todos vorschlagen
const businessRest = round1(BUSINESS_CAP - kap.businessH)
if (businessRest >= 1) {
const kandidaten = todos
.filter(t => !isErledigt(t) && !isThisWeek(t) && BUSINESS_KAT.has(t.kategorie ?? '') && (t.zeitH ?? 0) <= businessRest)
.sort(sortByNextDate)
.slice(0, 8)
if (kandidaten.length > 0) {
tips.push({
icon: '',
text: `Business: ${businessRest}h Puffer frei — nächstanstehende Vorschläge`,
color: '#22C55E',
items: kandidaten.map(t => {
const datum = t.geplanterTag ?? t.deadline
const datumStr = datum ? ` · ${datum.slice(5)}` : ''
return `${t.name.slice(0,50)} (${t.zeitH ?? '?'}h${datumStr})`
}),
})
}
}
// 7. Privat-Puffer: nächstanstehende Todos vorschlagen
const privatRest = round1(PRIVAT_CAP - kap.privatH)
if (privatRest >= 0.5) {
const kandidaten = todos
.filter(t => !isErledigt(t) && !isThisWeek(t) && PRIVAT_KAT.has(t.kategorie ?? '') && (t.zeitH ?? 0) <= privatRest)
.sort(sortByNextDate)
.slice(0, 6)
if (kandidaten.length > 0) {
tips.push({
icon: '🏠',
text: `Privat: ${privatRest}h Puffer frei — nächstanstehende Vorschläge`,
color: '#8B5CF6',
items: kandidaten.map(t => {
const datum = t.geplanterTag ?? t.deadline
const datumStr = datum ? ` · ${datum.slice(5)}` : ''
return `${t.name.slice(0,50)} (${t.zeitH ?? '?'}h${datumStr})`
}),
})
}
}
// 8. Alles gut
if (tips.length === 0) {
tips.push({ icon: '✓', text: `Alles im Rahmen — Kapazität ${kap.pct}%, Carry-over ${cr.rate}%. Weiter so!`, color: '#22C55E' })
}
return tips
}
function kategorienData(todos: Todo[]) {
const map: Record<string, number> = {}
for (const t of todos) {
if (!t.kategorie || isErledigt(t)) continue
map[t.kategorie] = (map[t.kategorie] ?? 0) + (t.zeitH ?? 0)
}
return Object.entries(map)
.map(([name, stunden]) => ({ name, stunden: round1(stunden) }))
.sort((a,b) => b.stunden - a.stunden)
}
function top5(todos: Todo[]) {
return todos
.filter(t => !isErledigt(t) && t.deadline)
.sort((a,b) => new Date(a.deadline!).getTime() - new Date(b.deadline!).getTime())
.slice(0, 5)
.map(t => ({ ...t, daysLeft: daysFromNow(t.deadline!) }))
}
function fruehwarnung(todos: Todo[]) {
const active = todos.filter(t => !isErledigt(t) && t.deadline)
return {
overdue: active.filter(t => daysFromNow(t.deadline!) < 0),
upcoming: active.filter(t => { const d = daysFromNow(t.deadline!); return d >= 0 && d <= 3 }),
}
}
function zeitdifferenz(todos: Todo[]) {
const map: Record<string, {geplant: number; tatsaechlich: number}> = {}
for (const t of todos) {
if (!map[t.projekt]) map[t.projekt] = { geplant: 0, tatsaechlich: 0 }
map[t.projekt].geplant += t.zeitH ?? 0
map[t.projekt].tatsaechlich += t.tatsaechlicheZeitH ?? 0
}
return Object.entries(map)
.map(([p, v]) => ({
projekt: p,
geplant: round1(v.geplant),
tatsaechlich: round1(v.tatsaechlich),
diff: round1(v.geplant - v.tatsaechlich),
}))
.filter(r => r.geplant > 0 || r.tatsaechlich > 0)
}
function meilensteinKetten(todos: Todo[]) {
const map: Record<string, {projekt: string; todos: Todo[]; overdueCount: number}> = {}
for (const t of todos.filter(t => !isErledigt(t) && t.meilenstein)) {
const key = `${t.projekt} ${t.meilenstein}`
if (!map[key]) map[key] = { projekt: t.projekt, todos: [], overdueCount: 0 }
map[key].todos.push(t)
if (t.deadline && daysFromNow(t.deadline) < 0) map[key].overdueCount++
}
return Object.entries(map)
.map(([label, v]) => ({ label, ...v }))
.sort((a,b) => b.overdueCount - a.overdueCount)
.slice(0, 6)
}
// ── UI Primitives ─────────────────────────────────────────────────────────────
function Card({ children, className }: { children: React.ReactNode; className?: string }) {
return (
<div className={className} style={{ background:'#1e293b', border:'1px solid #334155', borderRadius:'0.75rem', padding:'1.25rem' }}>
{children}
</div>
)
}
function Label({ children }: { children: React.ReactNode }) {
return (
<p style={{ fontSize:'0.65rem', fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'#475569', marginBottom:'0.75rem', margin:'0 0 0.75rem' }}>
{children}
</p>
)
}
function Badge({ color, children }: { color: string; children: React.ReactNode }) {
return (
<span style={{ background: color+'22', color, fontSize:'0.65rem', fontWeight:700, padding:'0.1rem 0.4rem', borderRadius:'0.25rem', letterSpacing:'0.05em' }}>
{children}
</span>
)
}
// ── Section: Kapazität + Puffer ───────────────────────────────────────────────
function KapazitaetsAmpel({ todos }: { todos: Todo[] }) {
const { businessH, privatH, businessPct, privatPct, bColor, pColor } = kapazitaet(todos)
function Bar({ label, geplant, cap, pct, color, hint }: {
label: string; geplant: number; cap: number; pct: number; color: string; hint: string
}) {
const barW = Math.min(pct, 100)
const statusLabel = pct <= 80 ? 'Im Rahmen' : pct <= 100 ? 'Grenzwertig' : 'Überlastet'
return (
<div style={{ marginBottom:'0.875rem' }}>
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:'0.2rem' }}>
<span style={{ fontSize:'0.68rem', fontWeight:700, color:'#475569', textTransform:'uppercase', letterSpacing:'0.08em' }}>{label}</span>
<span style={{ fontSize:'0.65rem', color:'#475569' }}>{hint}</span>
</div>
<div style={{ display:'flex', alignItems:'baseline', gap:'0.4rem', marginBottom:'0.3rem' }}>
<span style={{ fontSize:'1.75rem', fontWeight:800, color }}>{geplant}h</span>
<span style={{ color:'#475569', fontSize:'0.8rem' }}>/ {cap}h</span>
</div>
<div style={{ background:'#0f172a', borderRadius:'9999px', height:'7px', marginBottom:'0.25rem', overflow:'hidden' }}>
<div style={{ width:`${barW}%`, height:'100%', background:color, borderRadius:'9999px', transition:'width 0.5s ease' }} />
</div>
<div style={{ display:'flex', justifyContent:'space-between' }}>
<span style={{ fontSize:'0.7rem', color, fontWeight:600 }}>{statusLabel} ({pct}%)</span>
<Badge color={color}>{pct <= 80 ? '🟢' : pct <= 100 ? '🟡' : '🔴'}</Badge>
</div>
</div>
)
}
return (
<Card>
<Label>Kapazität diese Woche</Label>
<Bar label="Business" geplant={businessH} cap={BUSINESS_CAP} pct={businessPct} color={bColor} hint="MoFr 35h + WE 5h" />
<Bar label="Privat" geplant={privatH} cap={PRIVAT_CAP} pct={privatPct} color={pColor} hint="MoFr 7.5h + WE 7h" />
<div style={{ background:'#0f172a', borderRadius:'0.5rem', padding:'0.4rem 0.75rem', fontSize:'0.68rem', color:'#475569', marginTop:'0.25rem' }}>
<div style={{ display:'grid', gridTemplateColumns:'1fr auto', gap:'0.2rem' }}>
<span>Business (35h MoFr + 5h WE)</span><span style={{ color:'#3B82F6' }}>{BUSINESS_CAP}h</span>
<span>Privat (7.5h MoFr + 7h WE)</span><span style={{ color:'#8B5CF6' }}>{PRIVAT_CAP}h</span>
<span>3 Wochenendtage/Monat frei</span><span style={{ color:'#64748b' }}>blockiert</span>
</div>
</div>
</Card>
)
}
// ── Section: Machbarkeitsanalyse ──────────────────────────────────────────────
function Machbarkeit({ todos }: { todos: Todo[] }) {
const { machbar, overflow, score } = machbarkeit(todos)
const scoreColor = score >= 80 ? '#22C55E' : score >= 50 ? '#EAB308' : '#EF4444'
const scoreLabel = score >= 80 ? 'Machbar' : score >= 50 ? 'Knapp' : 'Nicht machbar'
return (
<Card>
<Label>Machbarkeitsanalyse</Label>
<div style={{ display:'flex', alignItems:'center', gap:'1rem', marginBottom:'0.75rem' }}>
<div style={{ textAlign:'center' }}>
<div style={{ fontSize:'2rem', fontWeight:800, color:scoreColor }}>{score}%</div>
<div style={{ fontSize:'0.7rem', color:scoreColor, fontWeight:600 }}>{scoreLabel}</div>
</div>
<div style={{ flex:1 }}>
<div style={{ fontSize:'0.75rem', color:'#94a3b8', marginBottom:'0.25rem' }}>
<span style={{ color:'#22C55E', fontWeight:600 }}>{machbar.length} Todos</span> passen rein
{overflow.length > 0 && <span> · <span style={{ color:'#EF4444', fontWeight:600 }}>{overflow.length} fallen raus</span></span>}
</div>
<div style={{ background:'#0f172a', borderRadius:'9999px', height:'6px', overflow:'hidden' }}>
<div style={{ width:`${score}%`, height:'100%', background:scoreColor }} />
</div>
</div>
</div>
{overflow.length > 0 && (
<div>
<p style={{ fontSize:'0.65rem', color:'#EF4444', fontWeight:700, marginBottom:'0.375rem', textTransform:'uppercase', letterSpacing:'0.05em' }}>
Passt diese Woche nicht rein:
</p>
{overflow.slice(0, 4).map(t => (
<div key={t.id} style={{ display:'flex', justifyContent:'space-between', background:'#0f172a', borderRadius:'0.375rem', padding:'0.3rem 0.5rem', marginBottom:'0.25rem', fontSize:'0.75rem' }}>
<span style={{ color:'#f1f5f9' }}>{t.name.slice(0,42)}</span>
<span style={{ color:'#EF4444', fontWeight:600, marginLeft:'0.5rem', whiteSpace:'nowrap' }}>{t.zeitH ?? '?'}h</span>
</div>
))}
{overflow.length > 4 && <p style={{ fontSize:'0.7rem', color:'#475569', marginTop:'0.25rem' }}>+{overflow.length-4} weitere</p>}
</div>
)}
</Card>
)
}
// ── Section: ATHENA Empfehlungen ──────────────────────────────────────────────
function AthenaEmpfehlungen({ todos }: { todos: Todo[] }) {
const tips = athenaEmpfehlungen(todos)
const [expanded, setExpanded] = useState<Record<number, boolean>>({})
const SHOW = 3
return (
<Card>
<Label>ATHENA Empfehlungen</Label>
<div style={{ display:'flex', flexDirection:'column', gap:'0.5rem' }}>
{tips.map((tip, i) => {
const hasItems = tip.items && tip.items.length > 0
const isOpen = expanded[i] ?? false
const visible = hasItems ? (isOpen ? tip.items! : tip.items!.slice(0, SHOW)) : []
const hiddenCount = hasItems ? tip.items!.length - SHOW : 0
return (
<div key={i} style={{ borderLeft:`3px solid ${tip.color}`, background:'#0f172a', borderRadius:'0 0.375rem 0.375rem 0', padding:'0.5rem 0.625rem 0.5rem 0.75rem' }}>
<div style={{ display:'flex', gap:'0.5rem', alignItems:'flex-start' }}>
<span style={{ fontSize:'0.9rem', flexShrink:0 }}>{tip.icon}</span>
<span style={{ fontSize:'0.78rem', color:'#cbd5e1', lineHeight:1.4, flex:1 }}>{tip.text}</span>
</div>
{hasItems && (
<div style={{ marginTop:'0.375rem', paddingLeft:'1.5rem' }}>
{visible.map((item, j) => (
<div key={j} style={{ fontSize:'0.72rem', color:'#94a3b8', padding:'0.2rem 0', borderTop:'1px solid #1e293b', lineHeight:1.4 }}>
{item}
</div>
))}
{hiddenCount > 0 && !isOpen && (
<button
onClick={() => setExpanded(e => ({ ...e, [i]: true }))}
style={{ fontSize:'0.68rem', color:tip.color, background:'none', border:'none', cursor:'pointer', padding:'0.2rem 0', marginTop:'0.1rem' }}
>
+{hiddenCount} weitere anzeigen
</button>
)}
{isOpen && tip.items!.length > SHOW && (
<button
onClick={() => setExpanded(e => ({ ...e, [i]: false }))}
style={{ fontSize:'0.68rem', color:'#475569', background:'none', border:'none', cursor:'pointer', padding:'0.2rem 0', marginTop:'0.1rem' }}
>
weniger anzeigen
</button>
)}
</div>
)}
</div>
)
})}
</div>
</Card>
)
}
// ── Section: Predictive Analytics ────────────────────────────────────────────
function PredictiveAnalytics({ todos }: { todos: Todo[] }) {
const cr = carryoverRate(todos)
const risks = deadlineRisiko(todos)
const trend = cr.rate === 0 ? { label:'Kein Rückstand', color:'#22C55E', icon:'↗' }
: cr.rate < 20 ? { label:'Stabil', color:'#22C55E', icon:'→' }
: cr.rate < 40 ? { label:'Leichter Rückstand', color:'#EAB308', icon:'↘' }
: { label:'Kritischer Rückstand', color:'#EF4444', icon:'↓' }
return (
<Card>
<Label>Predictive Analytics</Label>
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:'0.75rem', marginBottom:'0.75rem' }}>
{/* Carry-over Rate */}
<div style={{ background:'#0f172a', borderRadius:'0.5rem', padding:'0.625rem' }}>
<p style={{ fontSize:'0.65rem', color:'#475569', margin:'0 0 0.25rem', textTransform:'uppercase', letterSpacing:'0.05em' }}>Carry-over Rate</p>
<p style={{ fontSize:'1.5rem', fontWeight:800, color: cr.rate === 0 ? '#22C55E' : cr.rate < 30 ? '#EAB308' : '#EF4444', margin:0 }}>{cr.rate}%</p>
<p style={{ fontSize:'0.65rem', color:'#64748b', margin:'0.1rem 0 0' }}>{cr.overdueCount} / {cr.total} Todos überfällig</p>
</div>
{/* Trend */}
<div style={{ background:'#0f172a', borderRadius:'0.5rem', padding:'0.625rem' }}>
<p style={{ fontSize:'0.65rem', color:'#475569', margin:'0 0 0.25rem', textTransform:'uppercase', letterSpacing:'0.05em' }}>Trend</p>
<p style={{ fontSize:'1.5rem', fontWeight:800, color:trend.color, margin:0 }}>{trend.icon}</p>
<p style={{ fontSize:'0.65rem', color:trend.color, margin:'0.1rem 0 0', fontWeight:600 }}>{trend.label}</p>
</div>
</div>
{/* Deadline-Risiko je Projekt */}
{risks.length > 0 && (
<>
<p style={{ fontSize:'0.65rem', color:'#475569', textTransform:'uppercase', letterSpacing:'0.05em', margin:'0 0 0.375rem' }}>Deadline-Risiko je Projekt</p>
{risks.map(r => (
<div key={r.projekt} style={{ display:'flex', justifyContent:'space-between', alignItems:'center', background:'#0f172a', borderRadius:'0.375rem', padding:'0.3rem 0.5rem', marginBottom:'0.25rem' }}>
<span style={{ fontSize:'0.75rem', color:'#cbd5e1' }}>{r.projekt}</span>
<div style={{ display:'flex', alignItems:'center', gap:'0.375rem' }}>
<div style={{ background:'#334155', borderRadius:'9999px', width:'60px', height:'5px', overflow:'hidden' }}>
<div style={{ width:`${r.risikoPct}%`, height:'100%', background: r.risikoPct > 50 ? '#EF4444' : '#EAB308' }} />
</div>
<span style={{ fontSize:'0.7rem', color: r.risikoPct > 50 ? '#EF4444' : '#EAB308', fontWeight:700, width:'2.5rem', textAlign:'right' }}>{r.risikoPct}%</span>
</div>
</div>
))}
</>
)}
</Card>
)
}
// ── Section: Energieblöcke ────────────────────────────────────────────────────
function Energiebloecke({ todos }: { todos: Todo[] }) {
const { fokus, mittel, leicht } = energiebloecke(todos)
function Block({ label, color, items, hint }: { label:string; color:string; items:Todo[]; hint:string }) {
return (
<div style={{ flex:1 }}>
<div style={{ display:'flex', alignItems:'center', gap:'0.375rem', marginBottom:'0.375rem' }}>
<div style={{ width:'10px', height:'10px', borderRadius:'50%', background:color, flexShrink:0 }} />
<span style={{ fontSize:'0.7rem', fontWeight:700, color, textTransform:'uppercase', letterSpacing:'0.08em' }}>{label}</span>
<span style={{ fontSize:'0.65rem', color:'#475569' }}>({items.length})</span>
</div>
<div style={{ fontSize:'0.65rem', color:'#475569', marginBottom:'0.375rem' }}>{hint}</div>
{items.slice(0,3).map(t => (
<div key={t.id} style={{ background:'#0f172a', borderRadius:'0.375rem', padding:'0.25rem 0.5rem', marginBottom:'0.2rem', display:'flex', justifyContent:'space-between', gap:'0.5rem' }}>
<span style={{ fontSize:'0.72rem', color:'#94a3b8' }}>{t.name.slice(0,32)}</span>
<span style={{ fontSize:'0.65rem', color:'#475569', whiteSpace:'nowrap' }}>{t.zeitH ? `${t.zeitH}h` : '?h'}</span>
</div>
))}
{items.length > 3 && <p style={{ fontSize:'0.65rem', color:'#334155', margin:'0.2rem 0 0 0.5rem' }}>+{items.length-3} weitere</p>}
</div>
)
}
return (
<Card>
<Label>Energieblöcke (Wochenplan)</Label>
<div style={{ display:'flex', gap:'0.75rem' }}>
<Block label="Fokus" color="#EF4444" items={fokus} hint="≥2h · Hoch-Prio · morgens" />
<Block label="Mittel" color="#EAB308" items={mittel} hint="12h · Mittel-Prio" />
<Block label="Leicht" color="#22C55E" items={leicht} hint="<1h · flexibel" />
</div>
</Card>
)
}
// ── Section: Abhängigkeits-Tracker ────────────────────────────────────────────
function AbhaengigkeitsTracker({ todos }: { todos: Todo[] }) {
const ketten = meilensteinKetten(todos)
return (
<Card>
<Label>Abhängigkeits-Tracker (Meilenstein-Ketten)</Label>
{ketten.length === 0
? <p style={{ color:'#475569', fontSize:'0.875rem' }}>Keine offenen Meilensteine</p>
: ketten.map(k => (
<div key={k.label} style={{ marginBottom:'0.625rem' }}>
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'0.25rem' }}>
<span style={{ fontSize:'0.75rem', color:'#94a3b8', fontWeight:600 }}>{k.label}</span>
{k.overdueCount > 0 && <Badge color="#EF4444">{k.overdueCount} überfällig</Badge>}
</div>
<div style={{ paddingLeft:'0.5rem', borderLeft:'2px solid #334155' }}>
{k.todos.slice(0,3).map(t => (
<div key={t.id} style={{ display:'flex', alignItems:'center', gap:'0.375rem', marginBottom:'0.2rem' }}>
<span style={{ width:'6px', height:'6px', borderRadius:'50%', background: t.deadline && daysFromNow(t.deadline) < 0 ? '#EF4444' : '#334155', flexShrink:0, display:'block' }} />
<span style={{ fontSize:'0.72rem', color:'#64748b' }}>{t.name.slice(0,44)}</span>
</div>
))}
{k.todos.length > 3 && <p style={{ fontSize:'0.65rem', color:'#334155', margin:'0.1rem 0 0 0.875rem' }}>+{k.todos.length-3} weitere</p>}
</div>
</div>
))
}
</Card>
)
}
// ── Section: Kategorien-Chart ─────────────────────────────────────────────────
function KategorienChart({ todos }: { todos: Todo[] }) {
const data = kategorienData(todos)
return (
<Card>
<Label>Stunden je Kategorie (offen)</Label>
{data.length === 0
? <p style={{ color:'#475569', fontSize:'0.875rem' }}>Keine Daten</p>
: <ResponsiveContainer width="100%" height={240}>
<BarChart data={data} margin={{ top:4, right:8, left:-16, bottom:60 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#0f172a" />
<XAxis dataKey="name" tick={{ fontSize:9, fill:'#64748b' }} angle={-40} textAnchor="end" interval={0} />
<YAxis tick={{ fontSize:9, fill:'#64748b' }} unit="h" />
<Tooltip contentStyle={{ background:'#1e293b', border:'1px solid #334155', borderRadius:'0.5rem' }} labelStyle={{ color:'#f1f5f9', fontWeight:600 }} itemStyle={{ color:'#94a3b8' }} formatter={(v) => [`${v}h`, 'Stunden']} />
<Bar dataKey="stunden" radius={[4,4,0,0]}>
{data.map(e => <Cell key={e.name} fill={kc(e.name)} />)}
</Bar>
</BarChart>
</ResponsiveContainer>
}
</Card>
)
}
// ── Section: Top 5 Deadlines ──────────────────────────────────────────────────
function Top5({ todos }: { todos: Todo[] }) {
const items = top5(todos)
function dl(days: number) {
if (days < 0) return { text:`${Math.abs(days)}d überfällig`, color:'#EF4444' }
if (days === 0) return { text:'Heute', color:'#EAB308' }
if (days <= 3) return { text:`in ${days}d`, color:'#EAB308' }
return { text:`in ${days}d`, color:'#22C55E' }
}
return (
<Card>
<Label>Top 5 nächste Deadlines</Label>
{items.length === 0
? <p style={{ color:'#475569', fontSize:'0.875rem' }}>Keine offenen Todos mit Deadline</p>
: items.map((t,i) => {
const d = dl(t.daysLeft)
return (
<div key={t.id} style={{ display:'flex', gap:'0.625rem', background:'#0f172a', borderRadius:'0.5rem', padding:'0.5rem 0.625rem', marginBottom:'0.375rem' }}>
<span style={{ fontSize:'0.7rem', fontWeight:700, color:'#475569', width:'1rem', flexShrink:0 }}>{i+1}.</span>
<div style={{ flex:1, minWidth:0 }}>
<p style={{ color:'#f1f5f9', fontSize:'0.78rem', fontWeight:600, margin:0, lineHeight:1.3 }}>{t.name.slice(0,48)}</p>
<p style={{ color:'#475569', fontSize:'0.7rem', margin:'0.15rem 0 0' }}>{t.projekt}{t.meilenstein ? ` · ${t.meilenstein}` : ''}</p>
</div>
<span style={{ fontSize:'0.7rem', fontWeight:700, color:d.color, flexShrink:0, whiteSpace:'nowrap' }}>{d.text}</span>
</div>
)
})
}
</Card>
)
}
// ── Section: Frühwarnsystem ───────────────────────────────────────────────────
function Fruehwarnsystem({ todos }: { todos: Todo[] }) {
const { overdue, upcoming } = fruehwarnung(todos)
const kap = kapazitaet(todos)
const warnings = []
if (overdue.length > 0) warnings.push({ text:`${overdue.length} überfällige Todos`, color:'#EF4444' })
if (upcoming.length > 0) warnings.push({ text:`${upcoming.length} Todos fällig in ≤ 3 Tagen`, color:'#EAB308' })
if (kap.pct > 100) warnings.push({ text:`Überlastet: ${kap.pct}% der Kapazität`, color:'#EF4444' })
else if (kap.pct > 80) warnings.push({ text:`Grenzwertig: ${kap.pct}% der Kapazität`, color:'#EAB308' })
return (
<Card>
<Label>Frühwarnsystem</Label>
{warnings.length === 0
? <p style={{ color:'#22C55E', fontSize:'0.875rem' }}> Alles im Rahmen</p>
: <div style={{ display:'flex', flexDirection:'column', gap:'0.375rem', marginBottom:'0.75rem' }}>
{warnings.map((w,i) => (
<div key={i} style={{ borderLeft:`3px solid ${w.color}`, paddingLeft:'0.625rem', background:'#0f172a', borderRadius:'0 0.375rem 0.375rem 0', padding:'0.375rem 0.5rem 0.375rem 0.625rem', fontSize:'0.78rem', color:w.color }}>
{w.text}
</div>
))}
</div>
}
{overdue.slice(0,3).map(t => (
<p key={t.id} style={{ fontSize:'0.72rem', color:'#EF4444', margin:'0.1rem 0' }}>· {t.name.slice(0,46)} ({t.projekt})</p>
))}
</Card>
)
}
// ── Section: Diana ────────────────────────────────────────────────────────────
function DianaSection({ todos }: { todos: Todo[] }) {
const diana = todos.filter(t => t.kategorie === 'Diana' && !isErledigt(t))
if (diana.length === 0) return null
return (
<Card>
<Label>Diana</Label>
{diana.map(t => (
<div key={t.id} style={{ display:'flex', justifyContent:'space-between', background:'#0f172a', borderRadius:'0.375rem', padding:'0.3rem 0.5rem', marginBottom:'0.25rem', fontSize:'0.75rem' }}>
<span style={{ color:'#f1f5f9' }}>{t.name.slice(0,48)}</span>
{t.deadline && <span style={{ color: daysFromNow(t.deadline) < 0 ? '#EF4444' : '#94a3b8', fontSize:'0.7rem', whiteSpace:'nowrap', marginLeft:'0.5rem' }}>{t.deadline.slice(5)}</span>}
</div>
))}
</Card>
)
}
// ── Section: Me Time ──────────────────────────────────────────────────────────
function MeTime({ todos }: { todos: Todo[] }) {
const meTodos = todos.filter(t => t.kategorie === 'Me Time' && !isErledigt(t))
return (
<Card>
<Label>Me Time</Label>
<div style={{ background:'#EAB30822', border:'1px solid #EAB30866', borderRadius:'0.5rem', padding:'0.625rem 0.75rem', marginBottom:'0.5rem' }}>
<p style={{ fontSize:'0.85rem', fontWeight:700, color:'#EAB308', margin:'0 0 0.2rem' }}> Noch nicht definiert</p>
<p style={{ fontSize:'0.7rem', color:'#92700a', margin:0 }}>Zeitslot für Me Time ist noch offen bitte planen</p>
</div>
{meTodos.length === 0
? <p style={{ fontSize:'0.72rem', color:'#475569' }}>Keine Me-Time-Todos diese Woche</p>
: <p style={{ fontSize:'0.72rem', color:'#22C55E' }}>{meTodos.length} Me-Time-Todos eingetragen</p>
}
</Card>
)
}
// ── Section: Zeitdifferenz ────────────────────────────────────────────────────
function Zeitdifferenz({ todos }: { todos: Todo[] }) {
const rows = zeitdifferenz(todos).sort((a,b) => Math.abs(b.diff) - Math.abs(a.diff))
return (
<Card>
<Label>Zeitdifferenz je Projekt</Label>
{rows.map(r => {
const dc = r.diff >= 0 ? '#22C55E' : '#EF4444'
const ds = r.diff >= 0 ? '+' : ''
return (
<div key={r.projekt} style={{ display:'grid', gridTemplateColumns:'1fr auto auto auto', gap:'0.5rem', alignItems:'center', background:'#0f172a', borderRadius:'0.375rem', padding:'0.3rem 0.5rem', marginBottom:'0.2rem', fontSize:'0.72rem' }}>
<span style={{ color:'#cbd5e1', fontWeight:500 }}>{r.projekt}</span>
<span style={{ color:'#475569' }}>{r.geplant}h</span>
<span style={{ color:'#475569' }}>{r.tatsaechlich}h</span>
<span style={{ color:dc, fontWeight:700, textAlign:'right' }}>{ds}{r.diff}h</span>
</div>
)
})}
<div style={{ display:'grid', gridTemplateColumns:'1fr auto auto auto', gap:'0.5rem', padding:'0.2rem 0.5rem', fontSize:'0.62rem', color:'#334155' }}>
<span /><span>Geplant</span><span>Tatsächl.</span><span>Diff</span>
</div>
</Card>
)
}
// ── Section: Kritische Stellen ────────────────────────────────────────────────
function KritischeStellen({ data }: { data: KritischData }) {
const uhrzeit = new Date(data.stand).toLocaleString('de-DE', {
day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit'
})
if (data.allesOk) {
return (
<div style={{ background:'#052e16', border:'1px solid #166534', borderRadius:'0.75rem', padding:'0.875rem 1.25rem', display:'flex', justifyContent:'space-between', alignItems:'center' }}>
<div style={{ display:'flex', alignItems:'center', gap:'0.625rem' }}>
<span style={{ fontSize:'1rem' }}></span>
<span style={{ fontSize:'0.85rem', fontWeight:700, color:'#22C55E' }}>Alles im Rahmen keine kritischen Stellen</span>
</div>
<span style={{ fontSize:'0.65rem', color:'#166534' }}>Stand: {uhrzeit} Uhr</span>
</div>
)
}
const farbenMap: Record<string, string> = { rot: '#EF4444', gelb: '#EAB308' }
const typIcon: Record<string, string> = {
ueberfallig: '🔴', deadline: '⚠', meilenstein: '🎯', kapazitaet: '📊'
}
return (
<div style={{ background:'#1a0a0a', border:'1px solid #7f1d1d', borderRadius:'0.75rem', padding:'0.875rem 1.25rem' }}>
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'0.625rem' }}>
<span style={{ fontSize:'0.72rem', fontWeight:700, letterSpacing:'0.1em', textTransform:'uppercase', color:'#EF4444' }}>
Kritische Stellen ({data.kritisch.length})
</span>
<span style={{ fontSize:'0.65rem', color:'#7f1d1d' }}>Stand: {uhrzeit} Uhr</span>
</div>
<div style={{ display:'flex', flexDirection:'column', gap:'0.375rem' }}>
{data.kritisch.map((e, i) => {
const color = farbenMap[e.farbe] ?? '#EF4444'
return (
<div key={i} style={{ display:'flex', alignItems:'flex-start', gap:'0.625rem', background:'#0f172a', borderRadius:'0.375rem', padding:'0.4rem 0.625rem', borderLeft:`3px solid ${color}` }}>
<span style={{ fontSize:'0.8rem', flexShrink:0 }}>{typIcon[e.typ] ?? '⚠'}</span>
<div style={{ flex:1, minWidth:0 }}>
<span style={{ fontSize:'0.72rem', color:'#94a3b8', fontWeight:600, marginRight:'0.375rem' }}>{e.projekt}</span>
<span style={{ fontSize:'0.72rem', color:'#cbd5e1' }}>{e.text}</span>
</div>
</div>
)
})}
</div>
</div>
)
}
// ── Main ──────────────────────────────────────────────────────────────────────
export default function DashboardClient({ todos, fetchedAt, kritischData }: { todos: Todo[]; fetchedAt: string; kritischData: KritischData }) {
const totalOpen = todos.filter(t => !isErledigt(t)).length
const uhrzeit = new Date(fetchedAt).toLocaleTimeString('de-DE', { hour:'2-digit', minute:'2-digit' })
return (
<div style={{ minHeight:'100vh', background:'#0f172a', fontFamily:'var(--font-geist-sans, system-ui, sans-serif)', color:'#f1f5f9' }}>
{/* Header */}
<div style={{ background:`linear-gradient(135deg, ${MC_BLUE} 0%, ${MC_PURPLE} 100%)`, padding:'1.25rem 2rem' }}>
<div style={{ maxWidth:'1400px', margin:'0 auto', display:'flex', justifyContent:'space-between', alignItems:'flex-end', flexWrap:'wrap', gap:'0.5rem' }}>
<div>
<h1 style={{ fontSize:'1.4rem', fontWeight:900, color:'#fff', margin:0, letterSpacing:'-0.02em' }}>ATHENA PM Dashboard</h1>
<p style={{ color:'rgba(255,255,255,0.7)', fontSize:'0.75rem', margin:'0.2rem 0 0' }}>Market Compass · Live Notion PM</p>
</div>
<div style={{ textAlign:'right' }}>
<p style={{ color:'rgba(255,255,255,0.6)', fontSize:'0.7rem', margin:0 }}>Aktualisiert: {uhrzeit} Uhr</p>
<p style={{ color:'rgba(255,255,255,0.6)', fontSize:'0.7rem', margin:'0.1rem 0 0' }}>{totalOpen} offene Todos · 10 Projekte</p>
</div>
</div>
</div>
{/* Grid */}
<div style={{ maxWidth:'1400px', margin:'0 auto', padding:'1.5rem 2rem', display:'flex', flexDirection:'column', gap:'1rem' }}>
{/* Row 0: Kritische Stellen */}
<KritischeStellen data={kritischData} />
{/* Row 1: Kapazität + Machbarkeit + ATHENA Empfehlungen */}
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:'1rem' }}>
<KapazitaetsAmpel todos={todos} />
<Machbarkeit todos={todos} />
<AthenaEmpfehlungen todos={todos} />
</div>
{/* Row 2: Predictive Analytics + Energieblöcke + Me Time */}
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:'1rem' }}>
<PredictiveAnalytics todos={todos} />
<Energiebloecke todos={todos} />
<div style={{ display:'flex', flexDirection:'column', gap:'1rem' }}>
<MeTime todos={todos} />
<DianaSection todos={todos} />
</div>
</div>
{/* Row 3: Kategorien-Chart + Top 5 */}
<div style={{ display:'grid', gridTemplateColumns:'2fr 1fr', gap:'1rem' }}>
<KategorienChart todos={todos} />
<Top5 todos={todos} />
</div>
{/* Row 4: Frühwarnsystem + Zeitdifferenz + Abhängigkeiten */}
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:'1rem' }}>
<Fruehwarnsystem todos={todos} />
<Zeitdifferenz todos={todos} />
<AbhaengigkeitsTracker todos={todos} />
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,42 @@
import { readFileSync } from 'fs'
import { join } from 'path'
import { fetchAllTodos } from '@/lib/notion-pm'
import DashboardClient from './components/DashboardClient'
export const revalidate = 300
export const metadata = {
title: 'ATHENA PM Dashboard | Market Compass',
}
export interface KritischEintrag {
typ: 'deadline' | 'meilenstein' | 'kapazitaet' | 'ueberfallig'
projekt: string
text: string
farbe: 'rot' | 'gelb'
}
export interface KritischData {
stand: string
kritisch: KritischEintrag[]
allesOk: boolean
}
function loadKritischData(): KritischData {
try {
const filePath = join(process.cwd(), 'public', 'pm-kritisch.json')
const raw = readFileSync(filePath, 'utf-8')
return JSON.parse(raw) as KritischData
} catch {
return { stand: new Date().toISOString(), kritisch: [], allesOk: true }
}
}
export default async function DashboardPage() {
const [todos, kritischData] = await Promise.all([
fetchAllTodos(),
Promise.resolve(loadKritischData()),
])
const timestamp = new Date().toISOString()
return <DashboardClient todos={todos} fetchedAt={timestamp} kritischData={kritischData} />
}

View file

@ -10,8 +10,8 @@ const urbanist = Urbanist({
});
export const metadata: Metadata = {
title: "Das 8-Wochen Kundengewinnungs-System für Selbstständige | Market Compass",
description: "In 8 Wochen zu deinem eigenen Kundengewinnungs-System — speziell für Selbstständige. Mit Katja Pestereva von Market Compass und 4 Experten aus Vertrieb, Social Media & Video.",
title: "Das 8-Wochen Praxis-Bootcamp: dein Kundengewinnungs-System für Selbstständige | Market Compass",
description: "Das 8-Wochen Praxis-Bootcamp für Selbstständige — mit Katja Pestereva von Market Compass und 4 Experten aus Vertrieb, Social Media & Video.",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {

50
src/app/olga/layout.tsx Normal file
View file

@ -0,0 +1,50 @@
import type { Metadata } from "next"
import { Cormorant_Garamond, DM_Sans } from "next/font/google"
const cormorant = Cormorant_Garamond({
variable: "--font-cormorant",
subsets: ["latin"],
weight: ["300", "400", "500", "600"],
style: ["normal", "italic"],
})
const dmSans = DM_Sans({
variable: "--font-dm-sans",
subsets: ["latin"],
weight: ["300", "400", "500", "600"],
})
export const metadata: Metadata = {
title: "Skin Signal Deep Scan — Olga Izieva",
description:
"In 90 Minuten weißt du endlich, was dein Körper dir die ganze Zeit sagen wollte. Skin Signal Deep Scan von Olga Izieva — systemische Körperanalyse für Frauen 35+.",
}
export default function OlgaLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div
className={`${cormorant.variable} ${dmSans.variable}`}
style={{
fontFamily: "var(--font-dm-sans, system-ui, sans-serif)",
background: "#F0EBE1",
minHeight: "100vh",
}}
>
<style>{`
.olga-bio-grid {
grid-template-columns: 200px 1fr !important;
}
@media (max-width: 600px) {
.olga-bio-grid {
grid-template-columns: 1fr !important;
}
}
`}</style>
{children}
</div>
)
}

640
src/app/olga/page.tsx Normal file
View file

@ -0,0 +1,640 @@
"use client"
import { useState } from "react"
import Image from "next/image"
const C = {
dark: "#1C2420",
green: "#2D4A3E",
greenLight: "#3D5C4E",
cognac: "#A06040",
cognacLight: "#C4845A",
parchment: "#F0EBE1",
parchmentMid: "#E8E0D4",
parchmentDark: "#DDD4C4",
brown: "#1A1210",
textDark: "#2A2218",
textMuted: "#7A6E62",
white: "#FDFAF6",
sage: "#EBF0ED",
}
function StrataDecor({ color = C.cognac, opacity = 0.2 }: { color?: string; opacity?: number }) {
return (
<svg width="280" height="40" viewBox="0 0 280 40" fill="none" aria-hidden="true">
<path d="M0 10 Q70 5 140 12 Q210 19 280 8" stroke={color} strokeWidth="1.5" fill="none" opacity={opacity + 0.08} />
<path d="M0 20 Q70 15 140 22 Q210 29 280 18" stroke={color} strokeWidth="1" fill="none" opacity={opacity} />
<path d="M0 30 Q70 25 140 32 Q210 39 280 28" stroke={color} strokeWidth="0.75" fill="none" opacity={opacity - 0.08} />
</svg>
)
}
const body: React.CSSProperties = {
fontFamily: "var(--font-dm-sans, system-ui, sans-serif)",
color: C.textDark,
lineHeight: 1.7,
}
const serif: React.CSSProperties = {
fontFamily: "var(--font-cormorant, Georgia, serif)",
}
export default function OlgaDeepScanPage() {
const [formData, setFormData] = useState({ name: "", email: "", phone: "", message: "" })
const [submitted, setSubmitted] = useState(false)
function handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }))
}
function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setSubmitted(true)
}
return (
<main style={body}>
{/* ── HEADER ─────────────────────────────────────────────── */}
<header style={{ background: C.dark, padding: "14px 32px" }}>
<div style={{ maxWidth: 760, margin: "0 auto" }}>
<p style={{
...serif,
color: C.parchment,
fontSize: "0.95rem",
letterSpacing: "0.14em",
textTransform: "uppercase",
margin: 0,
opacity: 0.75,
}}>
Olga Izieva &nbsp;·&nbsp; Skin Signal Method
</p>
</div>
</header>
{/* ── HERO ───────────────────────────────────────────────── */}
<section style={{ background: C.green, padding: "88px 32px 80px" }}>
<div style={{ maxWidth: 720, margin: "0 auto" }}>
<span style={{
display: "inline-block",
background: C.cognac,
color: C.white,
fontSize: "0.7rem",
fontWeight: 600,
letterSpacing: "0.12em",
textTransform: "uppercase",
padding: "5px 14px",
borderRadius: 2,
marginBottom: 28,
}}>
Nur 5 Plätze verfügbar
</span>
<h1 style={{
...serif,
color: C.white,
fontSize: "clamp(2rem, 5vw, 3rem)",
fontWeight: 500,
lineHeight: 1.2,
marginBottom: 24,
maxWidth: 600,
}}>
In 90 Minuten weißt du endlich, was dein Körper dir die ganze Zeit sagen wollte
</h1>
<p style={{
color: C.parchment,
fontSize: "1.05rem",
lineHeight: 1.75,
marginBottom: 12,
maxWidth: 540,
opacity: 0.88,
}}>
Der Skin Signal Deep Scan eine systemische Körperanalyse, die Haut, Hormone, Darm, Schlaf und Stress als ein zusammenhängendes Bild liest.
</p>
<p style={{
color: C.parchment,
fontSize: "0.85rem",
marginBottom: 44,
opacity: 0.6,
}}>
Olga Izieva &nbsp;·&nbsp; 33 Jahre klinische Erfahrung &nbsp;·&nbsp; Heilpraktikerin &nbsp;·&nbsp; Medical Cosmetology
</p>
<a
href="#termin"
style={{
display: "inline-block",
background: C.cognac,
color: C.white,
padding: "15px 38px",
fontSize: "0.95rem",
fontWeight: 600,
textDecoration: "none",
borderRadius: 3,
letterSpacing: "0.04em",
}}
>
Meinen Platz sichern
</a>
<p style={{ color: C.parchment, fontSize: "0.8rem", marginTop: 14, opacity: 0.55 }}>
Pilotpreis 197 &nbsp;·&nbsp; persönlich bei Olga &nbsp;·&nbsp; 5 Plätze
</p>
<div style={{ marginTop: 48, opacity: 0.7 }}>
<StrataDecor color={C.parchment} opacity={0.12} />
</div>
</div>
</section>
{/* ── PROBLEM ────────────────────────────────────────────── */}
<section style={{ background: C.white, padding: "80px 32px" }}>
<div style={{ maxWidth: 720, margin: "0 auto" }}>
<p style={{ color: C.cognac, fontSize: "0.72rem", fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", marginBottom: 16 }}>
Kommt dir das bekannt vor?
</p>
<h2 style={{
...serif,
color: C.dark,
fontSize: "clamp(1.7rem, 3.5vw, 2.4rem)",
fontWeight: 500,
lineHeight: 1.25,
marginBottom: 48,
maxWidth: 560,
}}>
Du tust alles richtig und trotzdem stimmt irgendetwas nicht.
</h2>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 24 }}>
{[
["Haut die müde wirkt", "Trotz Pflege, Supplements und clean eating — sie strahlt einfach nicht mehr so wie früher."],
["Ärzte sagen: alles normal", "Die Blutwerte sind unauffällig. Aber du fühlst, dass etwas nicht stimmt — und niemand kann dir sagen was."],
["Du hast schon alles probiert", "Keto, Yoga, Schlaftracking, Vitamin D, Kollagen. Jedes Mal kurze Besserung. Dann wieder Ausgangszustand."],
["Niemand sieht das Gesamtbild", "Jeder schaut auf seinen Bereich. Haut oder Hormone oder Darm. Nie alles zusammen. Nie du."],
].map(([title, text], i) => (
<div key={i} style={{ padding: "24px 0", borderTop: `1.5px solid ${C.parchmentDark}` }}>
<div style={{
...serif,
color: C.cognac,
fontSize: "1.6rem",
fontWeight: 400,
marginBottom: 10,
opacity: 0.5,
}}>
{String(i + 1).padStart(2, "0")}
</div>
<p style={{ fontWeight: 600, color: C.textDark, fontSize: "0.95rem", marginBottom: 6 }}>{title}</p>
<p style={{ color: C.textMuted, fontSize: "0.88rem", lineHeight: 1.65 }}>{text}</p>
</div>
))}
</div>
</div>
</section>
{/* ── EPIPHANY ───────────────────────────────────────────── */}
<section style={{ background: C.sage, padding: "80px 32px" }}>
<div style={{ maxWidth: 720, margin: "0 auto" }}>
<StrataDecor color={C.green} opacity={0.16} />
<div style={{ height: 40 }} />
<blockquote style={{
...serif,
color: C.green,
fontSize: "clamp(1.6rem, 4vw, 2.6rem)",
fontStyle: "italic",
fontWeight: 400,
lineHeight: 1.35,
marginBottom: 40,
borderLeft: `3px solid ${C.cognac}`,
paddingLeft: 32,
maxWidth: 600,
}}>
Die Haut ist keine Krankheit. Sie ist die Botschaft."
</blockquote>
<p style={{ color: C.textMuted, fontSize: "1rem", lineHeight: 1.8, marginBottom: 16, maxWidth: 560 }}>
Olga Izieva sieht seit 33 Jahren, was Ärzte nicht sehen wollen: Hautprobleme sind keine Oberflächenfrage. Sie zeigen, was im Körper wirklich passiert.
</p>
<p style={{ color: C.textMuted, fontSize: "1rem", lineHeight: 1.8, maxWidth: 560 }}>
Als ihre eigene Haut durch Stress zusammenbrach und niemand helfen konnte, entwickelte sie die Skin Signal Method. Ein systemischer Ansatz, der Haut als das liest, was sie ist: das ehrlichste Diagnoseinstrument, das wir haben.
</p>
</div>
</section>
{/* ── VALUE STACK ────────────────────────────────────────── */}
<section style={{ background: C.white, padding: "80px 32px" }}>
<div style={{ maxWidth: 720, margin: "0 auto" }}>
<p style={{ color: C.cognac, fontSize: "0.72rem", fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", marginBottom: 16 }}>
Was du bekommst
</p>
<h2 style={{
...serif,
color: C.dark,
fontSize: "clamp(1.7rem, 3.5vw, 2.2rem)",
fontWeight: 500,
marginBottom: 8,
}}>
Skin Signal Deep Scan
</h2>
<p style={{ color: C.textMuted, fontSize: "0.95rem", marginBottom: 48 }}>
90 Minuten. Vollständige systemische Analyse. Alles schriftlich.
</p>
<div style={{ display: "flex", flexDirection: "column", gap: 0, marginBottom: 40, borderTop: `1.5px solid ${C.parchmentDark}` }}>
{[
["Visuelle Körperanalyse", "Haut, Zunge, Nägel, Haare, Lippen — Olga liest alle äußeren Signale deines Körpers", "150 €"],
["Systemische Anamnese", "Hormone, Darm, Schlaf, Stress, Ernährung, Bewegung, Medikamente — beide vollständigen Fragebögen", "180 €"],
["Schriftliche System-Hypothese", "Olgas Einschätzung was systemisch hinter deinen Symptomen steckt — benannt, nicht vermutet", "220 €"],
["7-Tage-Sofortplan", "Drei bis fünf konkrete Schritte die heute noch beginnen können — schriftlich", "102 €"],
["Klare Einschätzung nächster Schritt", "Du weißt danach genau was als nächstes kommt — Olga schlägt die konkrete Richtung vor", "50 €"],
].map(([title, desc, value], i) => (
<div key={i} style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 20,
padding: "20px 0",
borderBottom: `1px solid ${C.parchmentDark}`,
}}>
<div style={{ flex: 1 }}>
<p style={{ fontWeight: 600, color: C.textDark, fontSize: "0.93rem", marginBottom: 3 }}>{title}</p>
<p style={{ color: C.textMuted, fontSize: "0.84rem", lineHeight: 1.55 }}>{desc}</p>
</div>
<p style={{ color: C.cognac, fontWeight: 600, fontSize: "0.9rem", whiteSpace: "nowrap", paddingTop: 2 }}>
{value}
</p>
</div>
))}
</div>
{/* Preisblock */}
<div style={{
background: C.green,
borderRadius: 6,
padding: "32px 36px",
marginBottom: 20,
}}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 10, flexWrap: "wrap", gap: 8 }}>
<p style={{ color: C.parchment, fontSize: "0.88rem", opacity: 0.7 }}>Gesamtwert</p>
<p style={{ ...serif, color: C.parchment, fontSize: "1.4rem", textDecoration: "line-through", opacity: 0.4 }}>702 </p>
</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", flexWrap: "wrap", gap: 8 }}>
<p style={{ color: C.parchment, fontSize: "1rem", fontWeight: 600 }}>Pilotpreis für 5 Bestandskundinnen</p>
<p style={{ ...serif, color: C.white, fontSize: "2.6rem", fontWeight: 600, lineHeight: 1 }}>197 </p>
</div>
</div>
<p style={{ color: C.textMuted, fontSize: "0.83rem", lineHeight: 1.65, fontStyle: "italic", marginBottom: 40 }}>
Das ist der erste strukturierte Durchlauf der Skin Signal Method. Du bekommst die vollständige Analyse. Im Gegenzug nimmt sich Olga die Zeit, die sie im regulären Praxiskontext nicht hätte.
</p>
<div style={{ textAlign: "center" }}>
<a
href="#termin"
style={{
display: "inline-block",
background: C.cognac,
color: C.white,
padding: "15px 42px",
fontSize: "0.95rem",
fontWeight: 600,
textDecoration: "none",
borderRadius: 3,
letterSpacing: "0.04em",
}}
>
Meinen Platz sichern
</a>
</div>
</div>
</section>
{/* ── TRANSFORMATION ─────────────────────────────────────── */}
<section style={{ background: C.parchmentMid, padding: "80px 32px" }}>
<div style={{ maxWidth: 720, margin: "0 auto" }}>
<p style={{ color: C.cognac, fontSize: "0.72rem", fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", marginBottom: 24 }}>
Was danach anders ist
</p>
<div style={{ borderLeft: `3px solid ${C.green}`, paddingLeft: 32 }}>
<p style={{ ...serif, color: C.dark, fontSize: "clamp(1.2rem, 2.5vw, 1.5rem)", lineHeight: 1.7, marginBottom: 20, fontStyle: "italic" }}>
Nach diesem Gespräch weißt du endlich, was dein Körper dir die ganze Zeit sagen wollte."
</p>
<p style={{ color: C.textMuted, fontSize: "0.97rem", lineHeight: 1.8, marginBottom: 16 }}>
Die Haut die müde wirkt, die Energie die fehlt, das Gewicht das sich trotz allem nicht bewegt du verstehst jetzt das Muster dahinter. Olga hat dich als Gesamtbild gelesen: Haut, Zunge, Nägel, Haare, Darm, Hormone, Schlaf, Stress alles zusammen.
</p>
<p style={{ color: C.textMuted, fontSize: "0.97rem", lineHeight: 1.8 }}>
Du verlässt das Gespräch nicht mit einem weiteren Ratschlag. Sondern mit einer klaren Einschätzung was systemisch hinter deinen Symptomen steckt und einem 7-Tage-Plan der heute noch startet.{" "}
<strong style={{ color: C.dark }}>Dein Körper war nie dein Feind. Er hat nur auf Klarheit gewartet.</strong>
</p>
</div>
</div>
</section>
{/* ── ÜBER OLGA ──────────────────────────────────────────── */}
<section style={{ background: C.white, padding: "80px 32px" }}>
<div style={{ maxWidth: 720, margin: "0 auto" }}>
<p style={{ color: C.cognac, fontSize: "0.72rem", fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", marginBottom: 40 }}>
Über Olga Izieva
</p>
<div style={{
display: "grid",
gridTemplateColumns: "200px 1fr",
gap: 48,
alignItems: "start",
}}
className="olga-bio-grid"
>
{/* Foto */}
<div style={{ position: "relative" }}>
<div style={{
position: "relative",
width: 200,
height: 240,
borderRadius: 4,
overflow: "hidden",
boxShadow: "0 8px 32px rgba(28,36,32,0.12)",
}}>
<Image
src="/olga.jpg"
alt="Olga Izieva"
fill
style={{ objectFit: "cover", objectPosition: "center top" }}
priority
/>
</div>
<div style={{
position: "absolute",
bottom: -12,
right: -12,
width: 80,
height: 80,
borderRadius: "50%",
background: C.parchment,
border: `3px solid ${C.cognac}`,
display: "flex",
alignItems: "center",
justifyContent: "center",
...serif,
color: C.cognac,
fontSize: "0.65rem",
letterSpacing: "0.06em",
textTransform: "uppercase",
textAlign: "center",
lineHeight: 1.4,
fontWeight: 500,
padding: 4,
}}>
33 Jahre<br />Erfahrung
</div>
</div>
{/* Text */}
<div>
<h2 style={{
...serif,
color: C.dark,
fontSize: "clamp(1.6rem, 3vw, 2.2rem)",
fontWeight: 500,
marginBottom: 20,
lineHeight: 1.2,
}}>
Die Frau, die deinen Körper liest wie niemand sonst
</h2>
<p style={{ color: C.textMuted, fontSize: "0.95rem", lineHeight: 1.8, marginBottom: 16 }}>
Olga Izieva hat 33 Jahre als Kosmetikerin gearbeitet und dabei täglich gesehen, was Ärzte nicht sehen wollten: Die Haut ist kein kosmetisches Problem. Sie ist ein systemisches Signal.
</p>
<p style={{ color: C.textMuted, fontSize: "0.95rem", lineHeight: 1.8, marginBottom: 28 }}>
Sie ist Heilpraktikerin, hat Zertifikate in Ernährungswissenschaft, Psychologie und Medical Cosmetology. Sie koordiniert Ärzte, Spezialisten und Coaches der einzige Hub, der den visuellen Diagnosezugang der Kosmetik mit systemischer Medizin verbindet.
</p>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{[
"33 Jahre klinische Praxis als Kosmetikerin",
"Ausgebildete Heilpraktikerin",
"Zertifikate in Ernährungswissenschaft & Psychologie",
"Medical Cosmetology",
"Skin Signal Method™ — aus der eigenen Praxis heraus entwickelt",
].map((point, i) => (
<div key={i} style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
<span style={{ color: C.cognac, marginTop: 2, fontSize: "0.9rem" }}></span>
<p style={{ color: C.textDark, fontSize: "0.88rem", lineHeight: 1.5 }}>{point}</p>
</div>
))}
</div>
</div>
</div>
</div>
</section>
{/* ── GARANTIE ───────────────────────────────────────────── */}
<section style={{ background: C.parchment, padding: "80px 32px" }}>
<div style={{ maxWidth: 720, margin: "0 auto" }}>
<p style={{ color: C.cognac, fontSize: "0.72rem", fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", marginBottom: 16 }}>
Olgas Versprechen
</p>
<h2 style={{
...serif,
color: C.dark,
fontSize: "clamp(1.6rem, 3.5vw, 2.2rem)",
fontWeight: 500,
marginBottom: 48,
}}>
Ohne Wenn und Aber.
</h2>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 24 }}>
{[
{
label: "Klarheits-Garantie",
text: "Wenn du nach unserem Gespräch nicht weißt, was dein nächster konkreter Schritt ist — wir machen es nochmal. Kostenlos.",
},
{
label: "7-Tage-Garantie",
text: "Setz den 7-Tage-Sofortplan um. Wenn du nach einer Woche keine spürbare Veränderung merkst, bekommst du ein kostenloses 20-Minuten-Nachgespräch dazu.",
},
].map((g, i) => (
<div key={i} style={{
background: C.white,
borderRadius: 4,
padding: "28px 28px",
borderTop: `3px solid ${C.cognac}`,
}}>
<p style={{ color: C.cognac, fontSize: "0.72rem", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 14 }}>
{g.label}
</p>
<p style={{ ...serif, color: C.dark, fontSize: "1.05rem", lineHeight: 1.65, fontStyle: "italic" }}>
{g.text}"
</p>
</div>
))}
</div>
</div>
</section>
{/* ── ORDER FORM ─────────────────────────────────────────── */}
<section id="termin" style={{ background: C.sage, padding: "88px 32px 96px" }}>
<div style={{ maxWidth: 560, margin: "0 auto" }}>
<p style={{ color: C.cognac, fontSize: "0.72rem", fontWeight: 600, letterSpacing: "0.14em", textTransform: "uppercase", marginBottom: 16 }}>
Platz sichern
</p>
<h2 style={{
...serif,
color: C.dark,
fontSize: "clamp(1.7rem, 3.5vw, 2.4rem)",
fontWeight: 500,
marginBottom: 8,
lineHeight: 1.2,
}}>
Dein Platz im Skin Signal Deep Scan
</h2>
<p style={{ color: C.textMuted, fontSize: "0.93rem", marginBottom: 6, lineHeight: 1.65 }}>
Füll das Formular aus Olga meldet sich persönlich bei dir für die Terminvereinbarung.
</p>
<p style={{ color: C.cognac, fontSize: "0.85rem", fontWeight: 600, marginBottom: 44 }}>
Pilotpreis 197 &nbsp;·&nbsp; 90 Minuten &nbsp;·&nbsp; 5 Plätze
</p>
{submitted ? (
<div style={{
background: C.white,
borderRadius: 6,
padding: "40px 36px",
textAlign: "center",
borderTop: `3px solid ${C.green}`,
}}>
<p style={{
...serif,
color: C.green,
fontSize: "1.9rem",
fontStyle: "italic",
marginBottom: 12,
}}>
Anfrage eingegangen.
</p>
<p style={{ color: C.textMuted, fontSize: "0.95rem", lineHeight: 1.7 }}>
Olga meldet sich innerhalb von 24 Stunden persönlich bei dir mit allen Details zum Termin.
</p>
</div>
) : (
<form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: 18 }}>
{[
{ name: "name", label: "Dein Name", type: "text", placeholder: "Vorname Nachname", required: true },
{ name: "email", label: "E-Mail-Adresse", type: "email", placeholder: "deine@email.de", required: true },
{ name: "phone", label: "Telefonnummer (optional)", type: "tel", placeholder: "+49 ...", required: false },
].map((field) => (
<div key={field.name}>
<label style={{
display: "block",
color: C.textDark,
fontSize: "0.85rem",
fontWeight: 600,
marginBottom: 7,
letterSpacing: "0.02em",
}}>
{field.label}
</label>
<input
type={field.type}
name={field.name}
value={formData[field.name as keyof typeof formData]}
onChange={handleChange}
placeholder={field.placeholder}
required={field.required}
style={{
width: "100%",
padding: "12px 16px",
border: `1.5px solid ${C.parchmentDark}`,
borderRadius: 3,
background: C.white,
color: C.textDark,
fontSize: "0.95rem",
outline: "none",
boxSizing: "border-box",
}}
/>
</div>
))}
<div>
<label style={{
display: "block",
color: C.textDark,
fontSize: "0.85rem",
fontWeight: 600,
marginBottom: 7,
letterSpacing: "0.02em",
}}>
Was beschäftigt dich gerade am meisten? (optional)
</label>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
rows={4}
placeholder="Beschreib kurz was dich beschäftigt — Olga bereitet sich damit auf das Gespräch vor."
style={{
width: "100%",
padding: "12px 16px",
border: `1.5px solid ${C.parchmentDark}`,
borderRadius: 3,
background: C.white,
color: C.textDark,
fontSize: "0.95rem",
outline: "none",
resize: "vertical",
boxSizing: "border-box",
fontFamily: "var(--font-dm-sans, system-ui, sans-serif)",
}}
/>
</div>
<button
type="submit"
style={{
background: C.cognac,
color: C.white,
padding: "15px 36px",
fontSize: "0.95rem",
fontWeight: 600,
border: "none",
borderRadius: 3,
cursor: "pointer",
letterSpacing: "0.04em",
marginTop: 4,
alignSelf: "flex-start",
}}
>
Anfrage absenden
</button>
<p style={{ color: C.textMuted, fontSize: "0.79rem", lineHeight: 1.55 }}>
Kein automatischer Kauf. Olga meldet sich persönlich. Zahlung erfolgt nach Terminbestätigung.
</p>
</form>
)}
</div>
</section>
{/* ── FOOTER ─────────────────────────────────────────────── */}
<footer style={{ background: C.dark, padding: "24px 32px" }}>
<div style={{ maxWidth: 720, margin: "0 auto", display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 10 }}>
<p style={{ ...serif, color: C.parchment, fontSize: "0.95rem", opacity: 0.65, letterSpacing: "0.06em" }}>
Olga Izieva · Skin Signal Method
</p>
<p style={{ color: C.parchment, fontSize: "0.75rem", opacity: 0.35 }}>
Impressum · Datenschutz
</p>
</div>
</footer>
</main>
)
}

View file

@ -1,12 +1,10 @@
import Image from "next/image";
import WeekTimeline from "./components/WeekTimeline";
import FlipCards from "./components/FlipCards";
import BonusCards from "./components/BonusCards";
import MountainClimb from "./components/MountainClimb";
import {
CalendarIcon, GradCapIcon, UsersIcon, ClipboardIcon,
TargetIcon, LightbulbIcon, DocumentIcon, PhoneIcon,
VideoCameraIcon, HandshakeIcon, SearchIcon, MapIcon,
PackageIcon, ChatIcon, CheckIcon,
CalendarIcon, GradCapIcon, UsersIcon, ClipboardIcon, CheckIcon,
} from "./components/Icons";
const CTA_HREF = "#platz-sichern";
@ -66,14 +64,14 @@ export default function Home() {
<section className="py-24 lg:py-32 text-center relative overflow-hidden">
<div className="absolute inset-0" style={{ background: "radial-gradient(ellipse 80% 60% at 50% 0%, rgba(99,102,241,0.08) 0%, transparent 70%)" }} />
<div className="relative max-w-4xl mx-auto px-6 space-y-7">
<Pill>Online Gruppencoaching · Pilotprogramm · Start 1. Juli 2027</Pill>
<Pill>Praxis-Bootcamp · Pilotprogramm · Start 1. August 2026</Pill>
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-slate-900 leading-[1.1] tracking-tight">
In 8 Wochen zu deinem eigenen{" "}
Das 8-Wochen{" "}
<span className="bg-gradient-to-r from-indigo-600 to-violet-600 bg-clip-text text-transparent">
Kundengewinnungs-System
Praxis-Bootcamp:
</span>{" "}
speziell für Selbstständige
dein Kundengewinnungs-System für Selbstständige
</h1>
<p className="text-xl md:text-2xl text-slate-600 max-w-2xl mx-auto">
@ -83,7 +81,7 @@ export default function Home() {
<div className="flex flex-col items-center gap-4">
<Btn xl>Meinen Platz sichern </Btn>
<p className="text-sm text-slate-500">
Exklusive Kleingruppe · Maximal 10 Plätze · Nur noch 8 frei
Exklusive Kleingruppe · Maximal 10 Plätze
</p>
</div>
</div>
@ -134,7 +132,7 @@ export default function Home() {
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<IconCard icon={<CalendarIcon className="w-7 h-7 text-white" />} title="8 Wochen" sub="Schritt für Schritt aufgebaut — Woche für Woche" />
<IconCard icon={<GradCapIcon className="w-7 h-7 text-white" />} title="5 Experten" sub="An deiner Seite — aus Vertrieb, Social Media & Video" />
<IconCard icon={<GradCapIcon className="w-7 h-7 text-white" />} title="6 Experten" sub="An deiner Seite — aus Vertrieb, Social Media & Video" />
<IconCard icon={<UsersIcon className="w-7 h-7 text-white" />} title="10 Plätze" sub="Exklusive Kleingruppe — maximale Aufmerksamkeit" />
<IconCard icon={<ClipboardIcon className="w-7 h-7 text-white" />} title="25+ Vorlagen" sub="Praxis-Vorlagen die du sofort einsetzen kannst" />
</div>
@ -155,10 +153,10 @@ export default function Home() {
Die meisten Programme erklären dir entweder Marketing, Vertrieb oder Social Media getrennt voneinander. Du gehst mit Wissen nach Hause. Und weißt immer noch nicht, wie das alles zusammenhängt.
</p>
<p className="text-slate-600 leading-relaxed text-lg">
Das <strong className="text-slate-900">8-Wochen Kundengewinnungs-System</strong> macht genau das: Marketing, Vertrieb und Social Media in einem System, das Woche für Woche aufgebaut wird.
Das <strong className="text-slate-900">8-Wochen Praxis-Bootcamp</strong> macht genau das: Marketing, Vertrieb und Social Media in einem System, das Woche für Woche aufgebaut wird.
</p>
<p className="text-slate-800 font-semibold text-lg leading-relaxed">
Du baust kein neues Wissen auf. Du nimmst alles was du hast und bringst es in eine Struktur, die planbar die richtigen Kunden anzieht.
Kein Theorie-Overload. Du nimmst alles was du hast und setzt es endlich in eine Struktur um, die planbar die richtigen Kunden anzieht.
</p>
</div>
</section>
@ -168,46 +166,20 @@ export default function Home() {
*/}
<section className="mc-dark-bg py-16 text-center">
<div className="max-w-2xl mx-auto px-6 space-y-5">
<h2 className="text-2xl md:text-3xl font-extrabold text-white">
Nur noch <span className="bg-gradient-to-r from-indigo-400 to-violet-400 bg-clip-text text-transparent">8 Plätze</span> verfügbar.
<h2 className="text-2xl md:text-3xl font-extrabold" style={{ color: "#ffffff" }}>
Sichere dir jetzt deinen Platz.
</h2>
<p className="text-slate-400">Start: 1. Juli 2027 · Pilotpreis: 500 (statt 3.000 )</p>
<p style={{ color: "#e2e8f0" }}>Start: 1. August 2026 · Pilotpreis: 500 netto (statt 3.000 )</p>
<Btn xl>Meinen Platz sichern </Btn>
</div>
</section>
{/*
S3b NACH 8 WOCHEN NIMMST DU MIT
2×4 Icon-Kacheln
*/}
<section className="py-20 bg-white">
<div className="max-w-5xl mx-auto px-6">
<div className="text-center mb-12">
<Pill>Deine Ergebnisse</Pill>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">Nach 8 Wochen nimmst du mit:</h2>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-5">
<IconCard icon={<TargetIcon className="w-7 h-7 text-white" />} title="Klare Wunschkunden-Persona" sub="+ vollständiger Marketing-Audit" />
<IconCard icon={<LightbulbIcon className="w-7 h-7 text-white" />} title="Angebot auf den Punkt" sub="Positionierung in einem Satz — für alle Kanäle" />
<IconCard icon={<DocumentIcon className="w-7 h-7 text-white" />} title="Deine Verkaufsseite" sub="Vollständige Struktur — fertig zum Umsetzen" />
<IconCard icon={<CalendarIcon className="w-7 h-7 text-white" />} title="90-Tage-Marketingplan" sub="+ 2 fokussierte Kanäle entschieden" />
<IconCard icon={<PhoneIcon className="w-7 h-7 text-white" />} title="Content-Plan 4 Wochen" sub="Was, wann und warum du postest" />
<IconCard icon={<VideoCameraIcon className="w-7 h-7 text-white" />} title="Social Media Verkauf" sub="+ professionelles Video-Setup mit dem Handy" />
<IconCard icon={<HandshakeIcon className="w-7 h-7 text-white" />} title="Dein Verkaufsskript" sub="Für Direktansprache und Netzwerkveranstaltungen" />
<IconCard icon={<SearchIcon className="w-7 h-7 text-white" />} title="Google Business optimiert" sub="+ SEO-Grundlagen für deine Website" />
</div>
</div>
</section>
{/*
S4 WAS DU BAUST (Mountain animation)
Mountain animation (ersetzt Kacheln)
*/}
<MountainClimb />
<WeekTimeline />
<FlipCards />
{/*
MEET THE COACH Brunson-Style
Echtes Foto links + Bio rechts
@ -249,10 +221,11 @@ export default function Home() {
<div className="border-t border-slate-100 pt-5 space-y-3">
<p className="text-xs font-bold text-slate-400 uppercase tracking-widest">Dein Expertenteam:</p>
{[
{ name: "Katja Pestereva", role: "Marketing-System, Positionierung, SEO", weeks: "Wo. 1, 2, 3, 4, 8" },
{ name: "Katja Pestereva", role: "Marketing-System, Einstiegsangebot, Positionierung, Kanal-Strategie", weeks: "Wo. 1, 2, 3, 4" },
{ name: "Can Turkdogan", role: "Content-Strategie & Social Media", weeks: "Woche 5" },
{ name: "Stefan & Philipp · OWV", role: "Professionelles Filmen & Video", weeks: "Woche 6" },
{ name: "Manuela Ludewig", role: "Vertrieb & Direktansprache", weeks: "Woche 7" },
{ name: "Pia Nestler", role: "Finanzmanagement für Selbstständige", weeks: "Woche 8" },
].map(({ name, role, weeks }) => (
<div key={name} className="flex items-start gap-2 text-sm">
<div className="w-1.5 h-1.5 rounded-full bg-indigo-500 flex-shrink-0 mt-2" />
@ -269,6 +242,10 @@ export default function Home() {
</div>
</section>
<WeekTimeline />
<FlipCards />
{/*
TESTIMONIALS
*/}
@ -276,20 +253,79 @@ export default function Home() {
<div className="max-w-5xl mx-auto px-6">
<div className="text-center mb-12">
<Pill>Erfahrungen</Pill>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">Was Selbstständige über die Zusammenarbeit mit Katja sagen</h2>
<p className="mt-2 text-slate-500 text-sm italic">Das Programm startet Juli 2027. Diese Erfahrungen kommen aus der direkten Zusammenarbeit mit Katja.</p>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">Was unsere Kunden über unsere Zusammenarbeit sagen</h2>
<p className="mt-2 text-slate-500 text-sm italic">Das Programm startet August 2026. Diese Erfahrungen kommen aus der direkten Zusammenarbeit mit Market Compass.</p>
</div>
<div className="grid md:grid-cols-3 gap-6">
{[1, 2, 3].map((i) => (
<div key={i} className="mc-card p-6 space-y-4">
{/* Featured — Varvara (stärkste Geschichte) */}
<div className="mc-card p-6 md:p-8 mb-6" style={{ boxShadow: "inset 0 3px 0 #6366f1, 0 2px 12px rgba(99,102,241,0.06)" }}>
<div className="flex flex-col sm:flex-row gap-6">
<div className="relative w-16 h-16 rounded-full overflow-hidden flex-shrink-0 bg-indigo-100">
<Image src="/testimonials/varvara.png" alt="Varvara Beloussova" fill className="object-cover" sizes="64px" />
</div>
<div className="space-y-3 flex-1">
<div className="text-yellow-400 text-lg tracking-wider"></div>
<p className="text-slate-500 italic text-sm leading-relaxed">[Google-Bewertung {i} einfügen]</p>
<div className="flex items-center gap-3 border-t border-slate-100 pt-4">
<div className="w-9 h-9 rounded-full bg-gradient-to-br from-indigo-400 to-violet-400 flex-shrink-0" />
<p className="text-slate-400 text-xs"> [Name, Berufsfeld]</p>
<p className="text-slate-600 leading-relaxed italic">
Ich hatte einen Online-Kurs für angehende Psycholog:innen vorbereitet, viel Energie hineingesteckt aber über Instagram kamen einfach keine Anfragen. Unsere Beratung war kein oberflächliches Marketing-Gerede', sondern ein sehr ehrlicher, klarer Blick auf meine Situation. Mir wurde erklärt, dass meine Zielgruppe schlicht nicht in meinem Instagram vertreten ist und ich bekam ganz konkrete nächste Schritte. Schon am nächsten Tag waren 6 von 12 Plätzen vergeben! Warm, menschlich und gleichzeitig unglaublich präzise und professionell. Ich würde Market Compass von ganzem Herzen empfehlen."
</p>
<div className="border-t border-slate-100 pt-3">
<p className="font-semibold text-slate-900 text-sm">Varvara Beloussova</p>
<p className="text-slate-400 text-xs">Online-Kurs für Psycholog:innen</p>
</div>
</div>
))}
</div>
</div>
{/* 3 weitere Bewertungen */}
<div className="grid md:grid-cols-3 gap-6">
{/* Anke */}
<div className="mc-card p-6 space-y-4">
<div className="text-yellow-400 text-lg tracking-wider"></div>
<p className="text-slate-500 italic text-sm leading-relaxed">
Frau Pestereva überzeugt durch ihre überaus hohe fachliche Kompetenz, ihre schnelle und zuverlässige Unterstützung sowie ihre sehr freundliche Art. Die Kommunikation verlief stets unkompliziert, schnell und lösungsorientiert. Ich kann Market Compass uneingeschränkt weiterempfehlen."
</p>
<div className="flex items-center gap-3 border-t border-slate-100 pt-4">
<div className="relative w-9 h-9 rounded-full overflow-hidden flex-shrink-0 bg-indigo-100">
<Image src="/testimonials/anke.png" alt="Anke Rühle-Metz" fill className="object-cover" sizes="36px" />
</div>
<div>
<p className="font-semibold text-slate-900 text-xs">Anke Rühle-Metz</p>
<p className="text-slate-400 text-xs">Unternehmerin</p>
</div>
</div>
</div>
{/* Onlinewerbevideo */}
<div className="mc-card p-6 space-y-4">
<div className="text-yellow-400 text-lg tracking-wider"></div>
<p className="text-slate-500 italic text-sm leading-relaxed">
Auch wenn wir schon eine Weile als Videoproduktion in der Marketing-Welt unterwegs sind, konnten wir noch sehr viel in unseren Sessions mitnehmen. Allein die Zielgruppenanalyse war es wert wir freuen uns auf die weitere Zusammenarbeit."
</p>
<div className="flex items-center gap-3 border-t border-slate-100 pt-4">
<div className="relative w-9 h-9 rounded-full overflow-hidden flex-shrink-0 bg-indigo-100">
<Image src="/testimonials/onlinewerbevideo.png" alt="Onlinewerbevideo" fill className="object-cover" sizes="36px" />
</div>
<div>
<p className="font-semibold text-slate-900 text-xs">Onlinewerbevideo</p>
<p className="text-slate-400 text-xs">Videoproduktion</p>
</div>
</div>
</div>
{/* Ines */}
<div className="mc-card p-6 space-y-4">
<div className="text-yellow-400 text-lg tracking-wider"></div>
<p className="text-slate-500 italic text-sm leading-relaxed">
Sehr kompetent, gehen individuell auf die Kunden ein, hören sehr gut zu und erarbeiten marktfähige Konzepte. Das Personal ist super nett."
</p>
<div className="flex items-center gap-3 border-t border-slate-100 pt-4">
<div className="w-9 h-9 rounded-full bg-gradient-to-br from-indigo-400 to-violet-400 flex-shrink-0" />
<div>
<p className="font-semibold text-slate-900 text-xs">Ines Thiele-Seidel</p>
<p className="text-slate-400 text-xs">Unternehmerin</p>
</div>
</div>
</div>
</div>
</div>
</section>
@ -301,33 +337,15 @@ export default function Home() {
<div className="max-w-5xl mx-auto px-6">
<div className="text-center mb-12">
<Pill>Bonus-Paket</Pill>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">Und du bist während und nach dem Programm nie allein.</h2>
<h2 className="mt-4 text-3xl md:text-4xl font-extrabold text-slate-900">Hier ist alles was du <span className="bg-gradient-to-r from-indigo-600 to-violet-600 bg-clip-text text-transparent">HEUTE</span> zusätzlich zum Coaching Programm erhältst.</h2>
<p className="mt-3 text-slate-500 text-lg">Alles was du brauchst, um dein Business richtig voran zu bringen.</p>
</div>
<div className="mc-card p-6 mb-8 bg-gradient-to-r from-indigo-50 to-violet-50 border-indigo-100 text-center">
<p className="text-slate-700 leading-relaxed">
<strong>Die gesamten 8 Wochen:</strong> In der Messenger-Community beantwortet Katja täglich Fragen. Du kannst jederzeit posten Feedback kommt. 8 Wochen tägliche Begleitung.
</p>
</div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{[
{ n: 1, icon: <PackageIcon className="w-6 h-6 text-indigo-500" />, title: "Sofort loslegen: 7 fertige Vorlagen", desc: "Alle wichtigen Vorlagen direkt anwendbar — du fängst nicht bei null an.", val: "497 €" },
{ n: 2, icon: <PhoneIcon className="w-6 h-6 text-indigo-500" />, title: "Kunden gewinnen über Instagram", desc: "Wie du über Social Media planbar Anfragen bekommst — Schritt für Schritt.", val: "297 €" },
{ n: 3, icon: <VideoCameraIcon className="w-6 h-6 text-indigo-500" />, title: "Professionell filmen — mit dem Handy", desc: "Kein teures Equipment. Die Grundlagen von den Video-Experten.", val: "197 €" },
{ n: 4, icon: <MapIcon className="w-6 h-6 text-indigo-500" />, title: "Welche Plattform passt zu dir?", desc: "Kein Rätselraten — du weißt auf welcher Plattform deine Wunschkunden sind.", val: "147 €" },
{ n: 5, icon: <ChatIcon className="w-6 h-6 text-indigo-500" />, title: "1 Monat Begleitung nach dem Programm", desc: "Die Messenger-Community bleibt einen Monat nach dem Programm offen.", val: "97 €" },
{ n: 6, icon: <HandshakeIcon className="w-6 h-6 text-indigo-500" />, title: "Abschluss-Netzwerkevent (geplant)", desc: "Persönliches Feedback von Katja. Neue Kontakte. Exklusiv für den Pilotdurchlauf.", val: "197 €" },
].map(({ n, icon, title, desc, val }) => (
<div key={n} className="mc-card p-5 space-y-2">
<div className="flex items-center justify-between">
<span>{icon}</span>
<span className="text-xs font-bold text-emerald-600 bg-emerald-50 border border-emerald-200 rounded-full px-2 py-0.5">Wert: {val}</span>
</div>
<p className="text-xs font-bold text-indigo-600 uppercase tracking-wide">Bonus {n}</p>
<p className="font-bold text-slate-900 text-base">{title}</p>
<p className="text-slate-500 text-sm leading-relaxed">{desc}</p>
</div>
))}
</div>
<BonusCards />
</div>
</section>
@ -344,7 +362,7 @@ export default function Home() {
<table className="w-full text-sm">
<tbody>
{[
["8-Wochen Programm (Marketing · Vertrieb · Social Media · Video)", "3.000 €"],
["8-Wochen Praxis-Bootcamp (Marketing · Vertrieb · Social Media · Video)", "3.000 €"],
["Bonus 1: Sofort loslegen — 7 fertige Vorlagen", "497 €"],
["Bonus 2: Kunden gewinnen über Instagram", "297 €"],
["Bonus 3: Professionell filmen mit dem Handy", "197 €"],
@ -368,9 +386,9 @@ export default function Home() {
<div className="mt-12 text-center space-y-6">
<p className="text-slate-400 line-through text-sm">Regulär 3.000 </p>
<p className="text-8xl font-extrabold bg-gradient-to-r from-indigo-600 to-violet-600 bg-clip-text text-transparent leading-none">
500
500 netto
</p>
<p className="text-xl text-slate-700 font-semibold">gesamt · 2 × 250 / Monat</p>
<p className="text-xl text-slate-700 font-semibold">gesamt · 2 × 250 netto / Monat</p>
<div className="mc-card p-6 text-left space-y-2 bg-indigo-50 border-indigo-100">
<p className="font-bold text-slate-900">Warum dieser Preis?</p>
@ -391,7 +409,7 @@ export default function Home() {
</div>
<Btn xl>Meinen Platz sichern </Btn>
<p className="text-sm text-slate-500">Maximal 10 Plätze · Start: 1. Juli 2027 · Du entscheidest nach unserem ersten Gespräch.</p>
<p className="text-sm text-slate-500">Maximal 10 Plätze · Start: 1. August 2026 · Du entscheidest nach unserem ersten Gespräch.</p>
</div>
</div>
</section>
@ -411,10 +429,10 @@ export default function Home() {
{ q: "Was ist hier anders als bei anderen Kursen?", a: "Du bekommst kein neues Wissen. Du bringst alles was du hast in ein System. Live, mit Feedback, in einer kleinen Gruppe. Am Ende steht nicht ein Zertifikat — sondern eine fertige Verkaufsseiten-Struktur, ein Marketingplan, ein Verkaufsskript. Dinge die sofort eingesetzt werden können." },
{ q: "Wie viel Zeit brauche ich pro Woche?", a: "Plan 34 Stunden ein: 90 Minuten Workshop + die wöchentliche Aufgabe. Alle Aufzeichnungen bleiben abrufbar — wenn eine Woche stressiger ist, holst du nach." },
{ q: "Was wenn ich eine Session verpasse?", a: "Alle Workshops werden aufgezeichnet. Du holst nach wann es passt. Du verpasst nichts." },
{ q: "Wann startet das Programm — und wie melde ich mich an?", a: "Start: 1. Juli 2027. Maximal 10 Plätze verfügbar. Schreib dich für ein Gespräch ein — dort schauen wir gemeinsam ob das Programm zu dir und deiner Situation passt." },
{ q: "Wann startet das Programm — und wie melde ich mich an?", a: "Start: 1. August 2026. Maximal 10 Plätze verfügbar. Schreib dich für ein Gespräch ein — dort schauen wir gemeinsam ob das Programm zu dir und deiner Situation passt." },
{ q: "Was wenn es mir nichts bringt?", a: "14-Tage-Ausstiegsrecht nach der ersten Session — voller Betrag zurück, keine Fragen. Ziel nicht erreicht nach 8 Wochen? Kostenlose Verlängerung um 4 Wochen. (* Gilt wenn du aktiv teilgenommen und die Aufgaben umgesetzt hast.)" },
{ q: "Ich bin nicht sicher ob ich die Zeit habe.", a: "Das klären wir im Gespräch. Wir schauen zusammen auf deine Situation — und sagen dir ehrlich ob das jetzt der richtige Zeitpunkt ist." },
{ q: "Was genau zahle ich?", a: "500 € gesamt — 2 × 250 € / Monat. Das ist der Pilotpreis für den ersten Durchlauf. Der reguläre Preis liegt bei 3.000 €." },
{ q: "Was genau zahle ich?", a: "500 € netto gesamt — 2 × 250 € netto / Monat. Das ist der Pilotpreis für den ersten Durchlauf. Der reguläre Preis liegt bei 3.000 €." },
].map(({ q, a }, i) => (
<details key={i} className="mc-card overflow-hidden group">
<summary className="flex justify-between items-center p-5 cursor-pointer font-semibold text-slate-900 list-none">
@ -433,20 +451,20 @@ export default function Home() {
*/}
<section className="mc-dark-bg py-24 text-center">
<div className="max-w-2xl mx-auto px-6 space-y-7">
<h2 className="text-3xl md:text-5xl font-extrabold text-white leading-tight">
<h2 className="text-3xl md:text-5xl font-extrabold leading-tight" style={{ color: "#ffffff" }}>
Dein Wissen ist da.{" "}
<span className="bg-gradient-to-r from-indigo-400 to-violet-400 bg-clip-text text-transparent">
Jetzt bekommt es die Struktur die Kunden bringt.
</span>
</h2>
<p className="text-slate-400">Maximal 10 Plätze · Start: 1. Juli 2027</p>
<p style={{ color: "#e2e8f0" }}>Maximal 10 Plätze · Start: 1. August 2026</p>
<Btn xl>Meinen Platz sichern </Btn>
<p className="text-sm text-slate-500">Ein kurzes Gespräch du entscheidest danach.</p>
<p className="text-sm" style={{ color: "#cbd5e1" }}>Ein kurzes Gespräch du entscheidest danach.</p>
</div>
</section>
{/* FOOTER */}
<footer className="mc-nav-bg py-8 text-center text-slate-400 text-xs">
<footer className="mc-nav-bg py-8 text-center text-xs" style={{ color: "#cbd5e1" }}>
© 2026 Market Compass · Katja Pestereva
</footer>

108
src/lib/notion-pm.ts Normal file
View file

@ -0,0 +1,108 @@
const NOTION_VERSION = '2022-06-28'
function getHeaders() {
const token = process.env.PAI_NOTION_TOKEN
if (!token) throw new Error('PAI_NOTION_TOKEN not set')
return {
Authorization: `Bearer ${token}`,
'Notion-Version': NOTION_VERSION,
'Content-Type': 'application/json',
}
}
export const DBS: Record<string, string> = {
'GSO Gruppencoaching': '3641a317-e544-81f0-aa71-dc214949b532',
'OWV': '3641a317-e544-8105-84e8-d1bbcf6940a6',
'OSD': '3641a317-e544-8102-b677-ec4a77266d1c',
'Womenmatic': '3641a317-e544-8163-a10f-f2aa2669b2c6',
'Olga Izieva': '3641a317-e544-81ea-8f53-e21d20afda3f',
'Tecnoclean': '3641a317-e544-8113-a047-cb227d16d5ac',
'Dennis Alter': '3641a317-e544-810f-8a4c-e628e1d636fb',
'Interessenten': '3641a317-e544-8144-a67c-f39dab33b49c',
'Urlaub Kroatien': '3641a317-e544-819b-b28b-e6a49cf54dcb',
'Alltags ToDos': '3641a317-e544-812e-af33-e1912fe95416',
}
export interface Todo {
id: string
name: string
projekt: string
status: string | null
prioritaet: string | null
deadline: string | null
startdatum: string | null
geplanterTag: string | null
meilenstein: string | null
zeitH: number | null
tatsaechlicheZeitH: number | null
zeitdifferenzH: number | null
kategorie: string | null
imWochenplan: boolean
}
async function fetchDbPages(dbId: string): Promise<any[]> {
const headers = getHeaders()
const pages: any[] = []
let cursor: string | undefined
while (true) {
const body: Record<string, unknown> = { page_size: 100 }
if (cursor) body.start_cursor = cursor
const res = await fetch(`https://api.notion.com/v1/databases/${dbId}/query`, {
method: 'POST',
headers,
body: JSON.stringify(body),
next: { revalidate: 300 },
})
if (!res.ok) break
const data = await res.json()
pages.push(...(data.results ?? []))
if (!data.has_more) break
cursor = data.next_cursor
}
return pages
}
function parseTodo(page: any, projektName: string): Todo {
const p = page.properties ?? {}
const name = (p['Name']?.title ?? [])
.map((t: any) => t.plain_text as string)
.join('')
const zeitH = p['Zeit (h)']?.number ?? null
const tatsaechlicheZeitH = p['Tatsächliche Zeit (h)']?.number ?? null
const zdRaw = p['Zeitdifferenz (h)']?.formula
const zeitdifferenzH =
zdRaw?.type === 'number' ? (zdRaw.number ?? null) : null
return {
id: page.id,
name,
projekt: projektName,
status: p['Status']?.status?.name ?? null,
prioritaet: p['Priorität']?.select?.name ?? null,
deadline: p['Deadline']?.date?.start ?? null,
startdatum: p['Startdatum']?.date?.start ?? null,
geplanterTag: p['Geplanter Tag']?.date?.start ?? null,
meilenstein: p['Meilenstein']?.select?.name ?? null,
zeitH,
tatsaechlicheZeitH,
zeitdifferenzH,
kategorie: p['Kategorie']?.select?.name ?? null,
imWochenplan: p['Im Wochenplan']?.checkbox ?? false,
}
}
export async function fetchAllTodos(): Promise<Todo[]> {
const results = await Promise.all(
Object.entries(DBS).map(async ([name, id]) => {
const pages = await fetchDbPages(id)
return pages.map((p) => parseTodo(p, name))
})
)
return results.flat()
}